KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > filesystems > MIMEResolverImpl


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.filesystems;
21
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.util.logging.Level JavaDoc;
25 import java.util.logging.Logger JavaDoc;
26 import org.openide.cookies.InstanceCookie;
27 import org.openide.filesystems.FileObject;
28 import org.openide.filesystems.FileUtil;
29 import org.openide.filesystems.MIMEResolver;
30 import org.openide.loaders.DataObject;
31 import org.openide.loaders.Environment;
32 import org.openide.util.Utilities;
33 import org.openide.util.lookup.InstanceContent;
34 import org.openide.xml.XMLUtil;
35 import org.xml.sax.Attributes JavaDoc;
36 import org.xml.sax.SAXException JavaDoc;
37
38 /**
39  * MIMEResolver implementation driven by an XML document instance
40  * following PUBLIC "-//NetBeans//DTD MIME Resolver 1.0//EN".
41  *
42  * <p>
43  * 1. It provides Environment for XMLDataObjects with above public ID.
44  * <p>
45  * 2. Provided environment returns (InstanceCookie) Impl instance.
46  * <p>
47  * 3. [Instance]Lookup return that Impl instance.
48  * <p>
49  * 4. MIMEResolver's findMIMEType() parses description file and applies checks on passed files.
50  * <p>
51  * <b>Note:</b> It is public to be accessible by XML layer.
52  *
53  * @author Petr Kuzel
54  */

55 public final class MIMEResolverImpl extends XMLEnvironmentProvider implements Environment.Provider {
56
57     private static final long serialVersionUID = 18975L;
58     
59     // enable some tracing
60
private static final Logger JavaDoc ERR = Logger.getLogger(MIMEResolverImpl.class.getName());
61         
62     private static final boolean CASE_INSENSITIVE =
63         Utilities.isWindows() || Utilities.getOperatingSystem() == Utilities.OS_VMS;
64     
65     // DefaultEnvironmentProvider~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66

67     protected InstanceContent createInstanceContent(DataObject obj) {
68         FileObject fo = obj.getPrimaryFile();
69         InstanceContent ic = new InstanceContent();
70         ic.add(new Impl(fo));
71         return ic;
72     }
73     
74     // MIMEResolver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
75

76     //
77
// It implements InstanceCookie because it is added to environment of XML document.
78
// The cookie return itself i.e. MIMEResolver to be searchable by Lookup.
79
//
80
static class Impl extends MIMEResolver implements InstanceCookie {
81         // This file object describes rules that drive ths instance
82
private final FileObject data;
83
84         // Resolvers in reverse order
85
private FileElement[] smell = null;
86                 
87         private short state = DescParser.INIT;
88         
89         Impl(FileObject obj) {
90             if (ERR.isLoggable(Level.FINE)) ERR.fine("MIMEResolverImpl.Impl.<init>(" + obj + ")"); // NOI18N
91
data = obj;
92         }
93         
94         /**
95          * Resolves FileObject and returns recognized MIME type
96          * @param fo is FileObject which should be resolved
97          * @return recognized MIME type or null if not recognized
98          */

99         public String JavaDoc findMIMEType(FileObject fo) {
100
101             synchronized (this) { // lazy init
102

103                 if (state == DescParser.INIT) {
104                     state = parseDesc();
105                 }
106                 
107                 if (state == DescParser.ERROR) {
108                     return null;
109                 }
110             }
111
112             // smell is filled in reverse order
113

114             for (int i = smell.length-1; i>=0; i--) {
115                 String JavaDoc s = smell[i].resolve(fo);
116                 if (s != null) {
117                     if (ERR.isLoggable(Level.FINE)) ERR.fine("MIMEResolverImpl.findMIMEType(" + fo + ")=" + s); // NOI18N
118
return s;
119                 }
120             }
121             
122             return null;
123         }
124
125         // description document is parsed in the same thread
126
private short parseDesc() {
127             smell = new FileElement[0];
128             DescParser parser = new DescParser(data);
129             parser.parse();
130             smell = (parser.template != null) ? parser.template : smell;
131             if (ERR.isLoggable(Level.FINE)) {
132                 if (parser.state == DescParser.ERROR) {
133                     ERR.fine("MIMEResolverImpl.Impl parsing error!");
134                 } else {
135                     StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
136                     buf.append("Parse: ");
137                     for (int i = 0; i<smell.length; i++)
138                         buf.append('\n').append(smell[i]);
139                     ERR.fine(buf.toString());
140                 }
141             }
142             return parser.state;
143         }
144         
145         // InstanceCookie ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
146

147         public Object JavaDoc instanceCreate() {
148             return this;
149         }
150
151         public Class JavaDoc instanceClass() {
152             return this.getClass();
153         }
154
155         public String JavaDoc instanceName() {
156             return this.getClass().getName();
157         }
158
159         /** For debug purposes. */
160         public String JavaDoc toString() {
161             return "MIMEResolverImpl.Impl[" + data + ", " + smell + "]"; // NOI18N
162
}
163
164         
165     }
166
167     
168     // XML -> memory representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
169

170     /**
171      * Resonsible for parsing backend FileObject and filling resolvers
172      * in memory structure according to it.
173      */

174     private static class DescParser extends DefaultParser {
175
176         private FileElement[] template = null;
177         
178         // file state substates
179
private short file_state = INIT;
180         
181         // references active resolver component
182
private MIMEComponent component = null;
183         private String JavaDoc componentDelimiter = null;
184
185
186         DescParser(FileObject fo) {
187             super(fo);
188         }
189
190         // pseudo validation states
191
private static final short IN_ROOT = 1;
192         private static final short IN_FILE = 2;
193         private static final short IN_RESOLVER = 3;
194         private static final short IN_COMPONENT = 4;
195
196         // second state dimension
197
private static final short IN_EXIT = INIT + 1;
198         
199         // grammar elements
200
private static final String JavaDoc ROOT = "MIME-resolver"; // NOI18N
201
private static final String JavaDoc FILE = "file"; // NOI18N
202
private static final String JavaDoc MIME = "mime"; // NOI18N
203
private static final String JavaDoc EXT = "ext"; // NOI18N
204
private static final String JavaDoc RESOLVER = "resolver"; // NOI18N
205
private static final String JavaDoc FATTR = "fattr"; // NOI18N
206
private static final String JavaDoc NAME = "name"; // NOI18N
207
private static final String JavaDoc MAGIC = "magic"; // NOI18N
208
private static final String JavaDoc HEX = "hex"; // NOI18N
209
private static final String JavaDoc MASK = "mask"; // NOI18N
210
private static final String JavaDoc VALUE = "text"; // NOI18N
211
private static final String JavaDoc EXIT = "exit"; // NOI18N
212
private static final String JavaDoc XML_RULE_COMPONENT = "xml-rule"; // NOI18N
213

214         public void startElement(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName, Attributes JavaDoc atts) throws SAXException JavaDoc {
215
216             String JavaDoc s;
217
218             switch (state) {
219
220                 case INIT:
221
222                     if (ROOT.equals(qName) == false) error();
223                     state = IN_ROOT;
224                     break;
225
226                 case IN_ROOT:
227                     if (FILE.equals(qName) == false) error();
228
229                     // prepare file element structure
230
// actual one is at index 0
231

232                     if (template == null) {
233                         template = new FileElement[] {new FileElement()};
234                     } else {
235                         FileElement[] n = new FileElement[template.length +1];
236                         System.arraycopy(template, 0, n, 1, template.length);
237                         n[0] = new FileElement();
238                         template = n;
239                     }
240
241                     state = IN_FILE;
242                     break;
243
244                 case IN_FILE:
245
246                     if (file_state == IN_EXIT) error();
247                     
248                     if (EXT.equals(qName)) {
249
250                         s = atts.getValue(NAME); if (s == null) error();
251                         template[0].fileCheck.addExt(s);
252
253                     } else if (MAGIC.equals(qName)) {
254
255                         s = atts.getValue(HEX); if (s == null) error();
256                         String JavaDoc mask = atts.getValue(MASK);
257                         
258                         char[] chars = s.toCharArray();
259                         byte[] mask_bytes = null; // mask is optional
260

261                         try {
262                         
263                             if (mask != null) {
264                                 char[] mask_chars = mask.toCharArray();
265                                 mask_bytes = XMLUtil.fromHex(mask_chars, 0, mask_chars.length);
266                             }
267                         
268                             byte[] magic = XMLUtil.fromHex(chars, 0, chars.length);
269                             if (template[0].fileCheck.setMagic(magic, mask_bytes) == false) {
270                                 error();
271                             }
272                         } catch (IOException JavaDoc ioex) {
273                             error();
274                         }
275
276
277                     } else if (MIME.equals(qName)) {
278
279                         s = atts.getValue(NAME); if (s == null) error();
280                         template[0].fileCheck.addMIME(s);
281
282                     } else if (FATTR.equals(qName)) {
283
284                         s = atts.getValue(NAME); if (s == null) error();
285                         String JavaDoc val = atts.getValue(VALUE);
286                         template[0].fileCheck.addAttr(s, val);
287
288                     } else if (RESOLVER.equals(qName)) {
289
290                         if (template[0].fileCheck.exts == null
291                             && template[0].fileCheck.mimes == null
292                             && template[0].fileCheck.fatts == null
293                             && template[0].fileCheck.magic == null) {
294                                 error(); // at least one must be specified
295
}
296
297                         s = atts.getValue(MIME); if (s == null) error();
298                         template[0].setMIME(s);
299
300                         state = IN_RESOLVER;
301                         
302                         break;
303
304                     } else if (EXIT.equals(qName)) {
305                         
306                         file_state = IN_EXIT;
307                         break;
308                         
309                         
310                     } else {
311                         String JavaDoc reason = "Unexpected element: " + qName;
312                         error(reason);
313                     }
314                     break;
315
316                 case IN_RESOLVER:
317                     
318                     // it is switch to hardcoded components
319
// you can smooth;y add new ones by entering them
320

321                     // PLEASE update DTD public ID register it to XML Environment.Provider
322
// Let the DTD is backward compatible
323

324                     if (XML_RULE_COMPONENT.equals(qName)) {
325                         enterComponent(XML_RULE_COMPONENT, new XMLMIMEComponent());
326                         component.startElement(namespaceURI, localName, qName, atts);
327                     }
328                     
329                     break;
330
331                 case IN_COMPONENT:
332                     
333                     component.startElement(namespaceURI, localName, qName, atts);
334                     break;
335                     
336                 default:
337
338             }
339         }
340
341         private void enterComponent(String JavaDoc name, MIMEComponent component) {
342             this.component = component;
343             componentDelimiter = name;
344
345             component.setDocumentLocator(getLocator());
346             template[0].rule = component;
347             state = IN_COMPONENT;
348         }
349         
350         public void endElement(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName) throws SAXException JavaDoc {
351             switch (state) {
352                 case IN_FILE:
353                     if (FILE.equals(qName)) {
354                         state = IN_ROOT;
355                         file_state = INIT;
356                     }
357                     break;
358                     
359                 case IN_RESOLVER:
360                     if (RESOLVER.equals(qName)) {
361                         state = IN_FILE;
362                     }
363                     break;
364                     
365                 case IN_COMPONENT:
366                     component.endElement(namespaceURI, localName, qName);
367                     if (componentDelimiter.equals(qName)) {
368                         state = IN_RESOLVER;
369                     }
370                     break;
371             }
372         }
373
374         public void characters(char[] data, int offset, int len) throws SAXException JavaDoc {
375             if (state == IN_COMPONENT) component.characters(data, offset, len);
376         }
377     }
378     
379     /**
380      * Represents a resolving process made using a <tt>file</tt> element.
381      * <p>
382      * Responsible for pairing and performing fast check followed by optional
383      * rules and if all matches returning MIME type.
384      */

385     private static class FileElement {
386         FileElement() {}
387         
388         private Type fileCheck = new Type();
389         private String JavaDoc mime = null;
390         private MIMEComponent rule = null;
391
392         private void setMIME(String JavaDoc mime) {
393             if ("null".equals(mime)) return; // NOI18N
394
this.mime = mime;
395         }
396         
397         private String JavaDoc resolve(FileObject file) {
398                         
399             try {
400                 if (fileCheck.accept(file)) {
401                     if (mime == null) return null;
402                     if (rule == null) return mime;
403                     if (rule.acceptFileObject(file)) return mime;
404                 }
405             } catch (IOException JavaDoc io) {
406                 Logger.getLogger(MIMEResolverImpl.class.getName()).log(Level.WARNING, null, io);
407             }
408             return null;
409         }
410         
411         /**
412          * For debug puroses only.
413          */

414         public String JavaDoc toString() {
415             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
416             buf.append("FileElement(");
417             buf.append(fileCheck).append(' ');
418             buf.append(rule).append(' ');
419             buf.append("Result:").append(mime);
420             return buf.toString();
421         }
422     }
423
424         
425     /**
426      * Hold data from XML document and performs first stage check according to them.
427      * <p>
428      * The first stage check is resonsible for filtering files according to their
429      * attributes provided by lower layers.
430      * <p>
431      * We could generate hardwired class bytecode on a fly.
432      */

433     private static class Type {
434         Type() {}
435         private String JavaDoc[] exts;
436         private String JavaDoc[] mimes;
437         private String JavaDoc[] fatts;
438         private String JavaDoc[] vals; // contains null or value of attribute at the same index
439
private byte[] magic;
440         private byte[] mask;
441
442         
443         /**
444          * For debug purposes only.
445          */

446         public String JavaDoc toString() {
447             int i = 0;
448             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
449
450             buf.append("fast-check(");
451             
452             if (exts != null) {
453                 buf.append("exts:");
454                 for (i = 0; i<exts.length; i++)
455                     buf.append(exts[i]).append(", ");
456             }
457             
458             if (mimes != null) {
459                 buf.append("mimes:");
460                 for (i = 0; i<mimes.length; i++)
461                     buf.append(mimes[i]).append(", ");
462             }
463             
464             if (fatts != null) {
465                 buf.append("file-attributes:");
466                 for (i = 0; i<fatts.length; i++)
467                     buf.append(fatts[i]).append("='").append(vals[i]).append("', ");
468             }
469
470             if (magic != null) {
471                 buf.append("magic:").append(XMLUtil.toHex(magic, 0, magic.length));
472             }
473             
474             if (mask != null) {
475                 buf.append("mask:").append(XMLUtil.toHex(mask, 0, mask.length));
476             }
477
478             buf.append(')');
479             
480             return buf.toString();
481         }
482         
483         private void addExt(String JavaDoc ext) {
484             exts = Util.addString(exts, ext);
485         }
486
487         private void addMIME(String JavaDoc mime) {
488             mimes = Util.addString(mimes, mime.toLowerCase());
489         }
490         
491         private void addAttr(String JavaDoc name, String JavaDoc value) {
492             fatts = Util.addString(fatts, name);
493             vals = Util.addString(vals, value);
494         }
495
496         private boolean setMagic(byte[] magic, byte[] mask) {
497             if (magic == null) return true;
498             if (mask != null && magic.length != mask.length) return false;
499             this.magic = magic;
500             if (mask != null) {
501                 this.mask = mask;
502                 for (int i = 0; i<mask.length; i++) {
503                     this.magic[i] &= mask[i];
504                 }
505             }
506             return true;
507         }
508         
509         private boolean accept(FileObject fo) throws IOException JavaDoc {
510             
511             // check for resource extension
512

513             if (exts != null && fo.getExt() != null) {
514                 if (Util.contains(exts, fo.getExt(), CASE_INSENSITIVE)) return true;
515             }
516             
517             // check for resource mime type
518

519             if (mimes != null) {
520                 for (int i = mimes.length -1 ; i>=0; i--) {
521                     String JavaDoc s = FileUtil.getMIMEType(fo.getExt()); //!!! how to obtain resource MIME type as classified by lower layers?
522
if (s == null) continue;
523
524                     // RFC2045; remove content type paramaters and ignore case
525

526                     int l = s.indexOf(';');
527                     if (l>=0) s = s.substring(0, l-1);
528                     s = s.toLowerCase();
529                     if (s.equals(mimes[i])) return true;
530
531                     // RFC3023; allows "+xml" suffix
532

533                     if (mimes[i].length() > 0 && mimes[i].charAt(0) == '+' && s.endsWith(mimes[i])) return true; // NOI18N
534
}
535             }
536             
537             // check for magic
538

539             if (magic != null) {
540                 byte[] header = new byte[magic.length];
541
542 // System.err.println("FO" + fo);
543

544 // String m = mask == null ? "" : " mask " + XMLUtil.toHex(mask, 0, mask.length);
545
// System.err.println("Magic test " + XMLUtil.toHex(magic, 0, magic.length) + m);
546

547                 // fetch header
548

549                 InputStream JavaDoc in = null;
550                 boolean unexpectedEnd = false;
551                 try {
552                     in = fo.getInputStream();
553                     for (int i = 0; i<magic.length; ) {
554                         try {
555                             int read = in.read(header, i, magic.length-i);
556                             if (read < 0) unexpectedEnd = true;
557                             i += read;
558                         } catch (IOException JavaDoc ex) {
559                             unexpectedEnd = true;
560                             break;
561                         }
562                         if (unexpectedEnd) break;
563                     }
564                 } catch (IOException JavaDoc openex) {
565                     unexpectedEnd = true;
566                     if (fo.canRead() == true) {
567                         throw openex;
568                     } else {
569                         // #26521 silently do not recognize it
570
}
571                 } finally {
572                     try {
573                         if (in != null) in.close();
574                     } catch (IOException JavaDoc ioe) {
575                         // already closed
576
}
577                 }
578
579
580 // System.err.println("Header " + XMLUtil.toHex(header, 0, header.length));
581

582                 // compare it
583

584                 if ( unexpectedEnd == false ) {
585                     boolean diff = false;
586                     for (int i=0 ; i<magic.length; i++) {
587                         if (mask != null) header[i] &= mask[i];
588                         if (magic[i] != header[i]) {
589                             diff = true;
590                             break;
591                         }
592                     }
593
594                     if (diff == false) return true;
595                 }
596             }
597             
598             // check for fileobject attributes
599

600             if (fatts != null) {
601                 for (int i = fatts.length -1 ; i>=0; i--) {
602                     Object JavaDoc attr = fo.getAttribute(fatts[i]);
603                     if (attr != null) {
604                         if (vals[i] == null) return true;
605                         if (vals[i].equals(attr.toString())) return true;
606                     }
607                 }
608             }
609             
610             // no one template matched
611

612             return false;
613         }
614         
615     }
616 }
617
Popular Tags