/*
 * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.print;

import java.io.OutputStream;

import java.util.ArrayList;
import java.util.Iterator;

import javax.print.DocFlavor;

import sun.awt.AppContext;
import java.util.ServiceLoader;
import java.util.ServiceConfigurationError;

/**
 * A <code>StreamPrintServiceFactory</code> is the factory for
 * {@link StreamPrintService} instances,
 * which can print to an output stream in a particular
 * document format described as a mime type.
 * A typical output document format may be Postscript(TM).
 * <p>
 * This class is implemented by a service and located by the
 * implementation using the
 * <a href="../../../technotes/guides/jar/jar.html#Service Provider">
 * SPI JAR File specification</a>.
 * <p>
 * Applications locate instances of this class by calling the
 * {@link #lookupStreamPrintServiceFactories(DocFlavor, String)} method.
 * <p>
 * Applications can use a <code>StreamPrintService</code> obtained from a
 * factory in place of a <code>PrintService</code> which represents a
 * physical printer device.
 */

public abstract class StreamPrintServiceFactory {

    static class Services {
        private ArrayList listOfFactories = null;
    }

    private static Services getServices() {
        Services services =
            (Services)AppContext.getAppContext().get(Services.class);
        if (services == null) {
            services = new Services();
            AppContext.getAppContext().put(Services.class, services);
        }
        return services;
    }

    private static ArrayList getListOfFactories() {
        return getServices().listOfFactories;
    }

    private static ArrayList initListOfFactories() {
        ArrayList listOfFactories = new ArrayList();
        getServices().listOfFactories = listOfFactories;
        return listOfFactories;
    }

    /**
     * Locates factories for print services that can be used with
     * a print job to output a stream of data in the
     * format specified by {@code outputMimeType}.
     * <p>
     * The {@code outputMimeType} parameter describes the document type that
     * you want to create, whereas the {@code flavor} parameter describes the
     * format in which the input data will be provided by the application
     * to the {@code StreamPrintService}.
     * <p>
     * Although null is an acceptable value to use in the lookup of stream
     * printing services, it's typical to search for a particular
     * desired format, such as Postscript(TM).
     * <p>
     * @param flavor of the input document type - null means match all
     * types.
     * @param outputMimeType representing the required output format, used to
     * identify suitable stream printer factories. A value of null means
     * match all formats.
     * @return - matching factories for stream print service instance,
     *           empty if no suitable factories could be located.
     */
     public static StreamPrintServiceFactory[]
         lookupStreamPrintServiceFactories(DocFlavor flavor,
                                           String outputMimeType) {

         ArrayList list = getFactories(flavor, outputMimeType);
         return (StreamPrintServiceFactory[])
               (list.toArray(new StreamPrintServiceFactory[list.size()]));
     }

    /** Queries the factory for the document format that is emitted
     * by printers obtained from this factory.
     *
     * @return the output format described as a mime type.
     */
    public abstract String getOutputFormat();

    /**
     * Queries the factory for the document flavors that can be accepted
     * by printers obtained from this factory.
     * @return array of supported doc flavors.
     */
    public abstract DocFlavor[] getSupportedDocFlavors();

    /**
     * Returns a <code>StreamPrintService</code> that can print to
     * the specified output stream.
     * The output stream is created and managed by the application.
     * It is the application's responsibility to close the stream and
     * to ensure that this Printer is not reused.
     * The application should not close this stream until any print job
     * created from the printer is complete. Doing so earlier may generate
     * a <code>PrinterException</code> and an event indicating that the
     * job failed.
     * <p>
     * Whereas a <code>PrintService</code> connected to a physical printer
     * can be reused,
     * a <code>StreamPrintService</code> connected to a stream cannot.
     * The underlying <code>StreamPrintService</code> may be disposed by
     * the print system with
     * the {@link StreamPrintService#dispose() dispose} method
     * before returning from the
     * {@link DocPrintJob#print(Doc, javax.print.attribute.PrintRequestAttributeSet) print}
     * method of <code>DocPrintJob</code> so that the print system knows
     * this printer is no longer usable.
     * This is equivalent to a physical printer going offline - permanently.
     * Applications may supply a null print stream to create a queryable
     * service. It is not valid to create a PrintJob for such a stream.
     * Implementations which allocate resources on construction should examine
     * the stream and may wish to only allocate resources if the stream is
     * non-null.
     * <p>
     * @param out destination stream for generated output.
     * @return a PrintService which will generate the format specified by the
     * DocFlavor supported by this Factory.
     */
    public abstract StreamPrintService getPrintService(OutputStream out);


    private static ArrayList getAllFactories() {
        synchronized (StreamPrintServiceFactory.class) {

          ArrayList listOfFactories = getListOfFactories();
            if (listOfFactories != null) {
                return listOfFactories;
            } else {
                listOfFactories = initListOfFactories();
            }

            try {
                java.security.AccessController.doPrivileged(
                     new java.security.PrivilegedExceptionAction() {
                        public Object run() {
                            Iterator<StreamPrintServiceFactory> iterator =
                                ServiceLoader.load
                                (StreamPrintServiceFactory.class).iterator();
                            ArrayList lof = getListOfFactories();
                            while (iterator.hasNext()) {
                                try {
                                    lof.add(iterator.next());
                                }  catch (ServiceConfigurationError err) {
                                     /* In the applet case, we continue */
                                    if (System.getSecurityManager() != null) {
                                        err.printStackTrace();
                                    } else {
                                        throw err;
                                    }
                                }
                            }
                            return null;
                        }
                });
            } catch (java.security.PrivilegedActionException e) {
            }
            return listOfFactories;
        }
    }

    private static boolean isMember(DocFlavor flavor, DocFlavor[] flavors) {
        for (int f=0; f<flavors.length; f++ ) {
            if (flavor.equals(flavors[f])) {
                return true;
            }
        }
        return false;
    }

    private static ArrayList getFactories(DocFlavor flavor, String outType) {

        if (flavor == null && outType == null) {
            return getAllFactories();
        }

        ArrayList list = new ArrayList();
        Iterator iterator = getAllFactories().iterator();
        while (iterator.hasNext()) {
            StreamPrintServiceFactory factory =
                (StreamPrintServiceFactory)iterator.next();
            if ((outType == null ||
                 outType.equalsIgnoreCase(factory.getOutputFormat())) &&
                (flavor == null ||
                 isMember(flavor, factory.getSupportedDocFlavors()))) {
                list.add(factory);
            }
        }

        return list;
    }

}
