/*
 * 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.0.2
 */
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;
    }

}
