KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > xml > FileEntityResolver


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.core.xml;
21
22 import java.io.IOException JavaDoc;
23 import java.beans.PropertyChangeListener JavaDoc;
24 import java.io.BufferedInputStream JavaDoc;
25 import java.io.InputStream JavaDoc;
26
27 import org.xml.sax.InputSource JavaDoc;
28 import org.xml.sax.SAXException JavaDoc;
29 import org.w3c.dom.DocumentType JavaDoc;
30
31
32 import org.openide.filesystems.FileObject;
33 import org.openide.filesystems.Repository;
34 import org.openide.loaders.*;
35 import org.openide.cookies.InstanceCookie;
36 import org.openide.util.Lookup;
37 import org.openide.util.lookup.*;
38 import org.openide.xml.EntityCatalog;
39 import org.openide.filesystems.FileChangeListener;
40 import org.openide.filesystems.FileRenameEvent;
41 import org.openide.filesystems.FileEvent;
42 import org.openide.filesystems.FileAttributeEvent;
43 import java.net.URL JavaDoc;
44 import java.util.logging.Level JavaDoc;
45 import java.util.logging.Logger JavaDoc;
46 import org.openide.util.Exceptions;
47
48
49 /**
50  * Entity resolver which loads entities (typically DTDs) from fixed
51  * locations in the system file system, according to public ID.
52  * <p>
53  * It expects that PUBLIC has at maximum three "//" parts
54  * (standard // vendor // entity name // language). It is basically
55  * converted to <tt>"/xml/entities/{vendor}/{entity_name}"</tt> resource name.
56  * <p>
57  * It also attaches <tt>Environment</tt> according to registrations
58  * at <tt>/xml/lookups/</tt> area. There can be registered:
59  * <tt>Environment.Provider</tt> or deprecated <tt>XMLDataObject.Processor</tt>
60  * and <tt>XMLDataObject.Info</tt> instances.
61  * <p>
62  * All above are core implementation features.
63  *
64  * @author Jaroslav Tulach
65  */

66 public final class FileEntityResolver extends EntityCatalog implements Environment.Provider {
67     private static final String JavaDoc ENTITY_PREFIX = "/xml/entities"; // NOI18N
68
private static final String JavaDoc LOOKUP_PREFIX = "/xml/lookups"; // NOI18N
69

70     static final Logger JavaDoc ERR = Logger.getLogger(FileEntityResolver.class.getName());
71     
72     /** Constructor
73      */

74     public FileEntityResolver() {
75     }
76     
77     /** Tries to find the entity on system file system.
78      */

79     public InputSource JavaDoc resolveEntity(String JavaDoc publicID, String JavaDoc systemID) throws IOException JavaDoc, SAXException JavaDoc {
80         if (publicID == null) {
81             return null;
82         }
83
84
85         String JavaDoc id = convertPublicId (publicID);
86         
87         StringBuffer JavaDoc sb = new StringBuffer JavaDoc (200);
88         sb.append (ENTITY_PREFIX);
89         sb.append (id);
90         
91         FileObject fo = Repository.getDefault ().getDefaultFileSystem ().findResource (sb.toString ());
92         if (fo != null) {
93             
94             // fill in InputSource instance
95

96             InputSource JavaDoc in = new InputSource JavaDoc (fo.getInputStream ());
97             try {
98                 Object JavaDoc myPublicID = fo.getAttribute("hint.originalPublicID"); //NOI18N
99
if (myPublicID instanceof String JavaDoc) {
100                     in.setPublicId((String JavaDoc)myPublicID);
101                 }
102                 URL JavaDoc url = fo.getURL();
103                 in.setSystemId(url.toString()); // we get nasty nbfs: instead nbres: but it is enough
104
} catch (IOException JavaDoc ex) {
105                 // do no care just no system id
106
}
107             return in;
108         } else {
109             return null;
110         }
111     }
112     
113     /** A method that tries to find the correct lookup for given XMLDataObject.
114      * @return the lookup
115      */

116     public Lookup getEnvironment(DataObject obj) {
117         if (obj instanceof XMLDataObject) {
118             XMLDataObject xml = (XMLDataObject)obj;
119             
120             String JavaDoc id = null;
121             try {
122                 DocumentType JavaDoc domDTD = xml.getDocument ().getDoctype ();
123                 if (domDTD != null) id = domDTD.getPublicId ();
124             } catch (IOException JavaDoc ex) {
125                 Exceptions.printStackTrace(ex);
126                 return null;
127             } catch (org.xml.sax.SAXException JavaDoc ex) {
128                 Exceptions.printStackTrace(ex);
129                 return null;
130             }
131
132             if (id == null) {
133                 return null;
134             }
135             
136             id = convertPublicId (id);
137             
138             return new Lkp (id, xml);
139         } else if (obj instanceof InstanceDataObject) {
140             return getEnvForIDO((InstanceDataObject) obj);
141         }
142         return null;
143     }
144     
145     private Lookup getEnvForIDO(InstanceDataObject ido) {
146         FileEntityResolver.DTDParser parser = new DTDParser(ido.getPrimaryFile());
147         parser.parse();
148         String JavaDoc id = parser.getPublicId();
149         if (id == null) return null;
150         id = convertPublicId (id);
151         return new Lkp (id, ido);
152     }
153     
154     /** A method that extracts a listener from data object.
155      *
156      * @param obj the data object that we are looking for environment of
157      * @param source the obj that provides the environment
158      * @return lookup provided by the obj or null if none has been found
159      */

160     private static Lookup findLookup (DataObject obj, DataObject source) {
161         if (source == null) {
162             return null;
163         }
164         
165         try {
166             InstanceCookie cookie = source.getCookie (InstanceCookie.class);
167             
168             if (cookie != null) {
169                 Object JavaDoc inst = cookie.instanceCreate ();
170                 if (inst instanceof Environment.Provider) {
171                     return ((Environment.Provider)inst).getEnvironment (obj);
172                 }
173                 
174                 if (!(obj instanceof XMLDataObject)) return null;
175
176                 if (inst instanceof XMLDataObject.Processor) {
177                     // convert provider
178
XMLDataObject.Info info = new XMLDataObject.Info ();
179                     info.addProcessorClass (inst.getClass ());
180                     inst = info;
181                 }
182
183                 if (inst instanceof XMLDataObject.Info) {
184                     return createInfoLookup ((XMLDataObject)obj, ((XMLDataObject.Info)inst));
185                 }
186
187             }
188         } catch (IOException JavaDoc ex) {
189             Exceptions.printStackTrace(ex);
190         } catch (ClassNotFoundException JavaDoc ex) {
191             Exceptions.printStackTrace(ex);
192         }
193         
194         return null;
195     }
196         
197     
198     /** Ugly hack to get to openide hidden functionality.
199      */

200     private static java.lang.reflect.Method JavaDoc method;
201     private static Lookup createInfoLookup (XMLDataObject obj, XMLDataObject.Info info) {
202         // well, it is a wormhole, but just for default compatibility
203
synchronized (FileEntityResolver.class) {
204             if (method == null) {
205                 try {
206                     java.lang.reflect.Method JavaDoc m = XMLDataObject.class.getDeclaredMethod ("createInfoLookup", new Class JavaDoc[] { // NOI18N
207
XMLDataObject.class,
208                         XMLDataObject.Info.class
209                     });
210                     m.setAccessible (true);
211                     method = m;
212                 } catch (Exception JavaDoc ex) {
213                     Exceptions.printStackTrace(ex);
214                     return null;
215                 }
216             }
217         }
218         try {
219             return (Lookup)method.invoke (null, new Object JavaDoc[] { obj, info });
220         } catch (Exception JavaDoc ex) {
221             Exceptions.printStackTrace(ex);
222             return null;
223         }
224     }
225
226     /** Converts the publicID into filesystem friendly name.
227      * <p>
228      * It expects that PUBLIC has at maximum three "//" parts
229      * (standard // vendor // entity name // language). It is basically
230      * converted to "vendor/entity_name" resource name.
231      *
232      * @see EntityCatalog
233      */

234     private static String JavaDoc convertPublicId (String JavaDoc publicID) {
235         char[] arr = publicID.toCharArray ();
236
237
238         int numberofslashes = 0;
239         int state = 0;
240         int write = 0;
241         OUT: for (int i = 0; i < arr.length; i++) {
242             char ch = arr[i];
243
244             switch (state) {
245             case 0:
246                 // initial state
247
if (ch == '+' || ch == '-' || ch == 'I' || ch == 'S' || ch == 'O') {
248                     // do not write that char
249
continue;
250                 }
251                 // switch to regular state
252
state = 1;
253                 // fallthru
254
case 1:
255                 // regular state expecting any character
256
if (ch == '/') {
257                     state = 2;
258                     if (++numberofslashes == 3) {
259                         // last part of the ID, exit
260
break OUT;
261                     }
262                     arr[write++] = '/';
263                     continue;
264                 }
265                 break;
266             case 2:
267                 // previous character was /
268
if (ch == '/') {
269                     // ignore second / and write nothing
270
continue;
271                 }
272                 state = 1;
273                 break;
274             }
275
276             // write the char into the array
277
if (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9') {
278                 arr[write++] = ch;
279             } else {
280                 arr[write++] = '_';
281             }
282         }
283
284         return new String JavaDoc (arr, 0, write);
285     }
286         
287     
288     /** Finds a fileobject for given ID.
289      * @param id string id
290      * @param last[0] will be filled with last file object we should listen on
291      * @return file object that should represent it
292      */

293     private static FileObject findObject (String JavaDoc id, FileObject[] last) {
294         StringBuffer JavaDoc sb = new StringBuffer JavaDoc (200);
295         sb.append (LOOKUP_PREFIX);
296         sb.append (id);
297         int len = sb.length ();
298         // at least for now
299
sb.append (".instance"); // NOI18N
300

301         FileObject root = Repository.getDefault ().getDefaultFileSystem ().getRoot ();
302      
303         String JavaDoc toSearch1 = sb.toString ();
304         int indx = searchFolder (root, toSearch1, last);
305         if (indx == -1) {
306             // not possible to find folders
307
return null;
308         }
309
310         FileObject fo = last[0].getFileObject (toSearch1.substring (indx));
311         
312         if (fo == null) {
313             // try to find a file with xml extension
314
sb.setLength (len);
315             sb.append (".xml"); // NOI18N
316

317             fo = last[0].getFileObject (sb.toString ().substring (indx));
318         }
319         
320         return fo;
321     }
322     
323     /** Find last folder for resourceName.
324      * @param fo file object to search from
325      * @param resourceName name of file to find
326      * @param last last[0] will be filled with the last found name
327      * @return position of last / if everything has been searched, or -1 if some files are missing
328      */

329     private static int searchFolder (FileObject fo, String JavaDoc resourceName, FileObject[] last) {
330         int pos = 0;
331         
332         for (;;) {
333             int next = resourceName.indexOf('/', pos);
334             if (next == -1) {
335                 // end of the search
336
last[0] = fo;
337                 return pos;
338             }
339             
340             if (next == pos) {
341                 pos++;
342                 continue;
343             }
344             
345             FileObject nf = fo.getFileObject(resourceName.substring (pos, next));
346             if (nf == null) {
347                 // not found a continuation
348
last[0] = fo;
349                 return -1;
350             }
351             
352             // proceed to next one
353
pos = next + 1;
354             fo = nf;
355         }
356     }
357     
358     // internally stops documet parsing when looking for public id
359
private static class StopSaxException extends SAXException JavaDoc {
360         public StopSaxException() { super("STOP"); } //NOI18N
361
}
362
363     private static final StopSaxException STOP = new StopSaxException();
364     
365     ////////////////////////////////////////////////////////////////////////
366
// DTDParser
367
////////////////////////////////////////////////////////////////////////
368
/** resolve the PUBLIC item from the xml header of .settings file */
369     private static class DTDParser extends org.xml.sax.helpers.DefaultHandler JavaDoc
370     implements org.xml.sax.ext.LexicalHandler JavaDoc {
371         
372         private String JavaDoc publicId = null;
373         private FileObject src;
374         
375         public DTDParser(FileObject src) {
376             this.src = src;
377         }
378         
379         public String JavaDoc getPublicId() {
380             return publicId;
381         }
382         
383         public void parse() {
384             InputStream JavaDoc in = null;
385             try {
386                 org.xml.sax.XMLReader JavaDoc reader = org.openide.xml.XMLUtil.createXMLReader(false, false);
387                 reader.setContentHandler(this);
388                 reader.setEntityResolver(this);
389                 in = new BufferedInputStream JavaDoc (src.getInputStream());
390                 InputSource JavaDoc is = new InputSource JavaDoc(in);
391                 try {
392                     reader.setFeature("http://xml.org/sax/features/validation", false); //NOI18N
393
} catch (SAXException JavaDoc sex) {
394                     ERR.warning(
395                     "XML parser does not support validation feature."); //NOI18N
396
}
397                 try {
398                     reader.setProperty("http://xml.org/sax/properties/lexical-handler", this); //NOI18N
399
} catch (SAXException JavaDoc sex) {
400                     ERR.warning(
401                     "XML parser does not support lexical-handler feature."); //NOI18N
402
}
403                 reader.parse(is);
404             } catch (StopSaxException ex) {
405                 ERR.log(Level.FINE, null, ex);
406             } catch (Exception JavaDoc ex) { // SAXException, FileNotFoundException, IOException
407
if ("org.openide.util.lookup.AbstractLookup$ISE".equals (ex.getClass ().getName ())) { // NOI18N
408
// this is covered by the FileEntityResolverDeadlock54971Test
409
throw (IllegalStateException JavaDoc)ex;
410                 }
411                 
412                 try {
413                     // #25082: do not notify an exception if the file comes
414
// from other filesystem than the system filesystem
415
if (src.getFileSystem() == Repository.getDefault().getDefaultFileSystem()) {
416                         ERR.log(Level.WARNING, "Parsing " + src, ex); // NOI18N
417
}
418                 } catch (org.openide.filesystems.FileStateInvalidException fie) {
419                     // ignore
420
}
421             } finally {
422                 try {
423                     if (in != null) {
424                         in.close();
425                     }
426                 } catch (IOException JavaDoc exc) {
427                     ERR.log(Level.WARNING, "Closing stream for " + src, exc);
428                 }
429             }
430         }
431         
432         public InputSource JavaDoc resolveEntity(String JavaDoc publicId, String JavaDoc systemID) {
433             InputSource JavaDoc ret = new InputSource JavaDoc(new java.io.StringReader JavaDoc("")); // NOI18N
434
ret.setSystemId("StringReader"); //NOI18N
435
return ret;
436         }
437         
438         public void endDTD() throws org.xml.sax.SAXException JavaDoc {
439             throw STOP;
440         }
441         
442         public void startDTD(String JavaDoc name, String JavaDoc publicId, String JavaDoc systemId) throws org.xml.sax.SAXException JavaDoc {
443             this.publicId = publicId;
444         }
445         
446         public void startEntity(String JavaDoc str) throws org.xml.sax.SAXException JavaDoc {}
447         public void endEntity(String JavaDoc str) throws org.xml.sax.SAXException JavaDoc {}
448         public void comment(char[] values, int param, int param2) throws org.xml.sax.SAXException JavaDoc {}
449         public void startCDATA() throws org.xml.sax.SAXException JavaDoc {}
450         public void endCDATA() throws org.xml.sax.SAXException JavaDoc {}
451         
452     }
453     
454     
455     /** A special lookup associated with id.
456      */

457     private static final class Lkp extends ProxyLookup
458     implements PropertyChangeListener JavaDoc, FileChangeListener {
459         /** converted ID we are associated with */
460         private String JavaDoc id;
461         /** for this data object we initialized this lookup */
462         private DataObject xml;
463         
464         /** last file folder we are listening on. Initialized lazily */
465         private volatile FileObject folder;
466         /** a data object that produces values Initialized lazily */
467         private volatile DataObject obj;
468         
469         /** @param id the id to work on */
470         public Lkp (String JavaDoc id, DataObject xml) {
471             super (new Lookup[0]);
472             this.id = id;
473             this.xml = xml;
474         }
475      
476         /** Check whether all necessary values are updated.
477          */

478         protected void beforeLookup (Template t) {
479             if (ERR.isLoggable(Level.FINE)) {
480                 ERR.fine("beforeLookup: " + t.getType() + " for " + xml); // NOI18N
481
}
482             
483             if (folder == null && obj == null) {
484                 update ();
485             }
486         }
487         
488         /** Updates current state of the lookup.
489          */

490         private void update () {
491             if (ERR.isLoggable(Level.FINE)) ERR.fine("update: " + id + " for " + xml); // NOI18N
492
FileObject[] last = new FileObject[1];
493             FileObject fo = findObject (id, last);
494             if (ERR.isLoggable(Level.FINE)) ERR.fine("fo: " + fo + " for " + xml); // NOI18N
495
DataObject o = null;
496             
497             if (fo != null) {
498                 try {
499                     o = DataObject.find (fo);
500                     if (ERR.isLoggable(Level.FINE)) ERR.fine("object found: " + o + " for " + xml); // NOI18N
501
} catch (org.openide.loaders.DataObjectNotFoundException ex) {
502                     Exceptions.printStackTrace(ex);
503                 }
504             }
505         
506             if (o == obj) {
507                 if (ERR.isLoggable(Level.FINE)) ERR.fine("same data object" + " for " + xml); // NOI18N
508
// the data object is still the same as used to be
509
//
510
Lookup l = findLookup (xml, o);
511                 if (o != null && l != null) {
512                     if (ERR.isLoggable(Level.FINE)) ERR.fine("updating lookups" + " for " + xml); // NOI18N
513
// just update the lookups
514
setLookups (new Lookup[] { l });
515                     if (ERR.isLoggable(Level.FINE)) ERR.fine("updating lookups done" + " for " + xml); // NOI18N
516
// and exit
517
return;
518                 }
519             } else {
520                 // data object changed
521
Lookup l = findLookup(xml, o);
522                 
523                 if (o != null && l != null) {
524                     if (ERR.isLoggable(Level.FINE)) ERR.fine("change the lookup"); // NOI18N
525
// add listener to changes of the data object
526
o.addPropertyChangeListener (
527                         org.openide.util.WeakListeners.propertyChange (this, o)
528                     );
529                     // update the lookups
530
setLookups (new Lookup[] { l });
531                     if (ERR.isLoggable(Level.FINE)) ERR.fine("change in lookup done" + " for " + xml); // NOI18N
532
// and exit
533
obj = o;
534                     if (ERR.isLoggable(Level.FINE)) ERR.fine("data object updated to " + obj + " for " + xml); // NOI18N
535
return;
536                 } else {
537                     obj = o;
538                     if (ERR.isLoggable(Level.FINE)) ERR.fine("data object updated to " + obj + " for " + xml); // NOI18N
539
}
540             }
541             
542             if (ERR.isLoggable(Level.FINE)) ERR.fine("delegating to nobody for " + obj + " for " + xml); // NOI18N
543
// object is null => there are no lookups
544
setLookups (new Lookup[0]);
545             
546             // and start listening on latest existing folder
547
// if we did not do it yet
548
if (folder != last[0]) {
549                 folder = last[0];
550                 last[0].addFileChangeListener (
551                     org.openide.filesystems.FileUtil.weakFileChangeListener (this, last[0])
552                 );
553             }
554         }
555         
556         /** Fired when a file is deleted.
557          * @param fe the event describing context where action has taken place
558          */

559         public void fileDeleted(FileEvent fe) {
560             update ();
561         }
562         
563         /** Fired when a new folder is created. This action can only be
564          * listened to in folders containing the created folder up to the root of
565          * file system.
566          *
567          * @param fe the event describing context where action has taken place
568          */

569         public void fileFolderCreated(FileEvent fe) {
570             update ();
571         }
572         
573         /** Fired when a new file is created. This action can only be
574          * listened in folders containing the created file up to the root of
575          * file system.
576          *
577          * @param fe the event describing context where action has taken place
578          */

579         public void fileDataCreated(FileEvent fe) {
580             update ();
581         }
582         
583         /** Fired when a file attribute is changed.
584          * @param fe the event describing context where action has taken place,
585          * the name of attribute and the old and new values.
586          */

587         public void fileAttributeChanged(FileAttributeEvent fe) {
588         }
589         
590         public void propertyChange(java.beans.PropertyChangeEvent JavaDoc ev) {
591             String JavaDoc name = ev.getPropertyName();
592             
593             if (
594                 DataObject.PROP_COOKIE.equals(name) ||
595                 DataObject.PROP_NAME.equals(name) ||
596                 DataObject.PROP_VALID.equals(name) ||
597                 DataObject.PROP_PRIMARY_FILE.equals(name)
598             ) {
599                 update ();
600             }
601         }
602         
603         /** Fired when a file is renamed.
604          * @param fe the event describing context where action has taken place
605          * and the original name and extension.
606          */

607         public void fileRenamed(FileRenameEvent fe) {
608             update ();
609         }
610         
611         /** Fired when a file is changed.
612          * @param fe the event describing context where action has taken place
613          */

614         public void fileChanged(FileEvent fe) {
615         }
616         
617     }
618 }
619
Popular Tags