/*
 * @(#)PrintServiceLookup.java	1.15 05/11/17
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */


package javax.print;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.print.attribute.AttributeSet;

import sun.awt.AppContext;
import sun.misc.Service;

/** Implementations of this class provide lookup services for
  * print services (typically equivalent to printers) of a particular type.
  * <p>
  * Multiple implementations may be installed concurrently.
  * All implementations must be able to describe the located printers
  * as instances of a PrintService.   
  * Typically implementations of this service class are located
  * automatically in JAR files (see the SPI JAR file specification).
  * These classes must be instantiable using a default constructor.
  * Alternatively applications may explicitly register instances
  * at runtime.
  * <p>
  * Applications use only the static methods of this abstract class.
  * The instance methods are implemented by a service provider in a subclass
  * and the unification of the results from all installed lookup classes
  * are reported by the static methods of this class when called by
  * the application.
  * <p>
  * A PrintServiceLookup implementor is recommended to check for the
  * SecurityManager.checkPrintJobAccess() to deny access to untrusted code.
  * Following this recommended policy means that untrusted code may not
  * be able to locate any print services. Downloaded applets are the most
  * common example of untrusted code.
  * <p>
  * This check is made on a per lookup service basis to allow flexibility in
  * the policy to reflect the needs of different lookup services.
  * <p>
  * Services which are registered by registerService(PrintService)
  * will not be included in lookup results if a security manager is
  * installed and its checkPrintJobAccess() method denies access.
  */

public abstract class PrintServiceLookup {
   
    static class Services {
	private ArrayList listOfLookupServices = null;
	private ArrayList registeredServices = null;	
    }

    private static Services getServicesForContext() {
	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 getListOfLookupServices() {
	return getServicesForContext().listOfLookupServices;
    }

    private static ArrayList initListOfLookupServices() {
	ArrayList listOfLookupServices = new ArrayList();
	getServicesForContext().listOfLookupServices = listOfLookupServices;
	return listOfLookupServices;
    }


    private static ArrayList getRegisteredServices() {
	return getServicesForContext().registeredServices;
    }

    private static ArrayList initRegisteredServices() {
	ArrayList registeredServices = new ArrayList();
	getServicesForContext().registeredServices = registeredServices;
	return registeredServices;
    }

    /**
     * Locates print services capable of printing the specified
     * {@link DocFlavor}.
     *
     * @param flavor the flavor to print. If null, this constraint is not
     *        used.
     * @param attributes attributes that the print service must support.
     * If null this constraint is not used.
     *
     * @return array of matching <code>PrintService</code> objects
     * representing print services that support the specified flavor
     * attributes.  If no services match, the array is zero-length.
     */
    public static final PrintService[] 
	lookupPrintServices(DocFlavor flavor,
			    AttributeSet attributes) {
	ArrayList list = getServices(flavor, attributes);
	return (PrintService[])(list.toArray(new PrintService[list.size()]));
    }


    /**
     * Locates MultiDoc print Services capable of printing MultiDocs
     * containing all the specified doc flavors.
     * <P> This method is useful to help locate a service that can print
     * a <code>MultiDoc</code> in which the elements may be different
     * flavors. An application could perform this itself by multiple lookups
     * on each <code>DocFlavor</code> in turn and collating the results,
     * but the lookup service may be able to do this more efficiently.
     *
     * @param flavors the flavors to print. If null or empty this
     *        constraint is not used.
     * Otherwise return only multidoc print services that can print all
     * specified doc flavors.
     * @param attributes attributes that the print service must
     * support.  If null this constraint is not used.
     *
     * @return array of matching {@link MultiDocPrintService} objects.
     * If no services match, the array is zero-length.
     * 
     */
    public static final MultiDocPrintService[] 
	lookupMultiDocPrintServices(DocFlavor[] flavors,
				    AttributeSet attributes) {
	ArrayList list = getMultiDocServices(flavors, attributes);        
	return (MultiDocPrintService[])
	    list.toArray(new MultiDocPrintService[list.size()]);
    }
    
    
    /**
     * Locates the default print service for this environment.
     * This may return null.
     * If multiple lookup services each specify a default, the
     * chosen service is not precisely defined, but a
     * platform native service, rather than an installed service,
     * is usually returned as the default.  If there is no clearly
     * identifiable
     * platform native default print service, the default is the first
     * to be located in an implementation-dependent manner.
     * <p>
     * This may include making use of any preferences API that is available
     * as part of the Java or native platform.
     * This algorithm may be overridden by a user setting the property
     * javax.print.defaultPrinter.
     * A service specified must be discovered to be valid and currently
     * available to be returned as the default.
     *
     * @return the default PrintService.
     */
    
    public static final PrintService lookupDefaultPrintService() {    
    
	Iterator psIterator = getAllLookupServices().iterator();
	while (psIterator.hasNext()) {
	    try {
		PrintServiceLookup lus = (PrintServiceLookup)psIterator.next();
		PrintService service = lus.getDefaultPrintService();	
		if (service != null) {
		    return service;
		}
	    } catch (Exception e) {
	    }
	}
	return null;
    }

   
    /** 
     * Allows an application to explicitly register a class that
     * implements lookup services. The registration will not persist
     * across VM invocations.
     * This is useful if an application needs to make a new service
     * available that is not part of the installation.
     * If the lookup service is already registered, or cannot be registered,
     * the method returns false.
     * <p>
     *
     * @param sp an implementation of a lookup service.
     * @return <code>true</code> if the new lookup service is newly
     *         registered; <code>false</code> otherwise.
     */
    public static boolean registerServiceProvider(PrintServiceLookup sp) {
	synchronized (PrintServiceLookup.class) {
	    Iterator psIterator = getAllLookupServices().iterator();
	    while (psIterator.hasNext()) {
		try {
		    Object lus = psIterator.next();
		    if (lus.getClass() == sp.getClass()) {
			return false;
		    }
		} catch (Exception e) {
		}
	    }
	    getListOfLookupServices().add(sp);
	    return true;
	}

    }

  
    /**
     * Allows an application to directly register an instance of a
     * class which implements a print service.
     * The lookup operations for this service will be
     * performed by the PrintServiceLookup class using the attribute
     * values and classes reported by the service.
     * This may be less efficient than a lookup
     * service tuned for that service.
     * Therefore registering a <code>PrintServiceLookup</code> instance
     * instead is recommended.	
     * The method returns true if this service is not previously
     * registered and is now successfully registered.
     * This method should not be called with StreamPrintService instances.
     * They will always fail to register and the method will return false.
     * @param service an implementation of a print service.
     * @return <code>true</code> if the service is newly
     *         registered; <code>false</code> otherwise.
     */

    public static boolean registerService(PrintService service) {
	synchronized (PrintServiceLookup.class) {
	    if (service instanceof StreamPrintService) {
		return false;
	    }
	    ArrayList registeredServices = getRegisteredServices();	    
	    if (registeredServices == null) {
		registeredServices = initRegisteredServices();
	    } 
	    else {	
	      if (registeredServices.contains(service)) {
		return false;
	      }
	    }
	    registeredServices.add(service);
	    return true;
	}
    }


   /**
    * Locates services that can be positively confirmed to support
    * the combination of attributes and DocFlavors specified.
    * This method is not called directly by applications.
    * <p>
    * Implemented by a service provider, used by the static methods
    * of this class.
    * <p>
    * The results should be the same as obtaining all the PrintServices
    * and querying each one individually on its support for the
    * specified attributes and flavors, but the process can be more
    * efficient by taking advantage of the capabilities of lookup services
    * for the print services.
    *
    * @param flavor of document required.  If null it is ignored.
    * @param attributes required to be supported. If null this
    * constraint is not used.
    * @return array of matching PrintServices. If no services match, the
    * array is zero-length.
    */
    public abstract PrintService[] getPrintServices(DocFlavor flavor,
						    AttributeSet attributes);
    
    /**
     * Not called directly by applications.
     * Implemented by a service provider, used by the static methods
     * of this class.
     * @return array of all PrintServices known to this lookup service
     * class. If none are found, the array is zero-length.
     */
    public abstract PrintService[] getPrintServices() ;
    
       
   /**
    * Not called directly by applications.
    * <p>
    * Implemented by a service provider, used by the static methods
    * of this class.
    * <p>
    * Locates MultiDoc print services which can be positively confirmed
    * to support the combination of attributes and DocFlavors specified.
    * <p>
    *
    * @param flavors of documents required. If null or empty it is ignored.
    * @param attributes required to be supported. If null this
     * constraint is not used.
    * @return array of matching PrintServices. If no services match, the
    * array is zero-length.
    */
    public abstract MultiDocPrintService[] 
	getMultiDocPrintServices(DocFlavor[] flavors,
				 AttributeSet attributes);
    
    /**
     * Not called directly by applications.
     * Implemented by a service provider, and called by the print lookup
     * service
     * @return the default PrintService for this lookup service.
     * If there is no default, returns null.
     */
    public abstract PrintService getDefaultPrintService(); 
    
    private static ArrayList getAllLookupServices() {
	synchronized (PrintServiceLookup.class) {
	    ArrayList listOfLookupServices = getListOfLookupServices();
	    if (listOfLookupServices != null) {
		return listOfLookupServices;
	    } else {
		listOfLookupServices = initListOfLookupServices();
	    }
	    try {
		java.security.AccessController.doPrivileged(
		     new java.security.PrivilegedExceptionAction() {
                        public Object run() {
			    Iterator iterator =
				Service.providers(PrintServiceLookup.class);
			    ArrayList los = getListOfLookupServices();
			    while (iterator.hasNext()) {
				try {
				    PrintServiceLookup lus =
					(PrintServiceLookup)iterator.next();
				    los.add(lus);
				}  catch (Exception e) {
				}
			    }
			    return null;
			}
		});
	    } catch (java.security.PrivilegedActionException e) {
	    }

	    return listOfLookupServices;
	}
    }

    private static ArrayList getServices(DocFlavor flavor,
					 AttributeSet attributes) {   
	
	ArrayList listOfServices = new ArrayList();
	Iterator psIterator = getAllLookupServices().iterator();
	while (psIterator.hasNext()) {
	    try {
		PrintServiceLookup lus = (PrintServiceLookup)psIterator.next();
		PrintService[] services=null;
		if (flavor == null && attributes == null) {
		    try {
		    services = lus.getPrintServices();
		    } catch (Throwable tr) {
		    }
		} else {
		    services = lus.getPrintServices(flavor, attributes);
		}
		if (services == null) {
		    continue;
		}
		for (int i=0; i<services.length; i++) {
		    listOfServices.add(services[i]);
		}
	    } catch (Exception e) {
	    }
	}
	/* add any directly registered services */
	ArrayList registeredServices = null;
	try {
	  SecurityManager security = System.getSecurityManager();
	  if (security != null) {  
	    security.checkPrintJobAccess();
	  }
	  registeredServices = getRegisteredServices();
	} catch (SecurityException se) {
	}
	if (registeredServices != null) {
	    PrintService[] services = (PrintService[])
		registeredServices.toArray(
			   new PrintService[registeredServices.size()]);
	    for (int i=0; i<services.length; i++) {
		if (!listOfServices.contains(services[i])) {
		    if (flavor == null && attributes == null) {
			listOfServices.add(services[i]);
		    } else if (((flavor != null &&
				 services[i].isDocFlavorSupported(flavor)) ||
				flavor == null) &&
			       null == services[i].getUnsupportedAttributes(
						      flavor, attributes)) {
			listOfServices.add(services[i]);
		    }
		}
	    }
	}
	return listOfServices;
    }
	
    private static ArrayList getMultiDocServices(DocFlavor[] flavors,
						 AttributeSet attributes) {   
	

	ArrayList listOfServices = new ArrayList();
	Iterator psIterator = getAllLookupServices().iterator();
	while (psIterator.hasNext()) {
	    try {
		PrintServiceLookup lus = (PrintServiceLookup)psIterator.next();
		MultiDocPrintService[] services  = 
		    lus.getMultiDocPrintServices(flavors, attributes);
		if (services == null) {
		    continue;
		}
		for (int i=0; i<services.length; i++) {
		    listOfServices.add(services[i]);
		}
	    } catch (Exception e) {
	    }
	}
	/* add any directly registered services */
	ArrayList registeredServices = null;
	try {
	  SecurityManager security = System.getSecurityManager();
	  if (security != null) {  
	    security.checkPrintJobAccess();
	  }
	  registeredServices = getRegisteredServices();
	} catch (Exception e) {
	}
	if (registeredServices != null) {
	    PrintService[] services = (PrintService[])
		registeredServices.toArray(
			   new PrintService[registeredServices.size()]);
	    for (int i=0; i<services.length; i++) {
		if (services[i] instanceof MultiDocPrintService &&
		    !listOfServices.contains(services[i])) {
		    if (flavors == null || flavors.length == 0) {
			listOfServices.add(services[i]);
		    } else {
			boolean supported = true;
			for (int f=0; f<flavors.length; f++) {
			    if (services[i].isDocFlavorSupported(flavors[f])) {
	   
				if (services[i].getUnsupportedAttributes(
				     flavors[f], attributes) != null) {
				    	supported = false;
					break;
				}
			    } else {
				supported = false;
				break;
			    }
			}
			if (supported) {
			    listOfServices.add(services[i]);	
			}
		    }
		}
	    }
	}
	return listOfServices;
    }

}

