1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.activation;
  27 
  28 import java.io.InputStream;
  29 import java.io.IOException;
  30 import java.io.OutputStream;
  31 import java.io.PipedInputStream;
  32 import java.io.PipedOutputStream;
  33 import java.io.OutputStreamWriter;
  34 import java.net.URL;
  35 import java.awt.datatransfer.Transferable;
  36 import java.awt.datatransfer.DataFlavor;
  37 import java.awt.datatransfer.UnsupportedFlavorException;
  38 
  39 /**
  40  * The DataHandler class provides a consistent interface to data
  41  * available in many different sources and formats.
  42  * It manages simple stream to string conversions and related operations
  43  * using DataContentHandlers.
  44  * It provides access to commands that can operate on the data.
  45  * The commands are found using a CommandMap. <p>
  46  *
  47  * <b>DataHandler and the Transferable Interface</b><p>
  48  * DataHandler implements the Transferable interface so that data can
  49  * be used in AWT data transfer operations, such as cut and paste and
  50  * drag and drop. The implementation of the Transferable interface
  51  * relies on the availability of an installed DataContentHandler
  52  * object corresponding to the MIME type of the data represented in
  53  * the specific instance of the DataHandler.<p>
  54  *
  55  * <b>DataHandler and CommandMaps</b><p>
  56  * The DataHandler keeps track of the current CommandMap that it uses to
  57  * service requests for commands ({@code getCommand, getAllCommands,
  58  * getPreferredCommands}).
  59  * Each instance of a DataHandler may have a CommandMap associated with
  60  * it using the {@code setCommandMap} method.  If a CommandMap was
  61  * not set, DataHandler calls the {@code getDefaultCommandMap}
  62  * method in CommandMap and uses the value it returns. See
  63  * <i>CommandMap</i> for more information. <p>
  64  *
  65  * <b>DataHandler and URLs</b><p>
  66  * The current DataHandler implementation creates a private
  67  * instance of URLDataSource when it is constructed with a URL.
  68  *
  69  * @see javax.activation.CommandMap
  70  * @see javax.activation.DataContentHandler
  71  * @see javax.activation.DataSource
  72  * @see javax.activation.URLDataSource
  73  *
  74  * @since 1.6
  75  */
  76 
  77 public class DataHandler implements Transferable {
  78 
  79     // Use the datasource to indicate whether we were started via the
  80     // DataSource constructor or the object constructor.
  81     private DataSource dataSource = null;
  82     private DataSource objDataSource = null;
  83 
  84     // The Object and mimetype from the constructor (if passed in).
  85     // object remains null if it was instantiated with a
  86     // DataSource.
  87     private Object object = null;
  88     private String objectMimeType = null;
  89 
  90     // Keep track of the CommandMap
  91     private CommandMap currentCommandMap = null;
  92 
  93     // our transfer flavors
  94     private static final DataFlavor emptyFlavors[] = new DataFlavor[0];
  95     private DataFlavor transferFlavors[] = emptyFlavors;
  96 
  97     // our DataContentHandler
  98     private DataContentHandler dataContentHandler = null;
  99     private DataContentHandler factoryDCH = null;
 100 
 101     // our DataContentHandlerFactory
 102     private static DataContentHandlerFactory factory = null;
 103     private DataContentHandlerFactory oldFactory = null;
 104     // the short representation of the ContentType (sans params)
 105     private String shortType = null;
 106 
 107     /**
 108      * Create a {@code DataHandler} instance referencing the
 109      * specified DataSource.  The data exists in a byte stream form.
 110      * The DataSource will provide an InputStream to access the data.
 111      *
 112      * @param ds        the DataSource
 113      */
 114     public DataHandler(DataSource ds) {
 115         // save a reference to the incoming DS
 116         dataSource = ds;
 117         oldFactory = factory; // keep track of the factory
 118     }
 119 
 120     /**
 121      * Create a {@code DataHandler} instance representing an object
 122      * of this MIME type.  This constructor is
 123      * used when the application already has an in-memory representation
 124      * of the data in the form of a Java Object.
 125      *
 126      * @param obj       the Java Object
 127      * @param mimeType  the MIME type of the object
 128      */
 129     public DataHandler(Object obj, String mimeType) {
 130         object = obj;
 131         objectMimeType = mimeType;
 132         oldFactory = factory; // keep track of the factory
 133     }
 134 
 135     /**
 136      * Create a {@code DataHandler} instance referencing a URL.
 137      * The DataHandler internally creates a {@code URLDataSource}
 138      * instance to represent the URL.
 139      *
 140      * @param url       a URL object
 141      */
 142     public DataHandler(URL url) {
 143         dataSource = new URLDataSource(url);
 144         oldFactory = factory; // keep track of the factory
 145     }
 146 
 147     /**
 148      * Return the CommandMap for this instance of DataHandler.
 149      */
 150     private synchronized CommandMap getCommandMap() {
 151         if (currentCommandMap != null)
 152             return currentCommandMap;
 153         else
 154             return CommandMap.getDefaultCommandMap();
 155     }
 156 
 157     /**
 158      * Return the DataSource associated with this instance
 159      * of DataHandler.
 160      * <p>
 161      * For DataHandlers that have been instantiated with a DataSource,
 162      * this method returns the DataSource that was used to create the
 163      * DataHandler object. In other cases the DataHandler
 164      * constructs a DataSource from the data used to construct
 165      * the DataHandler. DataSources created for DataHandlers <b>not</b>
 166      * instantiated with a DataSource are cached for performance
 167      * reasons.
 168      *
 169      * @return  a valid DataSource object for this DataHandler
 170      */
 171     public DataSource getDataSource() {
 172         if (dataSource == null) {
 173             // create one on the fly
 174             if (objDataSource == null)
 175                 objDataSource = new DataHandlerDataSource(this);
 176             return objDataSource;
 177         }
 178         return dataSource;
 179     }
 180 
 181     /**
 182      * Return the name of the data object. If this DataHandler
 183      * was created with a DataSource, this method calls through
 184      * to the {@code DataSource.getName} method, otherwise it
 185      * returns <i>null</i>.
 186      *
 187      * @return  the name of the object
 188      */
 189     public String getName() {
 190         if (dataSource != null)
 191             return dataSource.getName();
 192         else
 193             return null;
 194     }
 195 
 196     /**
 197      * Return the MIME type of this object as retrieved from
 198      * the source object. Note that this is the <i>full</i>
 199      * type with parameters.
 200      *
 201      * @return  the MIME type
 202      */
 203     public String getContentType() {
 204         if (dataSource != null) // data source case
 205             return dataSource.getContentType();
 206         else
 207             return objectMimeType; // obj/type case
 208     }
 209 
 210     /**
 211      * Get the InputStream for this object. <p>
 212      *
 213      * For DataHandlers instantiated with a DataSource, the DataHandler
 214      * calls the {@code DataSource.getInputStream} method and
 215      * returns the result to the caller.
 216      * <p>
 217      * For DataHandlers instantiated with an Object, the DataHandler
 218      * first attempts to find a DataContentHandler for the Object. If
 219      * the DataHandler can not find a DataContentHandler for this MIME
 220      * type, it throws an UnsupportedDataTypeException.  If it is
 221      * successful, it creates a pipe and a thread.  The thread uses the
 222      * DataContentHandler's {@code writeTo} method to write the
 223      * stream data into one end of the pipe.  The other end of the pipe
 224      * is returned to the caller.  Because a thread is created to copy
 225      * the data, IOExceptions that may occur during the copy can not be
 226      * propagated back to the caller. The result is an empty stream.
 227      *
 228      * @return  the InputStream representing this data
 229      * @exception IOException   if an I/O error occurs
 230      *
 231      * @see javax.activation.DataContentHandler#writeTo
 232      * @see javax.activation.UnsupportedDataTypeException
 233      */
 234     public InputStream getInputStream() throws IOException {
 235         InputStream ins = null;
 236 
 237         if (dataSource != null) {
 238             ins = dataSource.getInputStream();
 239         } else {
 240             DataContentHandler dch = getDataContentHandler();
 241             // we won't even try if we can't get a dch
 242             if (dch == null)
 243                 throw new UnsupportedDataTypeException(
 244                                 "no DCH for MIME type " + getBaseType());
 245 
 246             if (dch instanceof ObjectDataContentHandler) {
 247                 if (((ObjectDataContentHandler)dch).getDCH() == null)
 248                     throw new UnsupportedDataTypeException(
 249                                 "no object DCH for MIME type " + getBaseType());
 250             }
 251             // there is none but the default^^^^^^^^^^^^^^^^
 252             final DataContentHandler fdch = dch;
 253 
 254             // from bill s.
 255             // ce n'est pas une pipe!
 256             //
 257             // NOTE: This block of code needs to throw exceptions, but
 258             // can't because it is in another thread!!! ARG!
 259             //
 260             final PipedOutputStream pos = new PipedOutputStream();
 261             PipedInputStream pin = new PipedInputStream(pos);
 262             new Thread(
 263                        new Runnable() {
 264                 public void run() {
 265                     try {
 266                         fdch.writeTo(object, objectMimeType, pos);
 267                     } catch (IOException e) {
 268 
 269                     } finally {
 270                         try {
 271                             pos.close();
 272                         } catch (IOException ie) { }
 273                     }
 274                 }
 275             },
 276                       "DataHandler.getInputStream").start();
 277             ins = pin;
 278         }
 279 
 280         return ins;
 281     }
 282 
 283     /**
 284      * Write the data to an {@code OutputStream}.<p>
 285      *
 286      * If the DataHandler was created with a DataSource, writeTo
 287      * retrieves the InputStream and copies the bytes from the
 288      * InputStream to the OutputStream passed in.
 289      * <p>
 290      * If the DataHandler was created with an object, writeTo
 291      * retrieves the DataContentHandler for the object's type.
 292      * If the DataContentHandler was found, it calls the
 293      * {@code writeTo} method on the {@code DataContentHandler}.
 294      *
 295      * @param os        the OutputStream to write to
 296      * @exception IOException   if an I/O error occurs
 297      */
 298     public void writeTo(OutputStream os) throws IOException {
 299         // for the DataSource case
 300         if (dataSource != null) {
 301             InputStream is = null;
 302             byte data[] = new byte[8*1024];
 303             int bytes_read;
 304 
 305             is = dataSource.getInputStream();
 306 
 307             try {
 308                 while ((bytes_read = is.read(data)) > 0) {
 309                     os.write(data, 0, bytes_read);
 310                 }
 311             } finally {
 312                 is.close();
 313                 is = null;
 314             }
 315         } else { // for the Object case
 316             DataContentHandler dch = getDataContentHandler();
 317             dch.writeTo(object, objectMimeType, os);
 318         }
 319     }
 320 
 321     /**
 322      * Get an OutputStream for this DataHandler to allow overwriting
 323      * the underlying data.
 324      * If the DataHandler was created with a DataSource, the
 325      * DataSource's {@code getOutputStream} method is called.
 326      * Otherwise, {@code null} is returned.
 327      *
 328      * @return the OutputStream
 329      *
 330      * @see javax.activation.DataSource#getOutputStream
 331      * @see javax.activation.URLDataSource
 332      */
 333     public OutputStream getOutputStream() throws IOException {
 334         if (dataSource != null)
 335             return dataSource.getOutputStream();
 336         else
 337             return null;
 338     }
 339 
 340     /**
 341      * Return the DataFlavors in which this data is available. <p>
 342      *
 343      * Returns an array of DataFlavor objects indicating the flavors
 344      * the data can be provided in. The array is usually ordered
 345      * according to preference for providing the data, from most
 346      * richly descriptive to least richly descriptive.<p>
 347      *
 348      * The DataHandler attempts to find a DataContentHandler that
 349      * corresponds to the MIME type of the data. If one is located,
 350      * the DataHandler calls the DataContentHandler's
 351      * {@code getTransferDataFlavors} method. <p>
 352      *
 353      * If a DataContentHandler can <i>not</i> be located, and if the
 354      * DataHandler was created with a DataSource (or URL), one
 355      * DataFlavor is returned that represents this object's MIME type
 356      * and the {@code java.io.InputStream} class.  If the
 357      * DataHandler was created with an object and a MIME type,
 358      * getTransferDataFlavors returns one DataFlavor that represents
 359      * this object's MIME type and the object's class.
 360      *
 361      * @return  an array of data flavors in which this data can be transferred
 362      * @see javax.activation.DataContentHandler#getTransferDataFlavors
 363      */
 364     public synchronized DataFlavor[] getTransferDataFlavors() {
 365         if (factory != oldFactory) // if the factory has changed, clear cache
 366             transferFlavors = emptyFlavors;
 367 
 368         // if it's not set, set it...
 369         if (transferFlavors == emptyFlavors)
 370             transferFlavors = getDataContentHandler().getTransferDataFlavors();
 371 
 372         if (transferFlavors == emptyFlavors)
 373             return transferFlavors;
 374         else
 375             return transferFlavors.clone();
 376 
 377     }
 378 
 379     /**
 380      * Returns whether the specified data flavor is supported
 381      * for this object.<p>
 382      *
 383      * This method iterates through the DataFlavors returned from
 384      * {@code getTransferDataFlavors}, comparing each with
 385      * the specified flavor.
 386      *
 387      * @param flavor    the requested flavor for the data
 388      * @return          true if the data flavor is supported
 389      * @see javax.activation.DataHandler#getTransferDataFlavors
 390      */
 391     public boolean isDataFlavorSupported(DataFlavor flavor) {
 392         DataFlavor[] lFlavors = getTransferDataFlavors();
 393 
 394         for (int i = 0; i < lFlavors.length; i++) {
 395             if (lFlavors[i].equals(flavor))
 396                 return true;
 397         }
 398         return false;
 399     }
 400 
 401     /**
 402      * Returns an object that represents the data to be
 403      * transferred. The class of the object returned is defined by the
 404      * representation class of the data flavor.<p>
 405      *
 406      * <b>For DataHandler's created with DataSources or URLs:</b><p>
 407      *
 408      * The DataHandler attempts to locate a DataContentHandler
 409      * for this MIME type. If one is found, the passed in DataFlavor
 410      * and the type of the data are passed to its {@code getTransferData}
 411      * method. If the DataHandler fails to locate a DataContentHandler
 412      * and the flavor specifies this object's MIME type and the
 413      * {@code java.io.InputStream} class, this object's InputStream
 414      * is returned.
 415      * Otherwise it throws an UnsupportedFlavorException. <p>
 416      *
 417      * <b>For DataHandler's created with Objects:</b><p>
 418      *
 419      * The DataHandler attempts to locate a DataContentHandler
 420      * for this MIME type. If one is found, the passed in DataFlavor
 421      * and the type of the data are passed to its getTransferData
 422      * method. If the DataHandler fails to locate a DataContentHandler
 423      * and the flavor specifies this object's MIME type and its class,
 424      * this DataHandler's referenced object is returned.
 425      * Otherwise it throws an UnsupportedFlavorException.
 426      *
 427      * @param flavor    the requested flavor for the data
 428      * @return          the object
 429      * @exception UnsupportedFlavorException    if the data could not be
 430      *                  converted to the requested flavor
 431      * @exception IOException   if an I/O error occurs
 432      * @see javax.activation.ActivationDataFlavor
 433      */
 434     public Object getTransferData(DataFlavor flavor)
 435                                 throws UnsupportedFlavorException, IOException {
 436         return getDataContentHandler().getTransferData(flavor, dataSource);
 437     }
 438 
 439     /**
 440      * Set the CommandMap for use by this DataHandler.
 441      * Setting it to {@code null} causes the CommandMap to revert
 442      * to the CommandMap returned by the
 443      * {@code CommandMap.getDefaultCommandMap} method.
 444      * Changing the CommandMap, or setting it to {@code null},
 445      * clears out any data cached from the previous CommandMap.
 446      *
 447      * @param commandMap        the CommandMap to use in this DataHandler
 448      *
 449      * @see javax.activation.CommandMap#setDefaultCommandMap
 450      */
 451     public synchronized void setCommandMap(CommandMap commandMap) {
 452         if (commandMap != currentCommandMap || commandMap == null) {
 453             // clear cached values...
 454             transferFlavors = emptyFlavors;
 455             dataContentHandler = null;
 456 
 457             currentCommandMap = commandMap;
 458         }
 459     }
 460 
 461     /**
 462      * Return the <i>preferred</i> commands for this type of data.
 463      * This method calls the {@code getPreferredCommands} method
 464      * in the CommandMap associated with this instance of DataHandler.
 465      * This method returns an array that represents a subset of
 466      * available commands. In cases where multiple commands for the
 467      * MIME type represented by this DataHandler are present, the
 468      * installed CommandMap chooses the appropriate commands.
 469      *
 470      * @return  the CommandInfo objects representing the preferred commands
 471      *
 472      * @see javax.activation.CommandMap#getPreferredCommands
 473      */
 474     public CommandInfo[] getPreferredCommands() {
 475         if (dataSource != null)
 476             return getCommandMap().getPreferredCommands(getBaseType(),
 477                                                         dataSource);
 478         else
 479             return getCommandMap().getPreferredCommands(getBaseType());
 480     }
 481 
 482     /**
 483      * Return all the commands for this type of data.
 484      * This method returns an array containing all commands
 485      * for the type of data represented by this DataHandler. The
 486      * MIME type for the underlying data represented by this DataHandler
 487      * is used to call through to the {@code getAllCommands} method
 488      * of the CommandMap associated with this DataHandler.
 489      *
 490      * @return  the CommandInfo objects representing all the commands
 491      *
 492      * @see javax.activation.CommandMap#getAllCommands
 493      */
 494     public CommandInfo[] getAllCommands() {
 495         if (dataSource != null)
 496             return getCommandMap().getAllCommands(getBaseType(), dataSource);
 497         else
 498             return getCommandMap().getAllCommands(getBaseType());
 499     }
 500 
 501     /**
 502      * Get the command <i>cmdName</i>. Use the search semantics as
 503      * defined by the CommandMap installed in this DataHandler. The
 504      * MIME type for the underlying data represented by this DataHandler
 505      * is used to call through to the {@code getCommand} method
 506      * of the CommandMap associated with this DataHandler.
 507      *
 508      * @param cmdName   the command name
 509      * @return  the CommandInfo corresponding to the command
 510      *
 511      * @see javax.activation.CommandMap#getCommand
 512      */
 513     public CommandInfo getCommand(String cmdName) {
 514         if (dataSource != null)
 515             return getCommandMap().getCommand(getBaseType(), cmdName,
 516                                                                 dataSource);
 517         else
 518             return getCommandMap().getCommand(getBaseType(), cmdName);
 519     }
 520 
 521     /**
 522      * Return the data in its preferred Object form. <p>
 523      *
 524      * If the DataHandler was instantiated with an object, return
 525      * the object. <p>
 526      *
 527      * If the DataHandler was instantiated with a DataSource,
 528      * this method uses a DataContentHandler to return the content
 529      * object for the data represented by this DataHandler. If no
 530      * {@code DataContentHandler} can be found for the
 531      * the type of this data, the DataHandler returns an
 532      * InputStream for the data.
 533      *
 534      * @return the content.
 535      * @exception IOException if an IOException occurs during
 536      *                              this operation.
 537      */
 538     public Object getContent() throws IOException {
 539         if (object != null)
 540             return object;
 541         else
 542             return getDataContentHandler().getContent(getDataSource());
 543     }
 544 
 545     /**
 546      * A convenience method that takes a CommandInfo object
 547      * and instantiates the corresponding command, usually
 548      * a JavaBean component.
 549      * <p>
 550      * This method calls the CommandInfo's {@code getCommandObject}
 551      * method with the {@code ClassLoader} used to load
 552      * the {@code javax.activation.DataHandler} class itself.
 553      *
 554      * @param cmdinfo   the CommandInfo corresponding to a command
 555      * @return  the instantiated command object
 556      */
 557     public Object getBean(CommandInfo cmdinfo) {
 558         Object bean = null;
 559 
 560         try {
 561             // make the bean
 562             ClassLoader cld = null;
 563             // First try the "application's" class loader.
 564             cld = SecuritySupport.getContextClassLoader();
 565             if (cld == null)
 566                 cld = this.getClass().getClassLoader();
 567             bean = cmdinfo.getCommandObject(this, cld);
 568         } catch (IOException e) {
 569         } catch (ClassNotFoundException e) { }
 570 
 571         return bean;
 572     }
 573 
 574     /**
 575      * Get the DataContentHandler for this DataHandler: <p>
 576      *
 577      * If a DataContentHandlerFactory is set, use it.
 578      * Otherwise look for an object to serve DCH in the
 579      * following order: <p>
 580      *
 581      * 1) if a factory is set, use it <p>
 582      * 2) if a CommandMap is set, use it <p>
 583      * 3) use the default CommandMap <p>
 584      *
 585      * In any case, wrap the real DataContentHandler with one of our own
 586      * to handle any missing cases, fill in defaults, and to ensure that
 587      * we always have a non-null DataContentHandler.
 588      *
 589      * @return  the requested DataContentHandler
 590      */
 591     private synchronized DataContentHandler getDataContentHandler() {
 592 
 593         // make sure the factory didn't change
 594         if (factory != oldFactory) {
 595             oldFactory = factory;
 596             factoryDCH = null;
 597             dataContentHandler = null;
 598             transferFlavors = emptyFlavors;
 599         }
 600 
 601         if (dataContentHandler != null)
 602             return dataContentHandler;
 603 
 604         String simpleMT = getBaseType();
 605 
 606         if (factoryDCH == null && factory != null)
 607             factoryDCH = factory.createDataContentHandler(simpleMT);
 608 
 609         if (factoryDCH != null)
 610             dataContentHandler = factoryDCH;
 611 
 612         if (dataContentHandler == null) {
 613             if (dataSource != null)
 614                 dataContentHandler = getCommandMap().
 615                                 createDataContentHandler(simpleMT, dataSource);
 616             else
 617                 dataContentHandler = getCommandMap().
 618                                 createDataContentHandler(simpleMT);
 619         }
 620 
 621         // getDataContentHandler always uses these 'wrapper' handlers
 622         // to make sure it returns SOMETHING meaningful...
 623         if (dataSource != null)
 624             dataContentHandler = new DataSourceDataContentHandler(
 625                                                       dataContentHandler,
 626                                                       dataSource);
 627         else
 628             dataContentHandler = new ObjectDataContentHandler(
 629                                                       dataContentHandler,
 630                                                       object,
 631                                                       objectMimeType);
 632         return dataContentHandler;
 633     }
 634 
 635     /**
 636      * Use the MimeType class to extract the MIME type/subtype,
 637      * ignoring the parameters.  The type is cached.
 638      */
 639     private synchronized String getBaseType() {
 640         if (shortType == null) {
 641             String ct = getContentType();
 642             try {
 643                 MimeType mt = new MimeType(ct);
 644                 shortType = mt.getBaseType();
 645             } catch (MimeTypeParseException e) {
 646                 shortType = ct;
 647             }
 648         }
 649         return shortType;
 650     }
 651 
 652     /**
 653      * Sets the DataContentHandlerFactory.  The DataContentHandlerFactory
 654      * is called first to find DataContentHandlers.
 655      * The DataContentHandlerFactory can only be set once.
 656      * <p>
 657      * If the DataContentHandlerFactory has already been set,
 658      * this method throws an Error.
 659      *
 660      * @param newFactory        the DataContentHandlerFactory
 661      * @exception Error if the factory has already been defined.
 662      *
 663      * @see javax.activation.DataContentHandlerFactory
 664      */
 665     public static synchronized void setDataContentHandlerFactory(
 666                                          DataContentHandlerFactory newFactory) {
 667         if (factory != null)
 668             throw new Error("DataContentHandlerFactory already defined");
 669 
 670         SecurityManager security = System.getSecurityManager();
 671         if (security != null) {
 672             try {
 673                 // if it's ok with the SecurityManager, it's ok with me...
 674                 security.checkSetFactory();
 675             } catch (SecurityException ex) {
 676                 // otherwise, we also allow it if this code and the
 677                 // factory come from the same class loader (e.g.,
 678                 // the JAF classes were loaded with the applet classes).
 679                 if (DataHandler.class.getClassLoader() !=
 680                         newFactory.getClass().getClassLoader())
 681                     throw ex;
 682             }
 683         }
 684         factory = newFactory;
 685     }
 686 }
 687 
 688 /**
 689  * The DataHanderDataSource class implements the
 690  * DataSource interface when the DataHandler is constructed
 691  * with an Object and a mimeType string.
 692  */
 693 class DataHandlerDataSource implements DataSource {
 694     DataHandler dataHandler = null;
 695 
 696     /**
 697      * The constructor.
 698      */
 699     public DataHandlerDataSource(DataHandler dh) {
 700         this.dataHandler = dh;
 701     }
 702 
 703     /**
 704      * Returns an {@code InputStream} representing this object.
 705      * @return  the {@code InputStream}
 706      */
 707     public InputStream getInputStream() throws IOException {
 708         return dataHandler.getInputStream();
 709     }
 710 
 711     /**
 712      * Returns the {@code OutputStream} for this object.
 713      * @return  the {@code OutputStream}
 714      */
 715     public OutputStream getOutputStream() throws IOException {
 716         return dataHandler.getOutputStream();
 717     }
 718 
 719     /**
 720      * Returns the MIME type of the data represented by this object.
 721      * @return  the MIME type
 722      */
 723     public String getContentType() {
 724         return dataHandler.getContentType();
 725     }
 726 
 727     /**
 728      * Returns the name of this object.
 729      * @return  the name of this object
 730      */
 731     public String getName() {
 732         return dataHandler.getName(); // what else would it be?
 733     }
 734 }
 735 
 736 /*
 737  * DataSourceDataContentHandler
 738  *
 739  * This is a <i>private</i> DataContentHandler that wraps the real
 740  * DataContentHandler in the case where the DataHandler was instantiated
 741  * with a DataSource.
 742  */
 743 class DataSourceDataContentHandler implements DataContentHandler {
 744     private DataSource ds = null;
 745     private DataFlavor transferFlavors[] = null;
 746     private DataContentHandler dch = null;
 747 
 748     /**
 749      * The constructor.
 750      */
 751     public DataSourceDataContentHandler(DataContentHandler dch, DataSource ds) {
 752         this.ds = ds;
 753         this.dch = dch;
 754     }
 755 
 756     /**
 757      * Return the DataFlavors for this {@code DataContentHandler}.
 758      * @return  the DataFlavors
 759      */
 760     public DataFlavor[] getTransferDataFlavors() {
 761 
 762         if (transferFlavors == null) {
 763             if (dch != null) { // is there a dch?
 764                 transferFlavors = dch.getTransferDataFlavors();
 765             } else {
 766                 transferFlavors = new DataFlavor[1];
 767                 transferFlavors[0] =
 768                     new ActivationDataFlavor(ds.getContentType(),
 769                                              ds.getContentType());
 770             }
 771         }
 772         return transferFlavors;
 773     }
 774 
 775     /**
 776      * Return the Transfer Data of type DataFlavor from InputStream.
 777      * @param df        the DataFlavor
 778      * @param ds        the DataSource
 779      * @return          the constructed Object
 780      */
 781     public Object getTransferData(DataFlavor df, DataSource ds) throws
 782                                 UnsupportedFlavorException, IOException {
 783 
 784         if (dch != null)
 785             return dch.getTransferData(df, ds);
 786         else if (df.equals(getTransferDataFlavors()[0])) // only have one now
 787             return ds.getInputStream();
 788         else
 789             throw new UnsupportedFlavorException(df);
 790     }
 791 
 792     public Object getContent(DataSource ds) throws IOException {
 793 
 794         if (dch != null)
 795             return dch.getContent(ds);
 796         else
 797             return ds.getInputStream();
 798     }
 799 
 800     /**
 801      * Write the object to the output stream.
 802      */
 803     public void writeTo(Object obj, String mimeType, OutputStream os)
 804                                                 throws IOException {
 805         if (dch != null)
 806             dch.writeTo(obj, mimeType, os);
 807         else
 808             throw new UnsupportedDataTypeException(
 809                         "no DCH for content type " + ds.getContentType());
 810     }
 811 }
 812 
 813 /*
 814  * ObjectDataContentHandler
 815  *
 816  * This is a <i>private</i> DataContentHandler that wraps the real
 817  * DataContentHandler in the case where the DataHandler was instantiated
 818  * with an object.
 819  */
 820 class ObjectDataContentHandler implements DataContentHandler {
 821     private DataFlavor transferFlavors[] = null;
 822     private Object obj;
 823     private String mimeType;
 824     private DataContentHandler dch = null;
 825 
 826     /**
 827      * The constructor.
 828      */
 829     public ObjectDataContentHandler(DataContentHandler dch,
 830                                     Object obj, String mimeType) {
 831         this.obj = obj;
 832         this.mimeType = mimeType;
 833         this.dch = dch;
 834     }
 835 
 836     /**
 837      * Return the DataContentHandler for this object.
 838      * Used only by the DataHandler class.
 839      */
 840     public DataContentHandler getDCH() {
 841         return dch;
 842     }
 843 
 844     /**
 845      * Return the DataFlavors for this {@code DataContentHandler}.
 846      * @return  the DataFlavors
 847      */
 848     public synchronized DataFlavor[] getTransferDataFlavors() {
 849         if (transferFlavors == null) {
 850             if (dch != null) {
 851                 transferFlavors = dch.getTransferDataFlavors();
 852             } else {
 853                 transferFlavors = new DataFlavor[1];
 854                 transferFlavors[0] = new ActivationDataFlavor(obj.getClass(),
 855                                              mimeType, mimeType);
 856             }
 857         }
 858         return transferFlavors;
 859     }
 860 
 861     /**
 862      * Return the Transfer Data of type DataFlavor from InputStream.
 863      * @param df        the DataFlavor
 864      * @param ds        the DataSource
 865      * @return          the constructed Object
 866      */
 867     public Object getTransferData(DataFlavor df, DataSource ds)
 868                                 throws UnsupportedFlavorException, IOException {
 869 
 870         if (dch != null)
 871             return dch.getTransferData(df, ds);
 872         else if (df.equals(getTransferDataFlavors()[0])) // only have one now
 873             return obj;
 874         else
 875             throw new UnsupportedFlavorException(df);
 876 
 877     }
 878 
 879     public Object getContent(DataSource ds) {
 880         return obj;
 881     }
 882 
 883     /**
 884      * Write the object to the output stream.
 885      */
 886     public void writeTo(Object obj, String mimeType, OutputStream os)
 887                                                 throws IOException {
 888         if (dch != null)
 889             dch.writeTo(obj, mimeType, os);
 890         else if (obj instanceof byte[])
 891             os.write((byte[])obj);
 892         else if (obj instanceof String) {
 893             OutputStreamWriter osw = new OutputStreamWriter(os);
 894             osw.write((String)obj);
 895             osw.flush();
 896         } else throw new UnsupportedDataTypeException(
 897                 "no object DCH for MIME type " + this.mimeType);
 898     }
 899 }