All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.loaders.XMLDataObject Maven / Gradle / Ivy

Go to download

The NetBeans Platform is a generic base for desktop applications. It provides the services common to almost all large desktop applications: window management, menus, settings and storage, an update manager, and file access. Get a head start by reusing these standard components, allowing you to concentrate fully on your application's business logic.

The newest version!
/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.loaders;

import java.net.URL;
import java.io.*;
import java.util.*;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.*;
import java.beans.*;

import javax.xml.parsers.*;


import org.xml.sax.*;
import org.xml.sax.ext.*;
import org.xml.sax.helpers.*;
import org.w3c.dom.*;

import org.openide.*;
import org.openide.actions.*;
import org.openide.cookies.*;
import org.openide.filesystems.*;
import org.openide.loaders.*;
import org.openide.text.*;
import org.openide.util.*;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.actions.SystemAction;
import org.openide.xml.*;
import org.openide.nodes.Node;
import org.openide.nodes.Children;
import org.openide.nodes.CookieSet;
import org.openide.windows.CloneableOpenSupport;
import org.openide.nodes.FilterNode;

/** 
 * Object that provides main functionality for xml documents.
 * These objects are recognized by the xml extension and
 * text/xml MIME type. 
 * 

* It is declaratively extensible by an {@link Environment}. * The Environment is assigned to document instances using a provider * registered by DOCTYPE's public ID in the system filesystem under * xml/lookups/{Transformed-DOCTYPE} where the DOCTYPE transformation * is the same as that defined for {@link EntityCatalog} registrations. * * @see XMLUtil * @see EntityCatalog * * @author Libor Kramolis, Jaroslav Tulach, Petr Kuzel */ public class XMLDataObject extends MultiDataObject { /** generated Serialized Version UID */ static final long serialVersionUID = 8757854986453256578L; /** Public ID of xmlinfo dtd. * @deprecated replaced with Lookup */ public static final String XMLINFO_DTD_PUBLIC_ID_FORTE = "-//Forte for Java//DTD xmlinfo//EN"; // NOI18N /** @deprecated replaced with Lookup */ public static final String XMLINFO_DTD_PUBLIC_ID = "-//NetBeans IDE//DTD xmlinfo//EN"; // NOI18N /** Mime type of XML documents. */ public static final String MIME = "text/xml"; //NOI18N //public static final String MIME2 = "application/xml"; //NOI18N /** PROP_DOCUMENT not parsed yet. Constant for getStatus method. */ public static final int STATUS_NOT = 0; /** PROP_DOCUMENT parsed ok. Constant for getStatus method. */ public static final int STATUS_OK = 1; /** PROP_DOCUMENT parsed with warnings. Constant for getStatus method. */ public static final int STATUS_WARNING = 2; /** PROP_DOCUMENT parsed with errors. Constant for getStatus method. */ public static final int STATUS_ERROR = 3; /** property name of DOM document property */ public static final String PROP_DOCUMENT = "document"; //??? it is not bound well // NOI18N /** property name of info property * @deprecated info is not supported anymore. Replaced with lookup. */ public static final String PROP_INFO = "info"; // NOI18N /** Default XML parser error handler */ private static ErrorPrinter errorHandler = new ErrorPrinter(); /** * Chain of resolvers contaning all EntityResolvers registred by a user. */ private static XMLEntityResolverChain chainingEntityResolver; /** map of DTD publicID => Info. */ private static HashMap infos = new HashMap(); // the lock can be seamlesly shared by all instances private static Object emgrLock = new Object (); // // Instance variables // /** the XML document we delegate to */ private DelDoc doc; /** the result of parsing */ private int status; //??? why it is not a bound property? // it if often out-of date (e.g. garbage collection) /** @deprecated EditorCookie provided by subclass support * need to prevail build in cookies. */ private EditorCookie editor = null; /** * Task body triggered by file change (primaryFile() or xmlinfo) parsing document * for extension (info) assigment information (xmlinfo or public id) */ private InfoParser infoParser; /* Lazy initialized. For logging and debugging. */ private ErrorManager err; /** * Create new XMLDataObject. It is usually called by a loader. * A user can get existing XMLDataObject by calling {@link DataObject#find(FileObject) * DataObject.find(FileObject f)} instead. * * @param fo the primary file object, never null * @param loader loader of this data object, never null * */ public XMLDataObject (FileObject fo, MultiFileLoader loader) throws DataObjectExistsException { super (fo, loader); fo.addFileChangeListener (FileUtil.weakFileChangeListener (getIP (), fo)); status = STATUS_NOT; // register provided cookies // EditorCookie must be for back compatability consulted with subclasses // // In new model subclasses should directly provide its CookieSet.Factory that // uses last prevails order instead of old CookieSet first prevails order. // It completely prevails over this factory :-) CookieSet.Factory factory = new CookieSet.Factory() { public Node.Cookie createCookie(Class klass) { if (klass.isAssignableFrom(EditorCookie.class) || klass.isAssignableFrom(OpenCookie.class) || klass.isAssignableFrom(CloseCookie.class) || klass.isAssignableFrom(PrintCookie.class) ) { if (editor == null) editor = createEditorCookie(); // the first pass if (editor == null) return null; //??? gc unfriendly return klass.isAssignableFrom(editor.getClass()) ? editor : null; } else { return null; } } }; CookieSet cookies = getCookieSet(); // EditorCookie.class must be synchronized with // XMLEditor.Env->findCloneableOpenSupport cookies.add(EditorCookie.class, factory); cookies.add(OpenCookie.class, factory); cookies.add(CloseCookie.class, factory); cookies.add(PrintCookie.class, factory); // set info for this file //getIP ().resolveInfo (); #16045 } /** Getter for info parser. Initializes the infoparser in "lazy" way so it is accessble even before * the constructor finishes. */ private final InfoParser getIP () { synchronized (emgrLock) { if (infoParser == null) { infoParser = new InfoParser (); } } return infoParser; } /** If the Info associated with this data object (if any) provides * a subclass of Node, then this object is created to represent the * XML data object, otherwise DataNode is created. * * @return the node representation for this data object * @see DataNode */ protected Node createNodeDelegate () { //??? what about interaction with Looks XMLNode xn = new XMLNode (this); // netbeans.core.nodes.description xn.setShortDescription (NbBundle.getMessage ( XMLDataObject.class, "HINT_XMLDataObject")); // NOI18N return xn; } /** Called when the info file is parsed and the icon should change. * @param res resource for the icon * @deprecated it is better to listen on properties */ protected void updateIconBase (String res) { //??? we could add default behaviour, taking status into account } /* * Wait until background parsing terminates to avoid concurent file access. * It should terminate very early if just running, we can wait for it. */ protected void handleDelete() throws IOException { getIP ().waitFinished(); // too late wait for finnish super.handleDelete(); } public HelpCtx getHelpCtx () { // help for fix #23528, objects represents 'settings' nodes in Options dialog // returns DEFAULT_HELP for next processing try { if (getPrimaryFile ().getFileSystem ().isDefault ()) { if (getCookie (InstanceCookie.class)!=null) { return HelpCtx.DEFAULT_HELP; } } } catch (FileStateInvalidException fsie) { // cannot determine type of this file object ==> return help id as normal } return new HelpCtx (XMLDataObject.class); } /** * Cookies from assigned Environment are not placed into * protected CookieSet and can be obtained only by invoking this method. *

* Cookie order for Info environments are handled consistently with * CookieSet i.e. FIFO. * @return a cookie (instanceof cls) that has been found in info or * super.getCookie(cls). */ public Node.Cookie getCookie (Class cls) { getIP ().waitFinished(); Node.Cookie cake = (Node.Cookie)getIP ().lookupCookie (cls); if (cake instanceof InstanceCookie) { cake = ofCookie ((InstanceCookie)cake, cls); } if (cake == null) { cake = super.getCookie (cls); } return cake; } /** Special support of InstanceCookie.Of. If the Info class * provides InstanceCookie but not IC.Of, we add the extra interface to * this data object. * * @param ic instance cookie * @param cls constraining class * @return instance of InstanceCookie.Of */ private InstanceCookie ofCookie (InstanceCookie ic, Class cls) { if (ic instanceof InstanceCookie.Of) { return ic; } else if (! cls.isAssignableFrom (ICDel.class)) { // Someone was looking for, and a processor etc. was // providing, some specialization which ICDel cannot // provide. Return the real implementation and forget // about making this a IC.Of. return ic; } else { ICDel d = new ICDel (this, ic); return d; } } private void notifyEx(Exception e) { ErrorManager emgr = ErrorManager.getDefault(); emgr.annotate(e, "Cannot resolve following class in xmlinfo."); // NOI18N emgr.notify(e); } /** Allows subclasses to provide their own editor cookie. * @return an editor cookie to be used as a result of getCookie(EditorCookie.class) * * @deprecated CookieSet factory should be used by subclasses instead. */ protected EditorCookie createEditorCookie () { return new XMLEditorSupport (this); } // Vertical CookieManager private final void addSaveCookie (SaveCookie save) { getCookieSet ().add (save); } private final void removeSaveCookie (SaveCookie save) { getCookieSet ().remove (save); } //??? we ahould add it into class comment to make it public // or should we introduce second layer XMLDataObject extending this one // and having documented this functionality (we cannot because of // so this huge DataObject will survive createEditorCookie()) /* * Really simple implementation of OpenCookie, EditorCookie, PrintCookie, * CloseCookie and managing SaveCookie. */ private static class XMLEditorSupport extends DataEditorSupport implements OpenCookie, EditorCookie.Observable, PrintCookie, CloseCookie { public XMLEditorSupport (XMLDataObject obj) { super (obj, new XMLEditorEnv (obj)); setMIMEType ("text/xml"); // NOI18N } class Save implements SaveCookie { public void save () throws IOException { saveDocument (); getDataObject ().setModified (false); } } protected boolean notifyModified () { if (! super.notifyModified ()) { return false; } if (getDataObject ().getCookie (SaveCookie.class) == null) { ((XMLDataObject) getDataObject ()).addSaveCookie (new Save ()); getDataObject ().setModified (true); } return true; } protected void notifyUnmodified () { super.notifyUnmodified (); SaveCookie save = (SaveCookie) getDataObject ().getCookie (SaveCookie.class); if (save != null) { ((XMLDataObject) getDataObject ()).removeSaveCookie (save); getDataObject ().setModified (false); } } //!!! it also stays for SaveCookie however does not understand // encoding declared in XML header => need to be rewritten. private static class XMLEditorEnv extends DataEditorSupport.Env { private static final long serialVersionUID = 6593415381104273008L; public XMLEditorEnv (DataObject obj) { super (obj); } protected FileObject getFile () { return getDataObject ().getPrimaryFile (); } protected FileLock takeLock () throws IOException { return ((XMLDataObject) getDataObject ()).getPrimaryEntry ().takeLock (); } public CloneableOpenSupport findCloneableOpenSupport () { // must be sync with cookies.add(EditorCookie.class, factory); // #12938 XML files do not persist in Source editor return (CloneableOpenSupport) getDataObject ().getCookie (EditorCookie.class); } } } /** Creates w3c's document for the xml file. Either returns cached reference * or parses the file and creates new document. * * @return the parsed document * @exception SAXException if there is a parsing error * @exception IOException if there is an I/O error */ public final Document getDocument () throws IOException, SAXException { emgr().log ("getDocument"); synchronized (this) { DelDoc d = doc; if (d == null) { d = new DelDoc (); doc = d; } return d.getProxyDocument(); } } /** Clears the document. Called when the document file is changed. */ final void clearDocument () { emgr().log ("clearDocument"); //err.notify (ErrorManager.INFORMATIONAL, new Throwable ("stack dump")); doc = null; firePropertyChange (PROP_DOCUMENT, null, null); } /** * @return one of STATUS_XXX constants representing PROP_DOCUMENT state. */ public final int getStatus () { return status; } /** @deprecated not used anymore * @return null */ public final Info getInfo () { return null; } /** @deprecated does not do anything useful */ public final synchronized void setInfo (Info ii) throws IOException { } /* JST: Commented out, we are disabling support for Infos * private final void setInfoImpl (Info ii) { if (info == ii) return; if ((info != null) && info.equals (ii)) return; // update properties and caches Info prevInfo = info; info = ii; if (info != null) { cachedCookies = null; updateIconBase (info.getIconBase ()); //??? the fire bellow shoud do it, why explicitly } firePropertyChange (PROP_INFO, prevInfo, info); } private void writeInfo () throws IOException { if (info == null) return; final FileObject primary = getPrimaryFile(); final FileObject parent = primary.getParent(); // a folder final org.openide.filesystems.FileSystem FS = parent.getFileSystem(); FS.runAtomicAction (new org.openide.filesystems.FileSystem.AtomicAction () { public void run () throws IOException { FileLock lock = null; OutputStream os = null; FileObject infoFO = FS.find (parent.getName(), primary.getName(), Loader.XMLINFO_EXT); if (infoFO == null) infoFO = parent.createData (primary.getName(), Loader.XMLINFO_EXT); try { lock = infoFO.lock (); os = infoFO.getOutputStream (lock); PrintWriter writer = new PrintWriter (new BufferedOutputStream(os)); info.write (writer); writer.close(); } finally { if (os != null) os.close (); if (lock != null) lock.releaseLock (); } } }); } */ /** Parses the primary file of this data object. * and provide different implementation. * * @return the document in the primary file * @exception IOException if error during parsing occures */ final Document parsePrimaryFile () throws IOException, SAXException { emgr().log ("parsePrimaryFile"); String loc = getPrimaryFile().getURL().toExternalForm(); try { return XMLUtil.parse(new InputSource(loc), false, /* #36295 */true, errorHandler, getSystemResolver()); } catch (IOException e) { // Perhaps this document was not on a mounted filesystem. // Try again with an input stream - no relative URLs will work, but this // is extremely unlikely to matter. Cf. #36340. InputStream is = getPrimaryFile().getInputStream(); try { return XMLUtil.parse(new InputSource(is), false, true, errorHandler, getSystemResolver()); } finally { is.close(); } } } /** Return ErrorManager for this instance. */ private ErrorManager emgr() { synchronized (emgrLock) { if (err == null) { err = ErrorManager.getDefault ().getInstance( "org.openide.loaders.XMLDataObject[" + getPrimaryFile().getPath() + "]" // NOI18N ); } } return err; } // ~~~~~~~~~~~~~~~~~~~~ Start of Utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~ To be replaced by XMLUtil ~~~~~~~~~~~~~~~~~~~~~~ /** Provides access to internal XML parser. * This method takes URL. After successful finish the * document tree is returned. Used non validating parser. * * @param url the url to read the file from * @deprecated Use {@link XMLUtil#parse(InputSource, boolean, boolean, ErrorHandler, EntityResolver) XMLUtil} instead * setting null error handler and validation to false. */ public static Document parse (URL url) throws IOException, SAXException { return parse (url, errorHandler, false); } /** Provides access to internal XML parser. * This method takes URL. After successful finish the * document tree is returned. Used non validating parser. * * @param url the url to read the file from * @param validate if true validating parser is used * @deprecated Use {@link XMLUtil#parse(InputSource, boolean, boolean, ErrorHandler, EntityResolver) XMLUtil} instead * setting null handler. */ public static Document parse (URL url, boolean validate) throws IOException, SAXException { return parse (url, errorHandler, validate); } /** Provides access to internal XML parser. * This method takes URL. After successful finish the * document tree is returned. * * @param url the url to read the file from * @param eh error handler to notify about exception * @deprecated Use {@link XMLUtil#parse(InputSource, boolean, boolean, ErrorHandler, EntityResolver) XMLUtil} instead * setting validation to false. */ public static Document parse (URL url, ErrorHandler eh) throws IOException, SAXException { return parse (url, eh, false); } /** Factory a DocumentBuilder and let it create a org.w3c.dom.Document * This method takes URL. After successful finish the * document tree is returned. * A parser producing the Document has * set entity resolver to system entity resolver chain. * * @param url the url to read the file from * @param eh error handler to notify about exception * @param validate if true validating parser is used * @throws SAXException annotated if thrown due to configuration problem * @throws FactoryConfigurationError * @return org.w3c.dom.Document * @deprecated Use {@link XMLUtil#parse(InputSource, boolean, boolean, ErrorHandler, EntityResolver) XMLUtil} instead. */ public static Document parse (URL url, ErrorHandler eh, boolean validate) throws IOException, SAXException { DocumentBuilder builder = XMLDataObjectImpl.makeBuilder(validate); builder.setErrorHandler(eh); builder.setEntityResolver(getChainingEntityResolver()); return builder.parse (new InputSource(url.toExternalForm())); } /** Creates SAX parse that can be used to parse XML files. * @return sax parser * @deprecated Use {@link XMLUtil#createXMLReader() XMLUtil} instead. * It will create a SAX XMLReader that is SAX Parser replacement. * You will have to replace DocumentHandler by ContentHandler * besause XMLReader accepts just ContentHandler. *

Alternatively if not interested in new callbacks defined by * SAX 2.0 you can wrap returned XMLReader into XMLReaderAdapter * that implements Parser. */ public static Parser createParser () { return createParser (false); } /** Factory SAX parser that can be used to parse XML files. * The factory is created according to javax.xml.parsers.SAXParserFactory property. * The parser has set entity resolver to system entity resolver chain. * @param validate if true validating parser is returned * @throws FactoryConfigurationError * @return sax parser or null if no parser can be created * @deprecated Use {@link XMLUtil#createXMLReader(boolean,boolean ) Util} instead * setting ns to false. * For more details see {@link #createParser() createParser} */ public static Parser createParser (boolean validate) { Parser parser = XMLDataObjectImpl.makeParser(validate); parser.setEntityResolver(getChainingEntityResolver()); return parser; } /** * Creates empty DOM Document using JAXP factoring. * @return Document or null on problems with JAXP factoring * @deprecated Replaced with {@link XMLUtil#createDocument(String,String,String,String) XMLUtil} * It directly violates DOM's root element reference read-only status. * If you can not move to XMLUtil for compatabilty reasons please * replace with following workaround: *

     * String templ = "";
     * InputSource in = new InputSource(new StringReader(templ));
     * in.setSystemId("StringReader");  //workaround
     * DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
     * Document doc = builder.parse(in);
     * 
*/ public static Document createDocument() { deprecated(); try { return XMLDataObjectImpl.makeBuilder(false).newDocument(); } catch (IOException ex) { return null; } catch (SAXException ex) { return null; } } /** * Writes DOM Document to writer. * * @param doc DOM Document to be written * @param writer OutoutStreamWriter preffered otherwise * encoding will be left for implementation specific autodection * * @deprecated Encoding used by Writer * may be in direct conflict with encoding * declared in document. Replaced with {@link XMLUtil#write(Document, OutputStream, String) Util}. */ public static void write (Document doc, Writer writer) throws IOException { deprecated(); // WARNING: back compatability code //use reflection to access "friendly" implementation in other package final String FAILURE = "org.openide.xml.XMLUtilImpl.write() invocation failed."; //NOI18N try { Class clzz = Class.forName("org.openide.xml.XMLUtilImpl"); //NOI18N Method impl = clzz.getDeclaredMethod("write", new Class[] {//NOI18N Document.class, Object.class, String.class }); impl.setAccessible(true); impl.invoke(null, new Object[] {doc, writer, null}); } catch (IllegalAccessException ex) { throw new IOException(FAILURE); } catch (IllegalArgumentException ex) { throw new IOException(FAILURE); } catch (NoSuchMethodException ex) { throw new IOException(FAILURE); } catch (ClassNotFoundException ex) { throw new IOException(FAILURE); } catch (InvocationTargetException ex) { Throwable t = ex.getTargetException(); if (t instanceof IOException) { throw (IOException) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } throw new IOException(FAILURE); } } /** * Write Document into OutputStream using given encoding. * It is a shortcut for writing configurations etc. It guarantee * just that data will be written. Structure and indentation * may change. * * @param doc DOM Document to be written * @param out data sink * @param enc - XML defined encoding name (i.e. IANA defined, one of UTF-8, UNICODE, ASCII). * @deprecated Moved to {@link XMLUtil#write(Document, OutputStream, String) XMLUtil}. */ public static void write(Document doc, OutputStream out, String enc) throws IOException { XMLUtil.write(doc, out, enc); } /** * Creates SAX InputSource for specified URL * @deprecated Deprecated as it was a workaround method. Replace * with new InputSource(url.toExternalForm()). */ public static org.xml.sax.InputSource createInputSource (URL url) throws IOException { return new InputSource(url.toExternalForm()); } /** * Registers the given public ID as corresponding to a particular * URI, typically a local copy. This URI will be used in preference * to ones provided as system IDs in XML entity declarations. This * mechanism would most typically be used for Document Type Definitions * (DTDs), where the public IDs are formally managed and versioned. * *

Any created parser use global entity resolver and you can * register its catalog entry. * * @param publicId The managed public ID being mapped * @param uri The URI of the preferred copy of that entity * * @deprecated Do not rely on global (non-modular) resolvers. * Use {@link EntityCatalog} and {@link XMLUtil} * instead. */ public static void registerCatalogEntry (String publicId, String uri) { if (publicId == null) throw new IllegalArgumentException("null public ID is not allowed."); //NOI18N XMLDataObjectImpl.registerCatalogEntry(publicId, uri); } /** * Registers a given public ID as corresponding to a particular Java * resource in a given class loader, typically distributed with a * software package. This resource will be preferred over system IDs * included in XML documents. This mechanism should most typically be * used for Document Type Definitions (DTDs), where the public IDs are * formally managed and versioned. * *

If a mapping to a URI has been provided, that mapping takes * precedence over this one. * *

Any created parser use global entity resolver and you can * register its catalog entry. * * @param publicId The managed public ID being mapped * @param resourceName The name of the Java resource * @param loader The class loader holding the resource, or null if * it is a system resource. * * @deprecated Do not rely on global (non-modular) resolvers. * Use {@link EntityCatalog} and {@link XMLUtil} * instead. */ public static void registerCatalogEntry (String publicId, String resourceName, ClassLoader loader) { if (publicId == null) throw new IllegalArgumentException("null public ID is not allowed."); //NOI18N XMLDataObjectImpl.registerCatalogEntry(publicId, "nbres:/" + resourceName); //NOI18N } /** * Add a given entity resolver to IDE resolver chain. * The resolver chain is searched by private chaining resolver * until some registered resolver succed. * *

Every created parser use global entity resolver and then chain. * * @deprecated EntityResolver is a parser user responsibility. * Every time set a EntityResolver to an XML parser you use. * The OpenIDE now defines a system {@link EntityCatalog}. * * @param resolver non null resolver to be added * * @return true if successfully added */ public static final boolean addEntityResolver(EntityResolver resolver) { // return false; Is is deprecated :-) return getChainingEntityResolver().addEntityResolver(resolver); } /** * Remove a given entity resolver from IDE resolver chain. * *

Every created parser use global entity resolver and then chain. * * @deprecated EntityResolver is a parser user responsibility. * * @param resolver non null resolver to be removed * @return removed resolver instance or null if not present */ public static final EntityResolver removeEntityResolver(EntityResolver resolver) { return getChainingEntityResolver().removeEntityResolver(resolver); } /** Accessor method for chaining entity resolver implementation. */ private static synchronized XMLEntityResolverChain getChainingEntityResolver() { if (chainingEntityResolver == null) { chainingEntityResolver = new XMLEntityResolverChain(); chainingEntityResolver.addEntityResolver(getSystemResolver()); } return chainingEntityResolver; } /** Lazy initialized system resolver. */ private static EntityResolver getSystemResolver() { return EntityCatalog.getDefault(); } /** * Registers new Info to particular XML document content type as * recognized by DTD public id. The registration is valid until IDE JVM termination. * * @param publicId used as key * @param info associated value or null to unregister * * @deprecated Register an {@link Environment} via lookup, see * {@link XMLDataObject some details}. */ public static void registerInfo (String publicId, Info info) { //!!! to be replaced by lookup synchronized (infos) { if (info == null) { infos.remove(publicId); } else { infos.put(publicId, info); } } } /** * Obtain registered Info for particular DTD public ID. * * @param publicId key which value is required * @return Info clone that is used for given publicId or null * * @deprecated Register via lookup */ public static Info getRegisteredInfo(String publicId) { //!!! to be replaced by lookup synchronized (infos) { Info ret = (Info) infos.get(publicId); return ret == null ? null : (Info)ret.clone (); } } // ~~~~~~~~~~~~~~~~~~~ PRIVATE AREA ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // dangerous, enter on your own risk // WARNING - wormhole called via reflection from core layer, // do not delete, rename, etc. /** Used for creating RuntimeCatalog instance */ private static EntityCatalog createEntityCatalog() { return XMLDataObjectImpl.createEntityCatalog(); } /* * Guess from stack trace who calls deprecated method and print it. */ private static void deprecated() { StringWriter wr = new StringWriter(); PrintWriter pr = new PrintWriter(wr); new Exception("").printStackTrace(pr); // NOI18N pr.flush(); String stack = wr.toString().trim(); int start = stack.indexOf("\n"); // NOI18N int end = stack.indexOf("\n", start + 1); // NOI18N while (stack.indexOf("XMLDataObject", start + 1 )>0) { // NOI18N start = end; end = stack.indexOf("\n", start + 1); // NOI18N } String line = stack.substring(start + 1, end).trim(); System.out.println("Warning: deprecated method called " + line); // NOI18N } /** * Default ErrorHandler reporting to log. */ static class ErrorPrinter implements org.xml.sax.ErrorHandler { private void message (final String level, final org.xml.sax.SAXParseException e) { ErrorManager em = ErrorManager.getDefault().getInstance("org.openide.loaders.XMLDataObject"); // NOI18N if (!em.isLoggable(ErrorManager.INFORMATIONAL)) { return; } final String msg = NbBundle.getMessage( XMLDataObject.class, "PROP_XmlMessage", //NOI18N new Object [] { level, e.getMessage(), e.getSystemId() == null ? "" : e.getSystemId(), // NOI18N "" + e.getLineNumber(), // NOI18N "" + e.getColumnNumber() // NOI18N } ); em.log(msg); } public void error (org.xml.sax.SAXParseException e) { message (NbBundle.getMessage(XMLDataObject.class, "PROP_XmlError"), e); //NOI18N } public void warning (org.xml.sax.SAXParseException e) { message (NbBundle.getMessage(XMLDataObject.class, "PROP_XmlWarning"), e); //NOI18N } public void fatalError (org.xml.sax.SAXParseException e) { message (NbBundle.getMessage(XMLDataObject.class, "PROP_XmlFatalError"), e); //NOI18N } } // end of inner class ErrorPrinter //~~~~~~~~~~~~~~~~~~~~~~~~ PARSER ---------------------------------- // internally stops documet parsing when looking for public id private static class StopSaxException extends SAXException { public StopSaxException() { super("STOP"); } //NOI18N } // static fields that that are logically a part of InfoParser private static final StopSaxException STOP = new StopSaxException(); /** We are guaranteed to be executed in one thread let reuse parser, etc. */ private static XMLReader sharedParserImpl = null; static { try { sharedParserImpl = XMLUtil.createXMLReader(); sharedParserImpl.setEntityResolver(new EmptyEntityResolver()); } catch (SAXException ex) { ErrorManager err = ErrorManager.getDefault(); err.annotate(ex, "System does not contain JAXP 1.1 compliant parser!"); // NOI18N err.notify(err.ERROR, ex); } //initialize stuff possibly needed by libs that do not use //JAXP but SAX 2 directly try { final Properties props = System.getProperties(); final String SAX2_KEY = "org.xml.sax.driver"; //NOI18N if (props.getProperty(SAX2_KEY) == null) { props.put(SAX2_KEY, sharedParserImpl.getClass().getName()); } } catch (RuntimeException ex) { //ignore it (we did the best efford) } } /** A template to ask lookup for */ private static final Lookup.Template TEMPLATE = new Lookup.Template (Node.Cookie.class); /** a string to signal null value for parsedId */ private static final String NULL = ""; // NOI18N /** * It simulates null that forbiden by SAX specs. */ private static class NullHandler extends DefaultHandler implements LexicalHandler { static final NullHandler INSTANCE = new NullHandler(); NullHandler() {} // LexicalHandler public void startDTD(String root, String pID, String sID) throws SAXException { } public void endDTD() throws SAXException { } public void startEntity(String name) throws SAXException { } public void endEntity(String name) throws SAXException { } public void startCDATA() throws SAXException { } public void endCDATA() throws SAXException { } public void comment(char[] ch, int start, int length) throws SAXException { } } /** * Parser that parses XML document header to get some hints from it (such as DOCTYPE public ID. * It is named as InfoParser for historical reasons as it originally parsed * a coupled file (with xmlinfo extension) that contained IDE related metadata. * That mechanism was replaced by /xml/lookup/ registrations and * by file attributes. */ private final class InfoParser extends DefaultHandler implements FileChangeListener, LexicalHandler, LookupListener { /** the result of parsing the IDE */ private String parsedId; /** Lookup associated with this document. */ private Lookup lookup; /** result used for this lookup */ private Lookup.Result result; private ThreadLocal QUERY = new ThreadLocal (); InfoParser() {} //~~~~~~~~~~~~~~~~~~~~~ Task body and control of queue ~~~~~~~~~~~~~~~~~~~ /** Getter for public ID of the document. */ public String getPublicId () { waitFinished (); return parsedId == NULL ? null : parsedId; } /** Does lookup for specific cookie. It also * calls lookup on the result of Node.Cookie.class query and that is why it * initializes the listeners to notify changes made on the data object. * * @param class to look for */ public Object lookupCookie (final Class clazz) { if (QUERY.get () == clazz) { // somebody is querying for the same cookie in the same thread // probably neverending-loop - ignore return new InstanceCookie () { public Class instanceClass () { return clazz; } public Object instanceCreate () throws IOException { throw new IOException ("Cyclic reference, sorry: " + clazz); } public String instanceName () { return clazz.getName (); } }; } Object previous = QUERY.get (); try { QUERY.set (clazz); waitFinished (); Lookup l = lookup != null ? lookup : Lookup.EMPTY; Lookup.Result r = result; if (r != null) { // just to initialize all listeners r.allItems (); } return l.lookup (clazz); } finally { QUERY.set (previous); } } /* * Find out DTD public ID. * Info is then assigned according to it from registry. */ public void waitFinished () { waitFinished (null); } /* * Find out DTD public ID. * Info is then assigned according to it from registry. */ private void waitFinished (String ignorePreviousId) { if (sharedParserImpl == null) return; XMLReader parser = sharedParserImpl; FileObject myFileObject = getPrimaryFile(); String previousID; String newID = null; previousID = parsedId; if (parsedId != null) { // ok, has already been parsed return; } URL url = null; InputStream in = null; try { url = myFileObject.getURL(); } catch (IOException ex) { warning(ex, "I/O exception while retrieving xml FileObject URL."); //NOI18N return; // cannot parse } synchronized (this) { try { if (!myFileObject.isValid()) return; parsedId = NULL; try { in = myFileObject.getInputStream(); } catch (IOException ex) { warning(ex, "I/O exception while openning xml."); //NOI18N return; // cannot parse } try { // // we use one shared parser instance, so we must protect // its integrity // synchronized (sharedParserImpl) { configureParser(parser, false, this); parser.setContentHandler(this); parser.setErrorHandler(this); InputSource input = new InputSource(url.toExternalForm()); input.setByteStream(in); parser.parse (input); } } catch (StopSaxException stopped) { newID = parsedId; } catch (SAXException checkStop) { // stop parsing anyway if (STOP.getMessage ().equals (checkStop.getMessage ())) { newID = parsedId; } else { String msg = "Thread:" + Thread.currentThread().getName(); //NOI18N emgr().annotate(checkStop, "DocListener should not throw SAXException but STOP one.\n" + msg); //NOI18N emgr().notify(emgr().INFORMATIONAL, checkStop); Exception ex = checkStop.getException(); if (ex != null) { emgr().notify(emgr().INFORMATIONAL, ex); } } } catch (FileNotFoundException ex) { // thrown when there is a problem with URL for example emgr().notify(emgr().INFORMATIONAL, ex); } catch (IOException ex) { // error while parsing for public id hide it. // somebody have deleted the file meanwhile, because I do not lock? emgr().notify(emgr().INFORMATIONAL, ex); } finally { // such small memory leak can complicate profiling a lot // on the other hand it might cause performance regression // guard it by dedicated property if (Boolean.getBoolean("netbeans.profile.memory")) { // NOI18N // dettach from shared impl, it is static! parser.setContentHandler(NullHandler.INSTANCE); parser.setErrorHandler(NullHandler.INSTANCE); try { parser.setProperty("http://xml.org/sax/properties/lexical-handler", NullHandler.INSTANCE); //NOI18N } catch (SAXException ignoreIt) { } try { // Crimson requires it to release old properties and handlers parser.parse((InputSource)null); } catch (Exception ignoreIt) { } } parser = null; } } finally { try { if (in != null) in.close(); } catch (IOException ex) { emgr().notify(emgr().INFORMATIONAL, ex); } } } if (ignorePreviousId != null && newID.equals (ignorePreviousId)) { // no updates in lookup return; } // out of any synchronized blocks udpate the lookup // because it can call into unknown places via its // Environment.findForOne if (newID != null) { updateLookup (previousID, newID); } } /** Updates the ID. */ private void updateLookup (String previousID, String id) { if (previousID != null && previousID.equals (id)) { return; } Lookup newLookup; // no lock here, because createInfoLookup & findForOne can call // foreing code Info info = getRegisteredInfo (id); if (info != null) { // use info newLookup = createInfoLookup (XMLDataObject.this, info); } else { // ask the environment for the lookups newLookup = Environment.findForOne (XMLDataObject.this); if (newLookup == null) { newLookup = Lookup.EMPTY; } } synchronized (this) { // just one update of lookup in this InfoParser Lookup.Result prevRes = result; lookup = newLookup; result = lookup.lookup (TEMPLATE); result.addLookupListener (this); if (prevRes != null) { prevRes.removeLookupListener (this); XMLDataObject.this.firePropertyChange (DataObject.PROP_COOKIE, null, null); } } } /* * We reuse the parser so it must be reconfigured prior every parsing task. */ private void configureParser(XMLReader parser, boolean validation, LexicalHandler lex) { try { parser.setFeature("http://xml.org/sax/features/validation", validation); //NOI18N } catch (SAXException sex) { emgr().log("Warning: XML parser does not support validation feature."); //NOI18N } try { parser.setProperty("http://xml.org/sax/properties/lexical-handler", lex); //NOI18N } catch (SAXException sex) { emgr().log("Warning: XML parser does not support lexical-handler feature."); //NOI18N //throw new Error(""); } } // ~~~~~~~~~~ ERROR REPORTING ~~~~~~~~~~~~~~~~~~~~~~ public void warning (Throwable ex) { warning(ex, null); } public void warning (Throwable ex, String annotation) { ErrorManager emgr = emgr(); if (annotation != null) emgr.annotate(ex, annotation); emgr.notify(ErrorManager.INFORMATIONAL, ex); //do not show until in debug mode } // LexicalHandler public void startDTD(String root, String pID, String sID) throws SAXException { parsedId = pID == null ? NULL : pID; stop(); } public void endDTD() throws SAXException { stop(); } public void startEntity(String name) throws SAXException { } public void endEntity(String name) throws SAXException { } public void startCDATA() throws SAXException { } public void endCDATA() throws SAXException { } public void comment(char[] ch, int start, int length) throws SAXException { } // redefine DefaultHandler //!!! should we stop on error? public void error(final org.xml.sax.SAXParseException p1) throws org.xml.sax.SAXException { stop(); } public void fatalError(final org.xml.sax.SAXParseException p1) throws org.xml.sax.SAXException { stop(); } public void endDocument() throws org.xml.sax.SAXException { stop(); } public void startElement(String uri, String lName, String qName, Attributes atts) throws org.xml.sax.SAXException { // no DTD present stop(); } private void stop() throws SAXException { throw STOP; } //~~~~~~~~~~~~~~~~~~ FS LISTENER ~~~~~~~~~~~~~~~~~ //listening at parent folder public void fileFolderCreated (FileEvent fe) { // not interesting } public void fileDataCreated (FileEvent fe) { // FileObject fo = fe.getFile(); // fileCreated(fo); } private void fileCreated(FileObject fo) { // if ( // fo.getName ().equals (getPrimaryFile ().getName ()) // ) { // // new info file created => force it to be reparsed // resolveInfo(); // } } /** Fired when a file is changed. * @param fe the event describing context where action has taken place */ public void fileChanged (FileEvent fe) { if (getPrimaryFile ().equals (fe.getFile ())) { // the main file changed => invalidate DOM document //resolveInfo (getPrimaryFile ()); //reparse info again clearDocument (); String prevId = parsedId; parsedId = null; // parse update only if the ID is different waitFinished (prevId); } } public void fileDeleted (FileEvent fe) { } public void fileRenamed (FileRenameEvent fe) { // // the same behaviour as when the file is deleted // fileDeleted (fe); // // and new created // fileCreated(fe.getFile()); } public void fileAttributeChanged (FileAttributeEvent fe) { // not interested in } /** A change in lookup. */ public void resultChanged(org.openide.util.LookupEvent lookupEvent) { XMLDataObject.this.firePropertyChange (DataObject.PROP_COOKIE, null, null); Node n = XMLDataObject.this.getNodeDelegateOrNull (); if (n instanceof XMLNode) { ((XMLNode)n).update (); } } } // end of InfoParser /** Avoid Internet connections */ private static class EmptyEntityResolver implements EntityResolver { EmptyEntityResolver() {} public InputSource resolveEntity(String publicId, String systemID) { InputSource ret = new InputSource(new StringReader("")); //??? we should tolerate file: and nbfs: // NOI18N ret.setSystemId("StringReader"); //NOI18N return ret; } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~ private Loader ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** The DataLoader for XmlDataObjects. */ static final class Loader extends MultiFileLoader { static final long serialVersionUID =3917883920409453930L; /** Creates a new XMLDataLoader */ public Loader () { super ("org.openide.loaders.XMLDataObject"); //!!! so the relation loader data object is fixed // NOI18N //super (XMLDataObject.class); // nothing like looks loader can be constructed } // can it produce subclasses? /** Get default actions. * @return array of default system actions or null if this loader does not have any * actions */ protected SystemAction[] defaultActions () { return new SystemAction[] { SystemAction.get(OpenAction.class), SystemAction.get(FileSystemAction.class), null, SystemAction.get(CutAction.class), SystemAction.get(CopyAction.class), SystemAction.get(PasteAction.class), null, SystemAction.get(DeleteAction.class), SystemAction.get(RenameAction.class), null, SystemAction.get(SaveAsTemplateAction.class), null, SystemAction.get(ToolsAction.class), SystemAction.get(PropertiesAction.class) }; } /** Get the default display name of this loader. * @return default display name */ protected String defaultDisplayName () { return NbBundle.getMessage (XMLDataObject.class, "PROP_XmlLoader_Name"); } /** For a given file finds a primary file. * @param fo the file to find primary file for * * @return the primary file for the file or null if the file is not * recognized by this loader */ protected FileObject findPrimaryFile (FileObject fo) { String mime = fo.getMIMEType (); if (MIME.equals(mime) || "application/xml".equals(mime)) { //NOI18N return fo; } /* JST: I believe that this can be removed because XML_EXT should * always be recognized as text/xml mime type * * Cc.: Unforunatelly there are .xml configuration files * that use "*+xml" suffixed MIME content type * for lightweight data-object-subclass-less typing purposes. * E.g. ProjectEnvironmentProviders are keyed by MIME type. * * It could be better to accept directly "+xml"s... */ if ("xml".equals(fo.getExt())) { return fo; } /** JST: Removed JSP should handle this in better way if ("tld".equals(fo.getExt())) { // NOI18N return fo; // JSP Tag Library Descriptor } */ // not recognized return null; } /** Creates the right data object for given primary file. * It is guaranteed that the provided file is realy primary file * returned from the method findPrimaryFile. * * @param primaryFile the primary file * @return the data object for this file * @exception DataObjectExistsException if the primary file already has data object */ protected MultiDataObject createMultiObject (FileObject primaryFile) throws DataObjectExistsException { return new XMLDataObject (primaryFile, this); } /** Creates the right primary entry for given primary file. * * @param primaryFile primary file recognized by this loader * @return primary entry for that file */ protected MultiDataObject.Entry createPrimaryEntry (MultiDataObject obj, FileObject primaryFile) { return new FileEntry (obj, primaryFile); } /** Creates right secondary entry for given file. The file is said to * belong to an object created by this loader. * * @param secondaryFile secondary file for which we want to create entry * @return the entry */ protected MultiDataObject.Entry createSecondaryEntry (MultiDataObject obj, FileObject secondaryFile) { // JST: We do not have secondary entries anymore, but it probably does not matter... return new FileEntry (obj, secondaryFile); } } // ~~~~~~~~~~~~~~~~~~~~~~ extension support via info ~~~~~~~~~~~~~~~~~~~~~~~~ // i would like to throw it away sometimes in future and replace it by better one /** This class has to be implemented by all processors in the * xmlinfo file. It is cookie, so after parsing such class is instantiated * and put into data objects cookie set. * * @deprecated use lookup */ public static interface Processor extends Node.Cookie { /** When the XMLDataObject creates new instance of the processor, * it uses this method to attach the processor to the data object. * * @param xmlDO XMLDataObject */ public void attachTo (XMLDataObject xmlDO); } /** @deprecated use Lookup * Representation of xmlinfo file holding container of Processors. */ public static final class Info implements Cloneable { List processors; String iconBase; /** Create info */ public Info () { processors = new ArrayList (); iconBase = null; } public Object clone () { Info ii = new Info(); for (Iterator it = processors.iterator(); it.hasNext();) { Class proc = (Class)it.next(); ii.processors.add (proc); } ii.iconBase = iconBase; return ii; } /** Add processor class to info. * The class should be public and either implement the Processor * interface or should * have public constructor with one argument (DataObject or XMLDataObject). * * @param proc the class to add to this info * @exception IllegalArgumentException if the class does not seem to be valid */ public synchronized void addProcessorClass (Class proc) { if (!Processor.class.isAssignableFrom (proc)) { Constructor[] arr = proc.getConstructors(); for (int i = 0; i < arr.length; i++) { Class[] params = arr[i].getParameterTypes(); if (params.length == 1) { if ( params[0] == DataObject.class || params[0] == XMLDataObject.class ) { arr = null; break; } } } if (arr != null) { // no suitable constructor throw new IllegalArgumentException(); } } processors.add (proc); } /** Remove processor class from info. * @return true if removed */ public boolean removeProcessorClass (Class proc) { return processors.remove (proc); } public Iterator processorClasses () { return processors.iterator(); } /** Set icon base */ public void setIconBase (String base) { iconBase = base; } /** @return icon base */ public String getIconBase () { return iconBase; } /** Write specified info to writer */ public void write (Writer writer) throws IOException { throw new IOException ("Not supported anymore"); // NOI18N /* writer.write ("\n\n"); // NOI18N writer.write (MessageFormat.format ("\n\n", // NOI18N new Object [] { InfoParser.TAG_INFO, XMLINFO_DTD_PUBLIC_ID })); writer.write (MessageFormat.format ("<{0}>\n", // NOI18N new Object [] { InfoParser.TAG_INFO })); for (Iterator it = processors.iterator(); it.hasNext();) writer.write (MessageFormat.format (" <{0} {1}=\"{2}\" />\n", // NOI18N new Object [] { InfoParser.TAG_PROCESSOR, InfoParser.ATT_PROCESSOR_CLASS, ((Class)it.next()).getName() })); if (iconBase != null) writer.write (MessageFormat.format (" <{0} {1}=\"{2}\" />\n", // NOI18N new Object [] { InfoParser.TAG_ICON, InfoParser.ATT_ICON_BASE, iconBase })); writer.write (MessageFormat.format ("\n", // NOI18N new Object [] { InfoParser.TAG_INFO })); */ } public boolean equals (Object obj) { if (obj == null) return false; if (obj instanceof Info == false) return false; Info i = (Info) obj; return ((iconBase != null && iconBase.equals(i.iconBase)) || (i.iconBase == iconBase)) && processors.equals(i.processors); } } // end of inner class Info /** A method for backward compatibility to create a lookup from data object and info * @param obj xml data object * @param info the info that should be associated */ static Lookup createInfoLookup (XMLDataObject obj, Info info) { return new InfoLkp (obj, info); } /** A backward compatibility class that converts the content of * an Info object into a Lookup class. */ private static final class InfoLkp extends AbstractLookup { public final Info info; public InfoLkp (XMLDataObject obj, Info info) { this.info = info; Iterator it = info.processorClasses (); ArrayList arr = new ArrayList (info.processors.size ()); while (it.hasNext ()) { Class c = (Class)it.next (); arr.add (new InfoPair (obj, c)); } setPairs (arr); } /** A pair that receives a class and can create its instance either * using default constructor or by passing data object into one * argument constructor. */ private static final class InfoPair extends AbstractLookup.Pair { /** the class to use or null if object has already been created */ private Class clazz; /** XMLDataObject associated or object created */ private Object obj; /** For use by subclasses. */ protected InfoPair (XMLDataObject obj, Class c) { this.obj = obj; this.clazz = c; } /** Tests whether this item can produce object * of class c. */ protected boolean instanceOf (Class c) { Class temp = clazz; if (temp == null) { return c.isInstance (obj); } else { return c.isAssignableFrom (temp); } } /** Method that can test whether an instance of a class has been created * by this item. * * @param obj the instance * @return if the item has already create an instance and it is the same * as obj. */ protected boolean creatorOf (Object obj) { return this.obj == obj; } /** The class of the result item. * @return the instance of the object. */ public synchronized Object getInstance () { if (clazz == null) { // already created an object return obj; } // after this method the obj or null will contain the created object // instead of reference to XMLDataObject XMLDataObject xmlDataObject = (XMLDataObject)obj; obj = null; // the clazz will be null to signal, that an instance // of object has been created Class next = clazz; clazz = null; try { if (Processor.class.isAssignableFrom (next)) { // the class implements Processor interface, so use // default constructor to construct instance obj = next.newInstance (); Processor proc = (Processor) obj; proc.attachTo (xmlDataObject); return obj; } else { // does not implement processor, try to search // for constructor with one argument of DataObject or // XMLDataObject Constructor[] arr = next.getConstructors(); for (int i = 0; i < arr.length; i++) { Class[] params = arr[i].getParameterTypes(); if (params.length == 1) { if ( params[0] == DataObject.class || params[0] == XMLDataObject.class ) { obj = arr[i].newInstance( new Object[] { xmlDataObject } ); return obj; } } } } throw new InternalError ("XMLDataObject processor class " + next + " invalid"); // NOI18N } catch (InvocationTargetException e) { xmlDataObject.notifyEx (e); } catch (InstantiationException e) { xmlDataObject.notifyEx(e); } catch (IllegalAccessException e) { xmlDataObject.notifyEx(e); } return obj; } /** The class of the result item. * @return the class of the item */ public Class getType () { Class temp = clazz; return temp != null ? temp : obj.getClass (); } /** A persistent indentifier of the item. Can be stored and use * in next run of the system. * * @return a string id of the item */ public String getId () { return "Info[" + getType ().getName (); // NOI18N } /** The best display name is probably the name of type... */ public String getDisplayName () { return getType ().getName (); } } } /** Computes correct node for given XMLDataObject. */ private Node findNode () { Node n = (Node)getIP ().lookupCookie (Node.class); if (n == null) { DataNode d = new DataNode (XMLDataObject.this, Children.LEAF); d.setIconBase ("org/openide/loaders/xmlObject"); // NOI18N d.setDefaultAction (SystemAction.get (OpenAction.class)); return d; } else { return n; } } /** Node that delegates either to data node or to a node provided by * the data object itself. */ private final class XMLNode extends FilterNode { public XMLNode (XMLDataObject obj) { this (obj.findNode ()); } private XMLNode (Node del) { super (del, new FilterNode.Children (del)); //setShortDescription("XML FILE"); } private void update () { changeOriginal (XMLDataObject.this.findNode (), true); } } /** A special delegator that adds InstanceCookie.Of to objects that miss it */ private static class ICDel extends Object implements InstanceCookie.Of { /** object we belong to */ private XMLDataObject obj; /** cookie we delegate to */ private InstanceCookie ic; public ICDel (XMLDataObject obj, InstanceCookie ic) { this.obj = obj; this.ic = ic; } public String instanceName () { return ic.instanceName (); } public Class instanceClass () throws java.io.IOException, ClassNotFoundException { return ic.instanceClass (); } public Object instanceCreate () throws java.io.IOException, ClassNotFoundException { return ic.instanceCreate (); } public boolean instanceOf (Class cls2) { if (ic instanceof InstanceCookie.Of) { return ((InstanceCookie.Of) ic).instanceOf (cls2); } else { try { return cls2.isAssignableFrom (instanceClass ()); } catch (IOException ioe) { // ignore exception return false; } catch (ClassNotFoundException cnfe) { // ignore exception return false; } } } public int hashCode () { return 2 * obj.hashCode () + ic.hashCode (); } public boolean equals (Object obj) { if (obj instanceof ICDel) { ICDel d = (ICDel)obj; return d.obj == obj && d.ic == ic; } return false; } } // end of ICDel /** Delegating DOM document that provides fast implementation of * getDocumentType and getPublicID methods. */ private final class DelDoc implements InvocationHandler { private Reference/**/ xmlDocument; private final Document proxyDocument; DelDoc() { proxyDocument = (Document)Proxy.newProxyInstance( DelDoc.class.getClassLoader(), new Class[] {Document.class}, this); } /** Creates w3c's document for the xml file. Either returns cached reference * or parses the file and creates new document. * * @param force really create the document if it does not exists yet? * @return the parsed document or null if not forced */ private final Document getDocumentImpl (boolean force) { synchronized (this) { Object doc = xmlDocument == null ? null : xmlDocument.get (); if (doc instanceof Document) { return (Document)doc; } if (!force) { return null; } status = STATUS_OK; try { Document d = parsePrimaryFile (); xmlDocument = new SoftReference (d); return d; } catch (SAXException e) { emgr ().notify (ErrorManager.INFORMATIONAL, e); } catch (IOException e) { emgr ().notify (ErrorManager.INFORMATIONAL, e); } status = STATUS_ERROR; Document d = XMLUtil.createDocument("brokenDocument", null, null, null); // NOI18N xmlDocument = new SoftReference (d); // fire property change, because the document is errornous firePropertyChange (PROP_DOCUMENT, null, null); return d; } } /** * Get the externally usable, lazy document. * Delegates everything to the parsed document on disk (parsing as necessary), * except that getDoctype().getPublicId() is specially implemented so as to * not require loading the whole document. */ public Document getProxyDocument() { return proxyDocument; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("getDoctype") && args == null) { // NOI18N return Proxy.newProxyInstance(DelDoc.class.getClassLoader(), new Class[] {DocumentType.class}, this); } else if (method.getName().equals("getPublicId") && args == null) { // NOI18N Document d = getDocumentImpl(false); if (d != null) { DocumentType doctype = d.getDoctype(); return doctype == null ? null : doctype.getPublicId(); } else { return getIP().getPublicId(); } } else { return method.invoke(getDocumentImpl(true), args); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy