001    /* DataHandler.java -- Handler for data available in multiple formats.
002       Copyright (C) 2004 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package javax.activation;
039    
040    import java.awt.datatransfer.DataFlavor;
041    import java.awt.datatransfer.Transferable;
042    import java.awt.datatransfer.UnsupportedFlavorException;
043    import java.io.InputStream;
044    import java.io.IOException;
045    import java.io.OutputStream;
046    import java.io.PipedInputStream;
047    import java.io.PipedOutputStream;
048    import java.net.URL;
049    
050    /**
051     * Handler for data available in multiple sources and formats.
052     *
053     * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
054     * @version 1.1
055     */
056    public class DataHandler
057      implements Transferable
058    {
059    
060      private static final DataFlavor[] NO_FLAVORS = new DataFlavor[0];
061      private static DataContentHandlerFactory factory = null;
062    
063      private final DataSource dataSource;
064      private DataSource objDataSource;
065      private Object object;
066      private String objectMimeType;
067      private CommandMap currentCommandMap;
068      private DataFlavor[] transferFlavors = NO_FLAVORS;
069      private DataContentHandler dataContentHandler;
070      private DataContentHandler factoryDCH;
071      private DataContentHandlerFactory oldFactory;
072      private String shortType;
073    
074      /**
075       * Constructor in which the data is read from a data source.
076       * @param ds the data source
077       */
078      public DataHandler(DataSource ds)
079      {
080        dataSource = ds;
081        oldFactory = factory;
082      }
083    
084      /**
085       * Constructor using a reified object representation.
086       * @param obj the object representation of the data
087       * @param mimeType the MIME type of the object
088       */
089      public DataHandler(Object obj, String mimeType)
090      {
091        dataSource = null;
092        object = obj;
093        objectMimeType = mimeType;
094        oldFactory = factory;
095      }
096    
097      /**
098       * Constructor in which the data is read from a URL.
099       * @param url the URL
100       */
101      public DataHandler(URL url)
102      {
103        dataSource = new URLDataSource(url);
104        oldFactory = factory;
105      }
106    
107      /**
108       * Returns the data source from which data is read.
109       */
110      public DataSource getDataSource()
111      {
112        if (dataSource != null)
113          {
114            return dataSource;
115          }
116        if (objDataSource == null)
117          {
118            objDataSource = new DataHandlerDataSource(this);
119          }
120        return objDataSource;
121      }
122    
123      /**
124       * Returns the name of the data object if created with a DataSource.
125       */
126      public String getName()
127      {
128        if (dataSource != null)
129          {
130            return dataSource.getName();
131          }
132        return null;
133      }
134    
135      /**
136       * Returns the MIME type of the data (with parameters).
137       */
138      public String getContentType()
139      {
140        if (dataSource != null)
141          {
142            return dataSource.getContentType();
143          }
144        return objectMimeType;
145      }
146    
147      /**
148       * Returns an input stream from which the data can be read.
149       */
150      public InputStream getInputStream()
151        throws IOException
152      {
153        if (dataSource != null)
154          {
155            return dataSource.getInputStream();
156          }
157        DataContentHandler dch = getDataContentHandler();
158        if (dch == null)
159          {
160            throw new UnsupportedDataTypeException("no DCH for MIME type " +
161                                                   getShortType());
162          }
163        if ((dch instanceof ObjectDataContentHandler) &&
164            ((ObjectDataContentHandler)dch).getDCH() == null)
165          {
166            throw new UnsupportedDataTypeException("no object DCH " +
167                                                   "for MIME type " +
168                                                   getShortType());
169          }
170        PipedOutputStream pos = new PipedOutputStream();
171        DataContentHandlerWriter dchw =
172          new DataContentHandlerWriter(dch, object, objectMimeType, pos);
173        Thread thread = new Thread(dchw, "DataHandler.getInputStream");
174        thread.start();
175        return new PipedInputStream(pos);
176      }
177    
178      static class DataContentHandlerWriter
179        implements Runnable
180      {
181    
182        DataContentHandler dch;
183        Object object;
184        String mimeType;
185        OutputStream out;
186    
187        DataContentHandlerWriter(DataContentHandler dch, Object object,
188                                 String mimeType, OutputStream out)
189        {
190          this.dch = dch;
191          this.object = object;
192          this.mimeType = mimeType;
193          this.out = out;
194        }
195    
196        public void run()
197        {
198          try
199            {
200              dch.writeTo(object, mimeType, out);
201            }
202          catch(IOException e)
203            {
204            }
205          finally
206            {
207              try
208                {
209                  out.close();
210                }
211              catch(IOException e)
212                {
213                }
214            }
215        }
216      }
217    
218      /**
219       * Writes the data as a byte stream.
220       * @param os the stream to write to
221       */
222      public void writeTo(OutputStream os)
223        throws IOException
224      {
225        if (dataSource != null)
226          {
227            InputStream in = dataSource.getInputStream();
228            byte[] buf = new byte[8192];
229            for (int len = in.read(buf); len != -1; len = in.read(buf))
230              {
231                os.write(buf, 0, len);
232              }
233            in.close();
234          }
235        else
236          {
237            DataContentHandler dch = getDataContentHandler();
238            dch.writeTo(object, objectMimeType, os);
239          }
240      }
241    
242      /**
243       * Returns an output stream that can be used to overwrite the underlying
244       * data, if the DataSource constructor was used.
245       */
246      public OutputStream getOutputStream()
247        throws IOException
248      {
249        if (dataSource != null)
250          {
251            return dataSource.getOutputStream();
252          }
253        return null;
254      }
255    
256      /**
257       * Returns the data flavors in which this data is available.
258       */
259      public synchronized DataFlavor[] getTransferDataFlavors()
260      {
261        if (factory != oldFactory || transferFlavors == NO_FLAVORS)
262          {
263            DataContentHandler dch = getDataContentHandler();
264            transferFlavors = dch.getTransferDataFlavors();
265          }
266        return transferFlavors;
267      }
268    
269      /**
270       * Indicates whether the specified data flavor is supported for this
271       * data.
272       */
273      public boolean isDataFlavorSupported(DataFlavor flavor)
274      {
275        DataFlavor[] flavors = getTransferDataFlavors();
276        for (int i = 0; i < flavors.length; i++)
277          {
278            if (flavors[i].equals(flavor))
279              {
280                return true;
281              }
282          }
283        return false;
284      }
285    
286      /**
287       * Returns an object representing the data to be transferred.
288       * @param flavor the requested data flavor
289       */
290      public Object getTransferData(DataFlavor flavor)
291        throws UnsupportedFlavorException, IOException
292      {
293        DataContentHandler dch = getDataContentHandler();
294        return dch.getTransferData(flavor, dataSource);
295      }
296    
297      /**
298       * Sets the command map to be used by this data handler.
299       * Setting to null uses the default command map.
300       * @param commandMap the command map to use
301       */
302      public synchronized void setCommandMap(CommandMap commandMap)
303      {
304        if (commandMap != currentCommandMap || commandMap == null)
305          {
306            transferFlavors = NO_FLAVORS;
307            dataContentHandler = null;
308            currentCommandMap = commandMap;
309          }
310      }
311    
312      /**
313       * Returns the preferred commands for this type of data.
314       */
315      public CommandInfo[] getPreferredCommands()
316      {
317        CommandMap commandMap = getCommandMap();
318        return commandMap.getPreferredCommands(getShortType());
319      }
320    
321      /**
322       * Returns the complete list of commands for this type of data.
323       */
324      public CommandInfo[] getAllCommands()
325      {
326        CommandMap commandMap = getCommandMap();
327        return commandMap.getAllCommands(getShortType());
328      }
329    
330      /**
331       * Returns the specified command.
332       * @param cmdName the command name
333       */
334      public CommandInfo getCommand(String cmdName)
335      {
336        CommandMap commandMap = getCommandMap();
337        return commandMap.getCommand(getShortType(), cmdName);
338      }
339    
340      /**
341       * Returns the data as a reified object.
342       */
343      public Object getContent()
344        throws IOException
345      {
346        DataContentHandler dch = getDataContentHandler();
347        return dch.getContent(getDataSource());
348      }
349    
350      /**
351       * Returns the instantiated bean using the specified command.
352       * @param cmdInfo the command to instantiate the bean with
353       */
354      public Object getBean(CommandInfo cmdInfo)
355      {
356        try
357          {
358            return cmdInfo.getCommandObject(this, getClass().getClassLoader());
359          }
360        catch (IOException e)
361          {
362            e.printStackTrace(System.err);
363            return null;
364          }
365        catch (ClassNotFoundException e)
366          {
367            e.printStackTrace(System.err);
368            return null;
369          }
370      }
371    
372      /**
373       * Sets the data content handler factory.
374       * If the factory has already been set, throws an Error.
375       * @param newFactory the factory to set
376       */
377      public static synchronized void
378        setDataContentHandlerFactory(DataContentHandlerFactory newFactory)
379      {
380        if (factory != null)
381          {
382            throw new Error("DataContentHandlerFactory already defined");
383          }
384        SecurityManager security = System.getSecurityManager();
385        if (security != null)
386          {
387            try
388              {
389                security.checkSetFactory();
390              }
391            catch (SecurityException e)
392              {
393                if (newFactory != null && DataHandler.class.getClassLoader()
394                    != newFactory.getClass().getClassLoader())
395                  {
396                    throw e;
397                  }
398              }
399          }
400        factory = newFactory;
401      }
402    
403      /*
404       * Returns just the base part of the data's content-type, with no
405       * parameters.
406       */
407      private synchronized String getShortType()
408      {
409        if (shortType == null)
410          {
411            String contentType = getContentType();
412            try
413              {
414                MimeType mimeType = new MimeType(contentType);
415                shortType = mimeType.getBaseType();
416              }
417            catch (MimeTypeParseException e)
418              {
419                shortType = contentType;
420              }
421          }
422        return shortType;
423      }
424    
425      /*
426       * Returns the command map for this handler.
427       */
428      private synchronized CommandMap getCommandMap()
429      {
430        if (currentCommandMap != null)
431          {
432            return currentCommandMap;
433          }
434        return CommandMap.getDefaultCommandMap();
435      }
436    
437      /*
438       * Returns the DCH for this handler.
439       */
440      private synchronized DataContentHandler getDataContentHandler()
441      {
442        if (factory != oldFactory)
443          {
444            oldFactory = factory;
445            factoryDCH = null;
446            dataContentHandler = null;
447            transferFlavors = NO_FLAVORS;
448          }
449        if (dataContentHandler != null)
450          {
451            return dataContentHandler;
452          }
453        String mimeType = getShortType();
454        if (factoryDCH == null && factory != null)
455          {
456            factoryDCH = factory.createDataContentHandler(mimeType);
457          }
458        if (factoryDCH != null)
459          {
460            dataContentHandler = factoryDCH;
461          }
462        if (dataContentHandler == null)
463          {
464            CommandMap commandMap = getCommandMap();
465            dataContentHandler = commandMap.createDataContentHandler(mimeType);
466          }
467        if (dataSource != null)
468          {
469            dataContentHandler =
470              new DataSourceDataContentHandler(dataContentHandler, dataSource);
471          }
472        else
473          {
474            dataContentHandler =
475              new ObjectDataContentHandler(dataContentHandler, object,
476                                           objectMimeType);
477          }
478        return dataContentHandler;
479      }
480    
481    }