DocumentFormat.java
001 /*
002  *  DocumentFormat.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Hamish Cunningham, 25/May/2000
013  *
014  *  $Id: DocumentFormat.java 19678 2016-10-14 12:03:14Z markagreenwood $
015  */
016 
017 package gate;
018 
019 import java.io.IOException;
020 import java.io.InputStream;
021 import java.io.Reader;
022 import java.net.URL;
023 import java.net.URLConnection;
024 import java.nio.charset.Charset;
025 import java.util.Collections;
026 import java.util.HashMap;
027 import java.util.LinkedList;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.Set;
031 import java.util.StringTokenizer;
032 import java.util.Vector;
033 
034 import org.apache.commons.io.IOUtils;
035 import org.apache.commons.lang.CharSet;
036 
037 import gate.corpora.MimeType;
038 import gate.corpora.RepositioningInfo;
039 import gate.creole.AbstractLanguageResource;
040 import gate.event.StatusListener;
041 import gate.util.DocumentFormatException;
042 
043 /** The format of Documents. Subclasses of DocumentFormat know about
044   * particular MIME types and how to unpack the information in any
045   * markup or formatting they contain into GATE annotations. Each MIME
046   * type has its own subclass of DocumentFormat, e.g. XmlDocumentFormat,
047   * RtfDocumentFormat, MpegDocumentFormat. These classes register themselves
048   * with a static index residing here when they are constructed. Static
049   * getDocumentFormat methods can then be used to get the appropriate
050   * format class for a particular document.
051   */
052 public abstract class DocumentFormat
053 extends AbstractLanguageResource implements LanguageResource{
054   
055   private static final long serialVersionUID = 4147880563349143923L;
056 
057   /** The MIME type of this format. */
058   private MimeType mimeType = null;
059 
060   /** Map of MimeTypeString to ClassHandler class. This is used to find the
061     * language resource that deals with the specific Document format
062     */
063   protected static final Map<String, DocumentFormat>
064           mimeString2ClassHandlerMap = new HashMap<String, DocumentFormat>();
065   /** Map of MimeType to DocumentFormat Class. This is used to find the
066     * DocumentFormat subclass that deals with a particular MIME type.
067     */
068   protected static final Map<String, MimeType>
069           mimeString2mimeTypeMap = new HashMap<String, MimeType>();
070 
071   /** Map of Set of file suffixes to MimeType. This is used to figure
072     * out what MIME type a document is from its file name.
073     */
074   protected static final Map<String, MimeType>
075           suffixes2mimeTypeMap = new HashMap<String, MimeType>();
076 
077   /** Map of Set of magic numbers to MimeType. This is used to guess the
078     * MIME type of a document, when we don't have any other clues.
079     */
080   protected static final Map<String, MimeType>
081           magic2mimeTypeMap = new HashMap<String, MimeType>();
082 
083   /** Map of markup elements to annotation types. If it is null, the
084     * unpackMarkup() method will convert all markup, using the element names
085     * for annotation types. If it is non-null, only those elements specified
086     * here will be converted.
087     */
088   protected Map<String,String> markupElementsMap = null;
089 
090   /** This map is used inside uppackMarkup() method...
091     * When an element from the map is encounted, The corresponding string
092     * element is added to the document content
093     */
094   protected Map<String,String> element2StringMap = null;
095 
096   /** The features of this resource */
097   private FeatureMap features = null;
098 
099   /** Default construction */
100   public DocumentFormat() {}
101 
102   /** listeners for status report */
103   private transient Vector<StatusListener> statusListeners;
104 
105   /** Flag for enable/disable collecting of repositioning information */
106   private Boolean shouldCollectRepositioning = new Boolean(false);
107 
108   /** If the document format could collect repositioning information
109    *  during the unpack phase this method will return <B>true</B>.
110    *  <BR>
111    *  You should override this method in the child class of the defined
112    *  document format if it could collect the repositioning information.
113    */
114   public Boolean supportsRepositioning() {
115     return new Boolean(false);
116   // supportsRepositioning
117 
118   public void setShouldCollectRepositioning(Boolean b) {
119     if(supportsRepositioning().booleanValue() && b.booleanValue()) {
120       shouldCollectRepositioning = b;
121     }
122     else {
123       shouldCollectRepositioning = new Boolean(false);
124     // if
125   // setShouldCollectRepositioning
126 
127   public Boolean getShouldCollectRepositioning() {
128     return shouldCollectRepositioning;
129   //
130 
131   /** Unpack the markup in the document. This converts markup from the
132     * native format (e.g. XML, RTF) into annotations in GATE format.
133     * Uses the markupElementsMap to determine which elements to convert, and
134     * what annotation type names to use.
135     */
136   abstract public void unpackMarkup(Document doc)
137                                       throws DocumentFormatException;
138 
139   abstract public void unpackMarkup(Document doc, RepositioningInfo repInfo,
140                                         RepositioningInfo ampCodingInfo)
141                                       throws DocumentFormatException;
142   /** Unpack the markup in the document. This method calls unpackMarkup on the
143     * GATE document, but after it saves its content as a feature attached to
144     * the document. This method is useful if one wants to save the content
145     * of the document being unpacked. After the markups have been unpacked,
146     * the content of the document will be replaced with a new one containing
147     * the text between markups.
148     *
149     @param doc the document that will be unpacked
150     @param originalContentFeatureType the name of the feature that will hold
151     * the document's content.
152     */
153   public void unpackMarkupDocument doc,
154                             String  originalContentFeatureType )
155                                               throws DocumentFormatException{
156      FeatureMap fm = doc.getFeatures();
157      if (fm == nullfm = Factory.newFeatureMap();
158      fm.put(originalContentFeatureType, doc.getContent().toString());
159      doc.setFeatures(fm);
160      unpackMarkup(doc);
161   }// unpackMarkup();
162 
163   /**
164     * Returns a MimeType having as input a fileSufix.
165     * If the file sufix is <b>null</b> or not recognised then,
166     <b>null</b> will be returned.
167     @param fileSufix The file sufix associated with a recognisabe mime type.
168     @return The MimeType associated with this file suffix.
169     */
170   static private MimeType  getMimeType(String fileSufix){
171     // Get a mimeType string associated with this fileSuffix
172     // Eg: for html returns  MimeType("text/html"), for xml returns
173     // MimeType("text/xml")
174     if(fileSufix == nullreturn null;
175     return  suffixes2mimeTypeMap.get(fileSufix.toLowerCase());
176   }//getMimeType
177   
178   public static Set<String> getSupportedMimeTypes() {
179     return Collections.unmodifiableSet(mimeString2mimeTypeMap.keySet());
180   }
181 
182   /**
183     * Returns a MymeType having as input a URL object. If the MimeType wasn't
184     * recognized it returns <b>null</b>.
185     @param url The URL object from which the MimeType will be extracted
186     @return A MimeType object for that URL, or <b>null</b> if the Mime Type is
187     * unknown.
188     */
189   static private MimeType  getMimeType(URL url) {
190     String mimeTypeString = null;
191     String charsetFromWebServer = null;
192     String contentType = null;
193     InputStream is = null;
194     MimeType mimeTypeFromWebServer = null;
195     MimeType mimeTypeFromFileSuffix = null;
196     MimeType mimeTypeFromMagicNumbers = null;
197 
198     if (url == null)
199       return null;
200     // Ask the web server for the content type
201     // We expect to get contentType something like this:
202     // "text/html; charset=iso-8859-1"
203     // Charset is optional
204 
205     try {
206     try{
207       URLConnection urlconn = url.openConnection();
208       is = urlconn.getInputStream();
209       contentType = urlconn.getContentType();
210     catch (IOException e){
211       // Failed to get the content type with te Web server.
212       // Let's try some other methods like FileSuffix or magic numbers.
213     }
214     // If a content Type was returned by the server, try to get the mime Type
215     // string
216     // If contentType is something like this:"text/html; charset=iso-8859-1"
217     // try to get content Type string (text/html)
218     if (contentType != null){
219       StringTokenizer st = new StringTokenizer(contentType, ";");
220       // We assume that the first token is the mime type string...
221       // If this doesn't happen then BAD LUCK :(( ...
222       if (st.hasMoreTokens())
223         mimeTypeString     = st.nextToken().toLowerCase();
224       // The next token it should be the CharSet
225       if (st.hasMoreTokens())
226         charsetFromWebServer = st.nextToken().toLowerCase();
227       if (charsetFromWebServer != null){
228         //We have something like : "charset=iso-8859-1" and let's extract the
229         // encoding.
230         st = new StringTokenizer(charsetFromWebServer, "=");
231         // Don't need this anymore
232         charsetFromWebServer = null;
233         // Discarding the first token which is : "charset"
234         if (st.hasMoreTokens())
235           st.nextToken();
236         // Get the encoding : "ISO-8859-1"
237         if (st.hasMoreTokens())
238           charsetFromWebServer = st.nextToken().toUpperCase();
239       // End if
240     }// end if
241     // Return the corresponding MimeType with WebServer from the associated MAP
242     mimeTypeFromWebServer = mimeString2mimeTypeMap.get(mimeTypeString);
243     // Let's try a file suffix detection
244     // mimeTypeFromFileSuffix = getMimeType(getFileSuffix(url));    
245     for(String suffix : getFileSuffixes(url)) {
246       mimeTypeFromFileSuffix = getMimeType(suffix);
247       if(mimeTypeFromFileSuffix != nullbreak;
248     }
249 
250     // Let's perform a magic numbers guess..
251     mimeTypeFromMagicNumbers = guessTypeUsingMagicNumbers(is,
252                                                     charsetFromWebServer);
253     }
254     finally {
255       IOUtils.closeQuietly(is)//null safe
256     }
257     //All those types enter into a deciding system
258     return decideBetweenThreeMimeTypesmimeTypeFromWebServer,
259                                         mimeTypeFromFileSuffix,
260                                         mimeTypeFromMagicNumbers);
261   }//getMimeType
262 
263   /**
264     * This method decides what mimeType is in majority
265     @param aMimeTypeFromWebServer a MimeType
266     @param aMimeTypeFromFileSuffix a MimeType
267     @param aMimeTypeFromMagicNumbers a MimeType
268     @return the MimeType which occurs most. If all are null, then returns
269     <b>null</b>
270     */
271   protected static MimeType decideBetweenThreeMimeTypes(
272                                     MimeType aMimeTypeFromWebServer,
273                                     MimeType aMimeTypeFromFileSuffix,
274                                     MimeType aMimeTypeFromMagicNumbers){
275 
276     // First a voting system
277     if (areEqual(aMimeTypeFromWebServer,aMimeTypeFromFileSuffix))
278       return aMimeTypeFromFileSuffix;
279     if (areEqual(aMimeTypeFromFileSuffix,aMimeTypeFromMagicNumbers))
280       return aMimeTypeFromFileSuffix;
281     if (areEqual(aMimeTypeFromWebServer,aMimeTypeFromMagicNumbers))
282       return aMimeTypeFromWebServer;
283 
284     // 1 is the highest priority
285     if (aMimeTypeFromFileSuffix != null)
286       aMimeTypeFromFileSuffix.addParameter("Priority","1");
287     // 2 is the second priority
288     if (aMimeTypeFromWebServer != null)
289       aMimeTypeFromWebServer.addParameter("Priority","2");
290     // 3 is the third priority
291     if (aMimeTypeFromMagicNumbers != null)
292       aMimeTypeFromMagicNumbers.addParameter("Priority","3");
293 
294     return decideBetweenTwoMimeTypes(
295                              decideBetweenTwoMimeTypes(aMimeTypeFromWebServer,
296                                                        aMimeTypeFromFileSuffix),
297                              aMimeTypeFromMagicNumbers);
298 
299   }// decideBetweenThreeMimeTypes
300 
301   /** Decide between two mimeTypes. The decistion is made on "Priority"
302     * parameter set into decideBetweenThreeMimeTypes method. If both mimeTypes
303     * doesn't have "Priority" paramether set, it will return one on them.
304     @param aMimeType a MimeType object with "Prority" parameter set
305     @param anotherMimeType a MimeType object with "Prority" parameter set
306     @return One of the two mime types.
307     */
308   protected static MimeType decideBetweenTwoMimeTypesMimeType aMimeType,
309                                                 MimeType anotherMimeType){
310     if (aMimeType == nullreturn anotherMimeType;
311     if (anotherMimeType == nullreturn aMimeType;
312 
313     int priority1 = 0;
314     int priority2 = 0;
315     // Both of them are not null
316     if (aMimeType.hasParameter("Priority"))
317       try{
318         priority1 =
319               new Integer(aMimeType.getParameterValue("Priority")).intValue();
320       }catch (NumberFormatException e){
321         return anotherMimeType;
322       }
323     if (anotherMimeType.hasParameter("Priority"))
324       try{
325         priority2 =
326           new Integer(anotherMimeType.getParameterValue("Priority")).intValue();
327       }catch (NumberFormatException e){
328         return aMimeType;
329       }
330 
331     // The lower the number, the highest the priority
332     if (priority1 <= priority2)
333       return aMimeType;
334     else
335       return anotherMimeType;
336   }// decideBetweenTwoMimeTypes
337 
338   /**
339     * Tests if two MimeType objects are equal.
340     @return true only if boths MimeType objects are different than <b>null</b>
341     * and their Types and Subtypes are equals. The method is case sensitive.
342     */
343   protected static boolean areEqualMimeType aMimeType,
344                                      MimeType anotherMimeType){
345     if (aMimeType == null || anotherMimeType == null)
346       return false;
347 
348     if aMimeType.getType().equals(anotherMimeType.getType()) &&
349          aMimeType.getSubtype().equals(anotherMimeType.getSubtype())
350        return true;
351     else
352       return false;
353   }// are Equal
354 
355   /**
356     * This method tries to guess the mime Type using some magic numbers.
357     @param aInputStream a InputStream which has to be transformed into a
358     *        InputStreamReader
359     @param anEncoding the encoding. If is null or unknown then a
360     * InputStreamReader with default encodings will be created.
361     @return the mime type associated with magic numbers
362     */
363   protected static MimeType guessTypeUsingMagicNumbers(InputStream aInputStream,
364                                                             String anEncoding){
365 
366     /*if (aInputStream == null) return null;
367     Reader reader = null;
368     if (anEncoding != null)
369       try{
370         reader = new BomStrippingInputStreamReader(aInputStream, anEncoding);
371       } catch (UnsupportedEncodingException e){
372         reader = null;
373       }
374     if (reader == null)
375       // Create a reader with the default encoding system
376       reader = new BomStrippingInputStreamReader(aInputStream);
377 
378     // We have a input stream reader
379     return runMagicNumbers(reader);*/
380     MimeType detectedMimeType = null;
381 
382     // the offset of the first match now we use a "first wins" priority
383     int firstOffset = Integer.MAX_VALUE;
384     
385     byte[] header = new byte[2048];
386     
387     try {
388       IOUtils.read(aInputStream, header);
389     }
390     catch (IOException e) {
391       return null;
392     }
393 
394     // Run the magic numbers test
395     for(Map.Entry<String, MimeType> kv : magic2mimeTypeMap.entrySet()) {
396       byte[] magic = null;
397       
398       try {
399         magic = kv.getKey().getBytes(anEncoding);
400       }
401       catch (Exception e) {
402         magic = kv.getKey().getBytes();
403       }
404       
405       int offset = indexOf(header,magic);
406       if (offset != -1) {
407         if (offset < firstOffset) {
408           detectedMimeType = kv.getValue();
409         }
410       }
411     }
412     
413     return detectedMimeType;
414   }//guessTypeUsingMagicNumbers
415   
416   /**
417    * Finds the first occurrence of the pattern in the text.
418    */
419   protected static int indexOf(byte[] data, byte[] pattern) {
420       int[] failure = computeFailure(pattern);
421 
422       int j = 0;
423       if (data.length == 0return -1;
424 
425       for (int i = 0; i < data.length; i++) {
426           while (j > && pattern[j!= data[i]) {
427               j = failure[j - 1];
428           }
429           if (pattern[j== data[i]) { j++; }
430           if (j == pattern.length) {
431               return i - pattern.length + 1;
432           }
433       }
434       return -1;
435   }
436 
437   /**
438    * Computes the failure function using a boot-strapping process,
439    * where the pattern is matched against itself.
440    */
441   private static int[] computeFailure(byte[] pattern) {
442       int[] failure = new int[pattern.length];
443 
444       int j = 0;
445       for (int i = 1; i < pattern.length; i++) {
446           while (j > && pattern[j!= pattern[i]) {
447               j = failure[j - 1];
448           }
449           if (pattern[j== pattern[i]) {
450               j++;
451           }
452           failure[i= j;
453       }
454 
455       return failure;
456   }
457 
458   /** Performs magic over Gate Document */
459   protected static MimeType runMagicNumbers(Reader aReader) {
460     // No reader, nothing to detect
461     ifaReader == nullreturn null;
462 
463     System.err.println("doing magic numbers");
464     
465     // Prepare to run the magic stuff
466     String strBuffer = null;
467     int bufferSize = 2048;
468     int charReads = 0;
469     char[] cbuf = new char[bufferSize];
470 
471     try {
472       charReads = aReader.read(cbuf,0,bufferSize);
473     catch (IOException e){
474       return null;
475     }// End try
476 
477     if (charReads == -1)
478       // the document is empty
479       return null;
480 
481     // Create a string form the buffer and perform some search on it.
482     strBuffer = new String(cbuf,0,charReads);
483 
484     // If this fails then surrender
485     return getTypeFromContent(strBuffer);
486   }// runMagicNumbers
487 
488   private static MimeType getTypeFromContent(String aContent){
489 
490     // change case to cover more variants
491     aContent = aContent.toLowerCase();
492 
493     // the mime type we have detected (null to start with)
494     MimeType detectedMimeType = null;
495 
496     // the offset of the first match now we use a "first wins" priority
497     int firstOffset = Integer.MAX_VALUE;
498 
499     // Run the magic numbers test
500     for(Map.Entry<String, MimeType> kv : magic2mimeTypeMap.entrySet()) {
501       // the magic code we are looking for
502       String magic = kv.getKey().toLowerCase();
503 
504       // the offset of this code in the content
505       int offset = aContent.indexOf(magic.toLowerCase());
506       if(offset != -&& offset < firstOffset) {
507         // if the magic code exists in the doc and appears before any others
508         // than use that mime type
509         detectedMimeType = kv.getValue();
510         firstOffset = offset;
511       }
512     }
513 
514     // return the mime type (null if we failed)
515     return detectedMimeType;
516   }
517 
518   /**
519     * Return the fileSuffix or null if the url doesn't have a file suffix
520     * If the url is null then the file suffix will be null also
521     */
522   @SuppressWarnings("unused")
523   private static String getFileSuffix(URL url){
524     String fileName = null;
525     String fileSuffix = null;
526 
527     // GIGO test  (garbage in garbage out)
528     if (url != null){
529       // get the file name from the URL
530       fileName = url.getFile();
531 
532       // tokenize this file name with "." as separator...
533       // the last token will be the file suffix
534       StringTokenizer st = new StringTokenizer(fileName,".");
535 
536       // fileSuffix is the last token
537       while (st.hasMoreTokens())
538         fileSuffix = st.nextToken();
539       // here fileSuffix is the last token
540     // End if
541     return fileSuffix;
542   }//getFileSufix
543 
544   /**
545    * Given a URL, this method returns all the 'file extensions' for the file
546    * part of the URL. For this purposes, a 'file extension' is any sequence of
547    * .-separated tokens (such as .gate.xml.gz). The order the extensions are 
548    * returned in is from the most specific (longest) to the most generic 
549    * (shortest) one, e.g. [.gate.xml.gz, .xml.gz, .gz]. 
550    */
551   private static List<String> getFileSuffixes(URL url){
552     List<String> res = new LinkedList<String>();
553     if (url != null){
554       // get the file name from the URL
555       String fileName = url.getPath();
556       int pos = fileName.lastIndexOf('/');
557       if(pos  > 0fileName = fileName.substring(pos);
558       pos = fileName.indexOf('.'1);
559       while(pos > && pos < fileName.length() 1) {
560         res.add(fileName.substring(pos + 1));
561         pos = fileName.indexOf('.', pos + 1);
562       }
563     }
564     return res;
565   }
566   
567   
568   /**
569     * Find a DocumentFormat implementation that deals with a particular
570     * MIME type, given that type.
571     @param  aGateDocument this document will receive as a feature
572     *                      the associated Mime Type. The name of the feature is
573     *                      MimeType and its value is in the format type/subtype
574     @param  mimeType the mime type that is given as input
575     */
576   static public DocumentFormat getDocumentFormat(gate.Document aGateDocument,
577                                                             MimeType mimeType){
578     FeatureMap      aFeatureMap    = null;
579     if(mimeType == null) {
580       String content = aGateDocument.getContent().toString();
581       // reduce size for better performance
582       if(content.length() 2048content = content.substring(02048);
583       mimeType = getTypeFromContentcontent );
584     }
585 
586     if (mimeType != null){
587       // If the Gate Document doesn't have a feature map atached then
588       // We will create and set one.
589       if(aGateDocument.getFeatures() == null){
590             aFeatureMap = Factory.newFeatureMap();
591             aGateDocument.setFeatures(aFeatureMap);
592       }// end if
593       aGateDocument.getFeatures().put("MimeType",mimeType.getType() "/" +
594                                           mimeType.getSubtype());
595 
596       return mimeString2ClassHandlerMap.get(mimeType.getType()
597                                                "/" + mimeType.getSubtype());
598     }// end If
599     return null;
600   // getDocumentFormat(aGateDocument, MimeType)
601 
602   /**
603     * Find a DocumentFormat implementation that deals with a particular
604     * MIME type, given the file suffix (e.g. ".txt") that the document came
605     * from.
606     @param  aGateDocument this document will receive as a feature
607     *                     the associated Mime Type. The name of the feature is
608     *                     MimeType and its value is in the format type/subtype
609     @param  fileSuffix the file suffix that is given as input
610     */
611   static public DocumentFormat getDocumentFormat(gate.Document aGateDocument,
612                                                             String fileSuffix) {
613     return getDocumentFormat(aGateDocument, getMimeType(fileSuffix));
614   // getDocumentFormat(String)
615   
616   /**
617    * Find the DocumentFormat implementation that deals with the given
618    * MIME type.
619    
620    @param mimeType the MIME type you want the DocumentFormat for
621    @return the DocumentFormat associated with the MIME type or null if
622    *         the MIME type does not have a registered DocumentFormat
623    */
624   public static DocumentFormat getDocumentFormat(MimeType mimeType) {
625     return mimeString2ClassHandlerMap.get(mimeType.getType() "/"
626             + mimeType.getSubtype());
627   }
628 
629   /**
630     * Find a DocumentFormat implementation that deals with a particular
631     * MIME type, given the URL of the Document. If it is an HTTP URL, we
632     * can ask the web server. If it has a recognised file extension, we
633     * can use that. Otherwise we need to use a map of magic numbers
634     * to MIME types to guess the type, and then look up the format using the
635     * type.
636     @param  aGateDocument this document will receive as a feature
637     *                      the associated Mime Type. The name of the feature is
638     *                      MimeType and its value is in the format type/subtype
639     @param  url  the URL that is given as input
640     */
641   static public DocumentFormat getDocumentFormat(gate.Document aGateDocument,
642                                                                       URL url) {
643     return getDocumentFormat(aGateDocument, getMimeType(url));
644   // getDocumentFormat(URL)
645   
646   /** Get the feature set */
647   @Override
648   public FeatureMap getFeatures() { return features; }
649 
650    /** Get the markup elements map */
651   public Map<String,String> getMarkupElementsMap() { return markupElementsMap; }
652 
653    /** Get the element 2 string map */
654   public Map<String,String> getElement2StringMap() { return element2StringMap; }
655 
656   /** Set the markup elements map */
657   public void setMarkupElementsMap(Map<String,String> markupElementsMap) {
658    this.markupElementsMap = markupElementsMap;
659   }
660 
661   /** Set the element 2 string map */
662   public void setElement2StringMap(Map<String,String> anElement2StringMap) {
663    element2StringMap = anElement2StringMap;
664   }
665 
666   /** Set the features map*/
667   @Override
668   public void setFeatures(FeatureMap features){this.features = features;}
669 
670   /** Set the mime type*/
671 
672   public void setMimeType(MimeType aMimeType){mimeType = aMimeType;}
673   /** Gets the mime Type*/
674   public MimeType getMimeType(){return mimeType;}
675 
676 
677   /**
678    * Utility method to get a {@link MimeType} given the type string.
679    */
680   public static MimeType getMimeTypeForString(String typeString) {
681     return mimeString2mimeTypeMap.get(typeString);
682   }
683 
684   /**
685    * Utility method to get the set of all file suffixes that are registered
686    * with this class.
687    */
688   public static Set<String> getSupportedFileSuffixes() {
689     return Collections.unmodifiableSet(suffixes2mimeTypeMap.keySet());
690   }
691 
692   //StatusReporter Implementation
693 
694 
695   public synchronized void removeStatusListener(StatusListener l) {
696     if (statusListeners != null && statusListeners.contains(l)) {
697       @SuppressWarnings("unchecked")
698       Vector<StatusListener> v = (Vector<StatusListener>statusListeners.clone();
699       v.removeElement(l);
700       statusListeners = v;
701     }
702   }
703   public synchronized void addStatusListener(StatusListener l) {
704     @SuppressWarnings("unchecked")
705     Vector<StatusListener> v = statusListeners == null new Vector<StatusListener>(2(Vector<StatusListener>statusListeners.clone();
706     if (!v.contains(l)) {
707       v.addElement(l);
708       statusListeners = v;
709     }
710   }
711   protected void fireStatusChanged(String e) {
712     if (statusListeners != null) {
713       
714       int count = statusListeners.size();
715       for (int i = 0; i < count; i++) {
716         statusListeners.elementAt(i).statusChanged(e);
717       }
718     }
719   }
720 
721 // class DocumentFormat