/*
 * DataHandler.java
 * Copyright (C) 2004 The Free Software Foundation
 * 
 * This file is part of GNU Java Activation Framework (JAF), a library.
 * 
 * GNU JAF is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * GNU JAF is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * As a special exception, if you link this library with other files to
 * produce an executable, this library does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * This exception does not however invalidate any other reasons why the
 * executable file might be covered by the GNU General Public License.
 */
package javax.activation;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.URL;

/**
 * Handler for data available in multiple sources and formats.
 *
 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
 * @version 1.1
 */
public class DataHandler
  implements Transferable
{

  private static final DataFlavor[] NO_FLAVORS = new DataFlavor[0];
  private static DataContentHandlerFactory factory = null;
  
  private DataSource dataSource;
  private DataSource objDataSource;
  private Object object;
  private String objectMimeType;
  private CommandMap currentCommandMap;
  private DataFlavor[] transferFlavors = NO_FLAVORS;
  private DataContentHandler dataContentHandler;
  private DataContentHandler factoryDCH;
  private DataContentHandlerFactory oldFactory;
  private String shortType;
  
  /**
   * Constructor in which the data is read from a data source.
   * @param ds the data source
   */
  public DataHandler(DataSource ds)
  {
    dataSource = ds;
    oldFactory = factory;
  }
  
  /**
   * Constructor using a reified object representation.
   * @param obj the object representation of the data
   * @param mimeType the MIME type of the object
   */
  public DataHandler(Object obj, String mimeType)
  {
    object = obj;
    objectMimeType = mimeType;
    oldFactory = factory;
  }
  
  /**
   * Constructor in which the data is read from a URL.
   * @param url the URL
   */
  public DataHandler(URL url)
  {
    dataSource = new URLDataSource(url);
    oldFactory = factory;
  }
  
  /**
   * Returns the data source from which data is read.
   */
  public DataSource getDataSource()
  {
    if (dataSource != null)
      {
        return dataSource;
      }
    if (objDataSource == null)
      {
        objDataSource = new DataHandlerDataSource(this);
      }
    return objDataSource;
  }
  
  /**
   * Returns the name of the data object if created with a DataSource.
   */
  public String getName()
  {
    if (dataSource != null)
      {
        return dataSource.getName();
      }
    return null;
  }
  
  /**
   * Returns the MIME type of the data (with parameters).
   */
  public String getContentType()
  {
    if (dataSource != null)
      {
        return dataSource.getContentType();
      }
    return objectMimeType;
  }
  
  /**
   * Returns an input stream from which the data can be read.
   */
  public InputStream getInputStream()
    throws IOException
  {
    if (dataSource != null)
      {
        return dataSource.getInputStream();
      }
    DataContentHandler dch = getDataContentHandler();
    if (dch == null)
      {
        throw new UnsupportedDataTypeException("no DCH for MIME type " +
                                               getShortType());
      }
    if ((dch instanceof ObjectDataContentHandler) &&
        ((ObjectDataContentHandler)dch).getDCH() == null)
      {
        throw new UnsupportedDataTypeException("no object DCH " +
                                               "for MIME type " +
                                               getShortType());
      }
    PipedOutputStream pos = new PipedOutputStream();
    DataContentHandlerWriter dchw =
      new DataContentHandlerWriter(dch, object, objectMimeType, pos);
    Thread thread = new Thread(dchw, "DataHandler.getInputStream");
    thread.start();
    return new PipedInputStream(pos);
  }
  
  static class DataContentHandlerWriter
    implements Runnable
  {
    
    DataContentHandler dch;
    Object object;
    String mimeType;
    OutputStream out;
    
    DataContentHandlerWriter(DataContentHandler dch, Object object,
                             String mimeType, OutputStream out)
    {
      this.dch = dch;
      this.object = object;
      this.mimeType = mimeType;
      this.out = out;
    }
    
    public void run()
    {
      try
        {
          dch.writeTo(object, mimeType, out);
        }
      catch(IOException e)
        {
        }
      finally
        {
          try
            {
              out.close();
            }
          catch(IOException e)
            {
            }
        }
    }
  }
  
  /**
   * Writes the data as a byte stream.
   * @param os the stream to write to
   */
  public void writeTo(OutputStream os)
    throws IOException
  {
    if (dataSource != null)
      {
        InputStream in = dataSource.getInputStream();
        byte[] buf = new byte[8192];
        for (int len = in.read(buf); len != -1; len = in.read(buf))
          {
            os.write(buf, 0, len);
          }
        in.close();
      }
    else
      {
        DataContentHandler dch = getDataContentHandler();
        dch.writeTo(object, objectMimeType, os);
      }
  }
  
  /**
   * Returns an output stream that can be used to overwrite the underlying
   * data, if the DataSource constructor was used.
   */
  public OutputStream getOutputStream()
    throws IOException
  {
    if (dataSource != null)
      {
        return dataSource.getOutputStream();
      }
    return null;
  }
  
  /**
   * Returns the data flavors in which this data is available.
   */
  public synchronized DataFlavor[] getTransferDataFlavors()
  {
    if (factory != oldFactory || transferFlavors == NO_FLAVORS)
      {
        DataContentHandler dch = getDataContentHandler();
        transferFlavors = dch.getTransferDataFlavors();
      }
    return transferFlavors;
  }
  
  /**
   * Indicates whether the specified data flavor is supported for this
   * data.
   */
  public boolean isDataFlavorSupported(DataFlavor flavor)
  {
    DataFlavor[] flavors = getTransferDataFlavors();
    for (int i = 0; i < flavors.length; i++)
      {
        if (flavors[i].equals(flavor))
          {
            return true;
          }
      }
    return false;
  }
  
  /**
   * Returns an object representing the data to be transferred.
   * @param flavor the requested data flavor
   */
  public Object getTransferData(DataFlavor flavor)
    throws UnsupportedFlavorException, IOException
  {
    DataContentHandler dch = getDataContentHandler();
    return dch.getTransferData(flavor, dataSource);
  }
  
  /**
   * Sets the command map to be used by this data handler.
   * Setting to null uses the default command map.
   * @param commandMap the command map to use
   */
  public synchronized void setCommandMap(CommandMap commandMap)
  {
    if (commandMap != currentCommandMap || commandMap == null)
      {
        transferFlavors = NO_FLAVORS;
        dataContentHandler = null;
        currentCommandMap = commandMap;
      }
  }
  
  /**
   * Returns the preferred commands for this type of data.
   */
  public CommandInfo[] getPreferredCommands()
  {
    CommandMap commandMap = getCommandMap();
    return commandMap.getPreferredCommands(getShortType());
  }

  /**
   * Returns the complete list of commands for this type of data.
   */
  public CommandInfo[] getAllCommands()
  {
    CommandMap commandMap = getCommandMap();
    return commandMap.getAllCommands(getShortType());
  }

  /**
   * Returns the specified command.
   * @param cmdName the command name
   */
  public CommandInfo getCommand(String cmdName)
  {
    CommandMap commandMap = getCommandMap();
    return commandMap.getCommand(getShortType(), cmdName);
  }
  
  /**
   * Returns the data as a reified object.
   */
  public Object getContent()
    throws IOException
  {
    DataContentHandler dch = getDataContentHandler();
    return dch.getContent(getDataSource());
  }
  
  /**
   * Returns the instantiated bean using the specified command.
   * @param cmdInfo the command to instantiate the bean with
   */
  public Object getBean(CommandInfo cmdInfo)
  {
    try
      {
        return cmdInfo.getCommandObject(this, getClass().getClassLoader());
      }
    catch (IOException e)
      {
        e.printStackTrace(System.err);
        return null;
      }
    catch (ClassNotFoundException e)
      {
        e.printStackTrace(System.err);
        return null;
      }
  }

  /**
   * Sets the data content handler factory.
   * If the factory has already been set, throws an Error.
   * @param newFactory the factory to set
   */
  public static synchronized void
    setDataContentHandlerFactory(DataContentHandlerFactory newFactory)
  {
    if (factory != null)
      {
        throw new Error("DataContentHandlerFactory already defined");
      }
    SecurityManager security = System.getSecurityManager();
    if (security != null)
      {
        try
          {
            security.checkSetFactory();
          }
        catch (SecurityException e)
          {
            if (newFactory != null && DataHandler.class.getClassLoader()
                != newFactory.getClass().getClassLoader())
              {
                throw e;
              }
          }
      }
    factory = newFactory;
  }
  
  /*
   * Returns just the base part of the data's content-type, with no
   * parameters.
   */
  private synchronized String getShortType()
  {
    if (shortType == null)
      {
        String contentType = getContentType();
        try
          {
            MimeType mimeType = new MimeType(contentType);
            shortType = mimeType.getBaseType();
          }
        catch (MimeTypeParseException e)
          {
            shortType = contentType;
          }
      }
    return shortType;
  }
  
  /*
   * Returns the command map for this handler.
   */
  private synchronized CommandMap getCommandMap()
  {
    if (currentCommandMap != null)
      {
        return currentCommandMap;
      }
    return CommandMap.getDefaultCommandMap();
  }
  
  /*
   * Returns the DCH for this handler.
   */
  private synchronized DataContentHandler getDataContentHandler()
  {
    if (factory != oldFactory)
      {
        oldFactory = factory;
        factoryDCH = null;
        dataContentHandler = null;
        transferFlavors = NO_FLAVORS;
      }
    if (dataContentHandler != null)
      {
        return dataContentHandler;
      }
    String mimeType = getShortType();
    if (factoryDCH == null && factory != null)
      {
        factoryDCH = factory.createDataContentHandler(mimeType);
      }
    if (factoryDCH != null)
      {
        dataContentHandler = factoryDCH;
      }
    if (dataContentHandler == null)
      {
        CommandMap commandMap = getCommandMap();
        dataContentHandler = commandMap.createDataContentHandler(mimeType);
      }
    if (dataSource != null)
      {
        dataContentHandler =
          new DataSourceDataContentHandler(dataContentHandler, dataSource);
      }
    else
      {
        dataContentHandler =
          new ObjectDataContentHandler(dataContentHandler, object,
                                       objectMimeType);
      }
    return dataContentHandler;
  }
  
}

