/plugins/XML/tags/release-2-0-7/xml/Resolver.java

#
Java | 570 lines | 394 code | 82 blank | 94 comment | 95 complexity | 5351719935d7000b4e8add93a87da4b0 MD5 | raw file

✨ Summary
  1. package xml;
  2. import java.awt.Component;
  3. import java.io.BufferedInputStream;
  4. import java.io.BufferedOutputStream;
  5. import java.io.File;
  6. import java.io.FileInputStream;
  7. import java.io.FileOutputStream;
  8. import java.io.IOException;
  9. import java.io.StringReader;
  10. import java.util.HashMap;
  11. import java.util.Iterator;
  12. import java.util.LinkedList;
  13. import java.util.List;
  14. import javax.swing.JOptionPane;
  15. import javax.swing.SwingUtilities;
  16. import org.apache.xerces.util.XMLCatalogResolver;
  17. import org.gjt.sp.jedit.Buffer;
  18. import org.gjt.sp.jedit.GUIUtilities;
  19. import org.gjt.sp.jedit.MiscUtilities;
  20. import org.gjt.sp.jedit.View;
  21. import org.gjt.sp.jedit.jEdit;
  22. import org.gjt.sp.jedit.io.VFS;
  23. import org.gjt.sp.jedit.io.VFSManager;
  24. import org.gjt.sp.util.Log;
  25. import org.xml.sax.InputSource;
  26. import org.xml.sax.SAXException;
  27. import org.xml.sax.ext.DefaultHandler2;
  28. /**
  29. * Resolver grabs and caches DTDs and xml schemas.
  30. * It also serves as a resource resolver for jeditresource: links
  31. * It is meant to replace CatalogManager.
  32. *
  33. * It is still under development and is not used yet.
  34. * It requires JDK 1.5.
  35. *
  36. * @author ezust
  37. *
  38. */
  39. public class Resolver extends DefaultHandler2
  40. {
  41. /** Ask before downloading */
  42. public static final int ASK = 0;
  43. /** Local files & catalogs only */
  44. public static final int LOCAL = 1;
  45. /** Download without asking */
  46. public static final int ALWAYS = 2;
  47. public static final String MODES[] = new String[] {"ask", "local", "always"};
  48. private static boolean loadedCache = false;
  49. static private boolean loadedCatalogs = false;
  50. public static final String NETWORK_PROPS = "xml.general.network";
  51. public static void reloadCatalogs()
  52. {
  53. loadedCatalogs = false;
  54. } //}}}
  55. /**
  56. * Ask before downloading remote files
  57. */
  58. public static final String MODE = NETWORK_PROPS + ".mode";
  59. /**
  60. * Cache downloaded remote files
  61. */
  62. public static final String CACHE = NETWORK_PROPS + ".cache";
  63. // {{{ static variables
  64. /** Internal catalog for DTDs which are packaged in
  65. * XML.jar and jEdit.jar */
  66. public static final String INTERNALCATALOG =
  67. "jeditresource:XML.jar!/xml/dtds/catalog";
  68. private static Object IGNORE = new Object();
  69. private static Resolver singleton = null;
  70. private static String resourceDir;
  71. // }}}
  72. // {{{ Instance Variables
  73. /** Internal catalog for DTDs which are packaged in
  74. * XML.jar and jEdit.jar
  75. Parses and manages the catalog files
  76. */
  77. private XMLCatalogResolver catalog = null;
  78. /** Mapping of URLs to public IDs */
  79. private HashMap reverseResourceCache;
  80. /** Mapping from public ID to URLs */
  81. private HashMap resourceCache;
  82. /** List of catalog files to load */
  83. private List catalogFiles;
  84. // }}}
  85. // {{{ instance()
  86. /**
  87. *
  88. * @return a global catalog resolver object you can use as an
  89. * LSResourceResolver or EntityResolver.
  90. */
  91. public static synchronized Resolver instance() {
  92. if (singleton == null) {
  93. singleton = new Resolver();
  94. singleton.load();
  95. }
  96. return singleton;
  97. }
  98. // }}}
  99. /**
  100. * You can't create an object directly.
  101. * use @ref instance() to get a singleton instance.
  102. *
  103. */
  104. private Resolver() {
  105. }
  106. private synchronized void load()
  107. {
  108. if(!loadedCache)
  109. {
  110. reverseResourceCache = new HashMap();
  111. resourceCache = new HashMap();
  112. if (isUsingCache())
  113. {
  114. resourceDir = MiscUtilities.constructPath(
  115. jEdit.getSettingsDirectory(),"dtds");
  116. }
  117. int i;
  118. String id, prop, uri;
  119. i = 0;
  120. while((id = jEdit.getProperty(prop = "xml.cache"
  121. + ".public-id." + i++)) != null)
  122. {
  123. uri = jEdit.getProperty(prop + ".uri");
  124. resourceCache.put(new Entry(Entry.PUBLIC,id,uri),uri);
  125. }
  126. i = 0;
  127. while((id = jEdit.getProperty(prop = "xml.cache"
  128. + ".system-id." + i++)) != null)
  129. {
  130. uri = jEdit.getProperty(prop + ".uri");
  131. Entry se = new Entry(Entry.SYSTEM,id,uri);
  132. resourceCache.put(se,uri);
  133. reverseResourceCache.put(uri,se);
  134. }
  135. loadedCache = true;
  136. }
  137. if (!loadedCatalogs) {
  138. loadedCatalogs = true;
  139. catalog = new XMLCatalogResolver();
  140. catalogFiles = new LinkedList();
  141. catalogFiles.add(INTERNALCATALOG);
  142. int i = 0;
  143. String uri = null;
  144. do {
  145. String prop = "xml.catalog." + i++;
  146. uri = jEdit.getProperty(prop);
  147. if (uri == null) break;
  148. if(MiscUtilities.isURL(uri))
  149. catalogFiles.add(uri);
  150. else
  151. catalogFiles.add(MiscUtilities.resolveSymlinks(uri));
  152. } while (uri != null);
  153. String[] catalogs = new String[catalogFiles.size()];
  154. for (i=0; i<catalogs.length; ++i)
  155. catalogs[i] = catalogFiles.get(i).toString();
  156. catalog.setPreferPublic(true);
  157. catalog.setCatalogList(catalogs);
  158. }
  159. } //}}}
  160. // {{{ resolveEntity
  161. /**
  162. * @param name
  163. * @param publicId
  164. * @param current
  165. * @param systemId
  166. */
  167. public InputSource resolveEntity(String name, String publicId, String current,
  168. String systemId) throws SAXException, java.io.IOException {
  169. load();
  170. if(publicId != null && publicId.length() == 0)
  171. publicId = null;
  172. if(systemId != null && systemId.length() == 0)
  173. systemId = null;
  174. String newSystemId = null;
  175. /* we need this hack to support relative path names inside
  176. * cached files. we want them to be resolved relative to
  177. * the original system ID of the cached resource, not the
  178. * cache file name on disk. */
  179. String parent;
  180. if(current != null)
  181. {
  182. Entry entry = (Entry)reverseResourceCache.get(current);
  183. if(entry != null)
  184. parent = entry.uri;
  185. else
  186. parent = MiscUtilities.getParentOfPath(current);
  187. }
  188. else
  189. parent = null;
  190. if(publicId == null && systemId != null && parent != null)
  191. {
  192. if(systemId.startsWith(parent))
  193. {
  194. // first, try resolving a relative name,
  195. // to handle jEdit built-in DTDs
  196. newSystemId = systemId.substring(parent.length());
  197. if(newSystemId.startsWith("/"))
  198. newSystemId = newSystemId.substring(1);
  199. newSystemId = resolveSystem(newSystemId);
  200. }
  201. }
  202. // next, try resolving full path name
  203. if(newSystemId == null)
  204. {
  205. if(publicId == null)
  206. newSystemId = resolveSystem(systemId);
  207. else
  208. newSystemId = resolvePublic(systemId,publicId);
  209. }
  210. // well, the catalog can't help us, so just assume the
  211. // system id points to a file
  212. if(newSystemId == null)
  213. {
  214. if(systemId == null)
  215. return null;
  216. else if(MiscUtilities.isURL(systemId))
  217. newSystemId = systemId;
  218. // XXX: is this correct?
  219. /* else if(systemId.startsWith("/"))
  220. newSystemId = "file://" + systemId;
  221. else if(parent != null && !MiscUtilities.isURL(parent))
  222. newSystemId = parent + systemId; */
  223. }
  224. if(newSystemId == null)
  225. return null;
  226. Buffer buf = jEdit.getBuffer(XmlPlugin.uriToFile(newSystemId));
  227. if(buf != null)
  228. {
  229. if(buf.isPerformingIO())
  230. VFSManager.waitForRequests();
  231. Log.log(Log.DEBUG, getClass(), "Found open buffer for " + newSystemId);
  232. InputSource source = new InputSource(publicId);
  233. source.setSystemId(newSystemId);
  234. try
  235. {
  236. buf.readLock();
  237. source.setCharacterStream(new StringReader(buf.getText(0,
  238. buf.getLength())));
  239. }
  240. finally
  241. {
  242. buf.readUnlock();
  243. }
  244. return source;
  245. }
  246. else if(newSystemId.startsWith("file:")
  247. || newSystemId.startsWith("jeditresource:"))
  248. {
  249. // InputSource source = new InputSource(systemId);
  250. // InputStream is = new URL(newSystemId).openStream();
  251. InputSource is = new InputSource(newSystemId);
  252. return is;
  253. }
  254. else if (getNetworkModeVal() == LOCAL)
  255. return null;
  256. else
  257. {
  258. final String _newSystemId = newSystemId;
  259. final VFS vfs = VFSManager.getVFSForPath(_newSystemId);
  260. // use a final array to pass a mutable value from the
  261. // invokeAndWait() call
  262. final Object[] session = new Object[1];
  263. Runnable run = new Runnable()
  264. {
  265. public void run()
  266. {
  267. View view = jEdit.getActiveView();
  268. if (getNetworkModeVal() == ASK && showDownloadResourceDialog(view,_newSystemId))
  269. {
  270. session[0] = vfs.createVFSSession(
  271. _newSystemId,view);
  272. }
  273. }
  274. };
  275. if(SwingUtilities.isEventDispatchThread())
  276. run.run();
  277. else
  278. {
  279. try
  280. {
  281. SwingUtilities.invokeAndWait(run);
  282. }
  283. catch(Exception e)
  284. {
  285. throw new RuntimeException(e);
  286. // Log.log(Log.ERROR,CatalogManager.class,e);
  287. }
  288. }
  289. if(session[0] != null)
  290. {
  291. InputSource source = new InputSource(systemId);
  292. source.setPublicId(publicId);
  293. if(isUsingCache())
  294. {
  295. File file;
  296. try
  297. {
  298. file = copyToLocalFile(session[0],vfs,newSystemId);
  299. }
  300. finally
  301. {
  302. vfs._endVFSSession(session,null);
  303. }
  304. addUserResource(publicId,systemId,file.toURL().toString());
  305. source.setByteStream(new FileInputStream(file));
  306. }
  307. else
  308. source.setByteStream(vfs._createInputStream(session,newSystemId,false,null));
  309. return source;
  310. }
  311. else
  312. {
  313. throw new IOException(jEdit.getProperty("xml.network-error"));
  314. }
  315. }
  316. } //}}}
  317. public void clearCache()
  318. {
  319. Iterator files = resourceCache.values().iterator();
  320. while(files.hasNext())
  321. {
  322. Object obj = files.next();
  323. if(obj instanceof String)
  324. {
  325. String file = (String)XmlPlugin.uriToFile((String)obj);
  326. Log.log(Log.NOTICE, getClass(), "Deleting " + file);
  327. new File(file).delete();
  328. }
  329. }
  330. resourceCache.clear();
  331. } //}}}
  332. private String resolveSystem(String id) throws IOException
  333. {
  334. String uri = null;
  335. Entry e = new Entry(Entry.SYSTEM,id,null);
  336. Object o = resourceCache.get(e);
  337. if (o != null) uri = o.toString();
  338. if(uri == null)
  339. return catalog.resolveSystem(id);
  340. else if(uri == IGNORE)
  341. return null;
  342. else
  343. return uri;
  344. } //}}}
  345. //{{{ copyToLocalFile() method
  346. private static File copyToLocalFile(Object session, VFS vfs, String path)
  347. throws IOException
  348. {
  349. if(jEdit.getSettingsDirectory() == null)
  350. return null;
  351. File _resourceDir = new File(resourceDir);
  352. if (!_resourceDir.exists())
  353. _resourceDir.mkdir();
  354. // Need to put this "copy from one stream to another"
  355. // into a common method some day, since other parts
  356. // of jEdit need it too...
  357. BufferedInputStream in = new BufferedInputStream(
  358. vfs._createInputStream(session,path,false,null));
  359. File localFile = File.createTempFile("cache", ".xml", _resourceDir);
  360. BufferedOutputStream out = new BufferedOutputStream(
  361. new FileOutputStream(localFile));
  362. byte[] buf = new byte[4096];
  363. int count = 0;
  364. while ((count = in.read(buf)) != -1)
  365. out.write(buf,0,count);
  366. out.close();
  367. return localFile;
  368. } //}}}
  369. //{{{ resolvePublic() method
  370. private String resolvePublic(String systemId, String publicId) throws IOException
  371. {
  372. Entry e = new Entry(Entry.PUBLIC,publicId,null);
  373. String uri = (String)resourceCache.get(e);
  374. if(uri == null)
  375. try {
  376. return catalog.resolvePublic(publicId,null);
  377. }
  378. catch (Exception e4) {
  379. e4.printStackTrace();
  380. return null;
  381. }
  382. else if(uri == IGNORE)
  383. return null;
  384. else
  385. return uri;
  386. } //}}}
  387. private boolean showDownloadResourceDialog(Component comp, String systemId)
  388. {
  389. Entry e = new Entry(Entry.SYSTEM,systemId,null);
  390. if(resourceCache.get(e) == IGNORE)
  391. return false;
  392. int result = GUIUtilities.confirm(comp,"xml.download-resource",
  393. new String[] { systemId },JOptionPane.YES_NO_OPTION,
  394. JOptionPane.QUESTION_MESSAGE);
  395. if(result == JOptionPane.YES_OPTION)
  396. return true;
  397. else
  398. {
  399. resourceCache.put(e,IGNORE);
  400. return false;
  401. }
  402. } //}}}
  403. //{{{ addUserResource() method
  404. /**
  405. * Don't want this public because then invoking {@link clearCache()}
  406. * will remove this file, not what you would expect!
  407. */
  408. private void addUserResource(String publicId, String systemId, String url)
  409. {
  410. if(publicId != null)
  411. {
  412. Entry pe = new Entry( Entry.PUBLIC, publicId, url );
  413. resourceCache.put( pe, url );
  414. }
  415. Entry se = new Entry( Entry.SYSTEM, systemId, url );
  416. resourceCache.put( se, url );
  417. reverseResourceCache.put(url,se);
  418. } //}}}
  419. public static class Entry
  420. {
  421. public static final int SYSTEM = 0;
  422. public static final int PUBLIC = 1;
  423. public int type;
  424. public String id;
  425. public String uri;
  426. public Entry(int type, String id, String uri)
  427. {
  428. this.type = type;
  429. this.id = id;
  430. this.uri = uri;
  431. }
  432. public boolean equals(Object o)
  433. {
  434. if(o instanceof Entry)
  435. {
  436. Entry e = (Entry)o;
  437. return e.type == type && e.id.equals(id);
  438. }
  439. else
  440. return false;
  441. }
  442. public int hashCode()
  443. {
  444. return id.hashCode();
  445. }
  446. } //}}}
  447. static public boolean isUsingCache() {
  448. if(jEdit.getSettingsDirectory() == null) return false;
  449. return jEdit.getBooleanProperty(CACHE);
  450. }
  451. static public void setUsingCache(boolean newCache) {
  452. jEdit.setBooleanProperty(CACHE, newCache);
  453. }
  454. /**
  455. *
  456. * @return the network mode: LOCAL, ASK, or ALWAYS
  457. */
  458. static public String getNetworkMode() {
  459. return jEdit.getProperty(NETWORK_PROPS + ".mode");
  460. }
  461. /**
  462. *
  463. * @param newVal 0=ask, 1=local mode, 2=always download
  464. */
  465. static public void setNetworkModeVal(int newVal) {
  466. setNetworkMode(MODES[newVal]);
  467. }
  468. /**
  469. *
  470. * @return 0=ask, 1=local mode, 2=always download
  471. */
  472. static public int getNetworkModeVal() {
  473. String mode = getNetworkMode();
  474. if (mode == null) return 0;
  475. for (int i=0; i<MODES.length; ++i) {
  476. if (mode.equals(MODES[i])) return i;
  477. }
  478. return 0;
  479. }
  480. static public void setNetworkMode(String newMode) {
  481. jEdit.setProperty(NETWORK_PROPS + ".mode", newMode);
  482. }
  483. public InputSource getExternalSubset(String name, String baseURI) throws SAXException, IOException
  484. {
  485. // TODO Auto-generated method stub
  486. return null;
  487. }
  488. }