001/* MimeTypesFileTypeMap.java -- A file type map using mime.types.
002   Copyright (C) 2004 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038package javax.activation;
039
040import gnu.java.lang.CPStringBuilder;
041
042import java.io.BufferedReader;
043import java.io.File;
044import java.io.FileReader;
045import java.io.InputStream;
046import java.io.InputStreamReader;
047import java.io.IOException;
048import java.io.Reader;
049import java.io.StringReader;
050import java.net.URL;
051import java.util.ArrayList;
052import java.util.Enumeration;
053import java.util.HashMap;
054import java.util.Iterator;
055import java.util.List;
056import java.util.Map;
057
058/**
059 * Implementation of FileTypeMap that uses the <tt>mime.types</tt> format.
060 * File entries are searched for in the following locations and order:
061 * <ol>
062 * <li>Programmatically added entries to this instance</li>
063 * <li>The file <tt>.mime.types</tt> in the user's home directory</li>
064 * <li>The file <i>&lt;java.home&gt;</i><tt>/lib/mime.types</tt></li>
065 * <li>The resource <tt>META-INF/mime.types</tt></li>
066 * <li>The resource <tt>META-INF/mimetypes.default</tt> in the JAF
067 * distribution</li>
068 * </ol>
069 *
070 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
071 * @version 1.1
072 */
073public class MimetypesFileTypeMap
074  extends FileTypeMap
075{
076
077  private static final int PROG = 0;
078  private static final int HOME = 1;
079  private static final int SYS = 2;
080  private static final int JAR = 3;
081  private static final int DEF = 4;
082  private static final String DEFAULT_MIME_TYPE = "application/octet-stream";
083  private static boolean debug = false;
084  static
085  {
086    try
087      {
088        String d = System.getProperty("javax.activation.debug");
089        debug = Boolean.valueOf(d).booleanValue();
090      }
091    catch (SecurityException e)
092      {
093      }
094  }
095  
096  private Map<String,String>[] mimetypes;
097  
098  /**
099   * Default constructor.
100   */
101  public MimetypesFileTypeMap()
102  {
103    init(null);
104  }
105  
106  /**
107   * Constructor specifying a filename.
108   * @param mimeTypeFileName the name of the file to read mime.types
109   * entries from
110   */
111  public MimetypesFileTypeMap(String mimeTypeFileName)
112    throws IOException
113  {
114    Reader in = null;
115    try
116      {
117        in = new FileReader(mimeTypeFileName);
118        init(in);
119      }
120    finally
121      {
122        if (in != null)
123          {
124            in.close();
125          }
126      }
127  }
128  
129  /**
130   * Constructor specifying an input stream.
131   * @param is the input stream to read mime.types entries from
132   */
133  public MimetypesFileTypeMap(InputStream is)
134  {
135    init(new InputStreamReader(is));
136  }
137
138  private void init(Reader in)
139  {
140    mimetypes = new Map[5];
141    for (int i = 0; i < mimetypes.length; i++)
142      {
143        mimetypes[i] = new HashMap<String,String>();
144      }
145    if (in != null)
146      {
147        if (debug)
148          {
149            System.out.println("MimetypesFileTypeMap: load PROG");
150          }
151        try
152          {
153            parse(mimetypes[PROG], in);
154          }
155        catch (IOException e)
156          {
157          }
158      }
159    
160    if (debug)
161      {
162        System.out.println("MimetypesFileTypeMap: load HOME");
163      }
164    try
165      {
166        String home = System.getProperty("user.home");
167        if (home != null)
168          {
169            parseFile(mimetypes[HOME], new CPStringBuilder(home)
170                      .append(File.separatorChar)
171                      .append(".mime.types")
172                      .toString());
173          }
174      }
175    catch (SecurityException e)
176      {
177      }
178    
179    if (debug)
180      {
181        System.out.println("MimetypesFileTypeMap: load SYS");
182      }
183    try
184      {
185        parseFile(mimetypes[SYS],
186                  new CPStringBuilder(System.getProperty("java.home"))
187                  .append(File.separatorChar)                                                     
188                  .append("lib")
189                  .append(File.separatorChar)
190                  .append("mime.types")
191                  .toString());
192      }
193    catch (SecurityException e)
194      {
195      }
196    if (debug)
197      {
198        System.out.println("MimetypesFileTypeMap: load JAR");
199      }
200    List<URL> systemResources = getSystemResources("META-INF/mime.types");
201    int len = systemResources.size();
202    if (len > 0)
203      {
204        for (int i = 0; i < len ; i++)
205          {
206            Reader urlIn = null;
207            URL url = (URL)systemResources.get(i);
208            try
209              {
210                urlIn = new InputStreamReader(url.openStream());
211                parse(mimetypes[JAR], urlIn);
212              }
213            catch (IOException e)
214              {
215              }
216            finally
217              {
218                if (urlIn != null)
219                  {
220                    try
221                      {
222                        urlIn.close();
223                      }
224                    catch (IOException e)
225                      {
226                      }
227                  }
228              }
229          }
230      }
231    else
232      {
233        parseResource(mimetypes[JAR], "/META-INF/mime.types");
234      }
235    
236    if (debug)
237      {
238        System.out.println("MimetypesFileTypeMap: load DEF");
239      }
240    parseResource(mimetypes[DEF], "/META-INF/mimetypes.default");
241  }
242  
243  /**
244   * Adds entries prorammatically to the registry.
245   * @param mime_types a mime.types formatted entries string
246   */
247  public synchronized void addMimeTypes(String mime_types)
248  {
249    if (debug)
250      {
251        System.out.println("MimetypesFileTypeMap: add to PROG");
252      }
253    try
254      {
255        parse(mimetypes[PROG], new StringReader(mime_types));
256      }
257    catch (IOException e)
258      {
259      }
260  }
261
262  /**
263   * Returns the MIME content type of the file.
264   * This calls <code>getContentType(f.getName())</code>.
265   * @param f the file
266   */
267  public String getContentType(File f)
268  {
269    return getContentType(f.getName());
270  }
271  
272  /**
273   * Returns the MIME type based on the given filename.
274   * If no entry is found, returns "application/octet-stream".
275   * @param filename the filename
276   */
277  public synchronized String getContentType(String filename)
278  {
279    int di = filename.lastIndexOf('.');
280    if (di < 0)
281      {
282        return DEFAULT_MIME_TYPE;
283      }
284    String tail = filename.substring(di + 1);
285    if (tail.length() < 1)
286      {
287        return DEFAULT_MIME_TYPE;
288      }
289    for (int i = 0; i < mimetypes.length; i++)
290      {
291        String mimeType = (String)mimetypes[i].get(tail);
292        if (mimeType != null)
293          {
294            return mimeType;
295          }
296      }
297    return DEFAULT_MIME_TYPE;
298  }
299  
300  private void parseFile(Map<String,String> mimetypes, String filename)
301  {
302    Reader in = null;
303    try
304      {
305        in = new FileReader(filename);
306        parse(mimetypes, in);
307      }
308    catch (IOException e)
309      {
310      }
311    finally
312      {
313        if (in != null)
314          {
315            try
316              {
317                in.close();
318              }
319            catch (IOException e)
320              {
321              }
322          }
323      }
324  }
325  
326  private void parseResource(Map<String,String> mimetypes, String name)
327  {
328    Reader in = null;
329    try
330      {
331        InputStream is = getClass().getResourceAsStream(name);
332        if (is != null)
333          {
334            in = new InputStreamReader(is);
335            parse(mimetypes, in);
336          }
337      }
338    catch (IOException e)
339      {
340      }
341    finally
342      {
343        if (in != null)
344          {
345            try
346              {
347                in.close();
348              }
349            catch (IOException e)
350              {
351              }
352          }
353      }
354  }
355  
356  private void parse(Map<String,String> mimetypes, Reader in)
357    throws IOException
358  {
359    BufferedReader br = new BufferedReader(in);
360    CPStringBuilder buf = null;
361    for (String line = br.readLine(); line != null; line = br.readLine())
362      {
363        line = line.trim();
364        int len = line.length();
365        if (len == 0 || line.charAt(0) == '#')
366          {
367            continue; // Empty line / comment
368          }
369        if (line.charAt(len - 1) == '\\')
370          {
371            if (buf == null)
372              {
373                buf = new CPStringBuilder();
374              }
375            buf.append(line.substring(0, len - 1));
376          }
377        else if (buf != null)
378          {
379            buf.append(line);
380            parseEntry(mimetypes, buf.toString());
381            buf = null;
382          }
383        else
384          {
385            parseEntry(mimetypes, line);
386          }
387      }
388  }
389  
390  private void parseEntry(Map<String,String> mimetypes,
391                          String line)
392  {
393    // Tokenize
394    String mimeType = null;
395    char[] chars = line.toCharArray();
396    int len = chars.length;
397    CPStringBuilder buffer = new CPStringBuilder();
398    for (int i = 0; i < len; i++)
399      {
400        char c = chars[i];
401        if (Character.isWhitespace(c))
402          {
403            if (mimeType == null)
404              {
405                mimeType = buffer.toString();
406              }
407            else if (buffer.length() > 0)
408              {
409                mimetypes.put(buffer.toString(), mimeType);
410              }
411            buffer.setLength(0);
412          }
413        else
414          buffer.append(c);
415      }
416    if (buffer.length() > 0)
417      {
418        mimetypes.put(buffer.toString(), mimeType);
419      }
420  }
421  
422  // -- Utility methods --
423  
424  private List<URL> getSystemResources(String name)
425  {
426    List<URL> acc = new ArrayList<URL>();
427    try
428      {
429        for (Enumeration<URL> i = ClassLoader.getSystemResources(name);
430             i.hasMoreElements(); )
431          acc.add(i.nextElement());
432      }
433    catch (IOException e)
434      {
435      }
436    return acc;
437  }
438  
439}
440