/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package org.openide;

import org.openide.util.HelpCtx;
import org.openide.util.Lookup;

import java.beans.*;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import java.util.Enumeration;


/** This class represents an abstract subclass for services
* (compilation, execution, debugging, etc.) that can be registered in
* the system.
*
* @author Jaroslav Tulach
* @deprecated The prefered way to register and lookup services
 *   is now {@link Lookup} as described in <a href="util/doc-files/api.html#lookup">
 *   services registration and lookup</a> page.
*/
public abstract class ServiceType extends Object implements java.io.Serializable, HelpCtx.Provider {
    /** generated Serialized Version UID */
    private static final long serialVersionUID = -7573598174423654252L;

    /** Name of property for the name of the service type. */
    public static final String PROP_NAME = "name"; // NOI18N
    private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.openide.ServiceType"); // NOI18N

    /** name of the service type */
    private String name;

    /** listeners support */
    private transient PropertyChangeSupport supp;

    /** Default human-presentable name of the service type.
    * In the default implementation, taken from the bean descriptor.
    * @return initial value of the human-presentable name
    * @see FeatureDescriptor#getDisplayName
    */
    protected String displayName() {
        try {
            return Introspector.getBeanInfo(getClass()).getBeanDescriptor().getDisplayName();
        } catch (Exception e) {
            // Catching IntrospectionException, but also maybe NullPointerException...?
            ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);

            return getClass().getName();
        }
    }

    /** Method that creates a cloned instance of this object. Subclasses
     * are encouraged to implement the {@link Cloneable}
     * interface, in such case the <code>clone</code> method is called as a result
     * of calling this method. If the subclass does not implement
     * <code>Cloneable</code>, it is serialized and deserialized,
     * thus new instance created.
     *
     * @return new instance
     * @exception IllegalStateException if something goes wrong, but should not happen
     * @deprecated Service instance files should instead be copied in order to clone them.
     */
    public final ServiceType createClone() {
        Exception anEx;

        if (this instanceof Cloneable) {
            try {
                return (ServiceType) clone();
            } catch (CloneNotSupportedException ex) {
                anEx = ex;
            }
        } else {
            try {
                org.openide.util.io.NbMarshalledObject m = new org.openide.util.io.NbMarshalledObject(this);

                return (ServiceType) m.get();
            } catch (IOException ex) {
                anEx = ex;
            } catch (ClassNotFoundException ex) {
                anEx = ex;
            }
        }

        // the code can get here only if an exception occured
        // moreover it should never happen that this code is executed
        IllegalStateException ex = new IllegalStateException();

        ErrorManager err = ErrorManager.getDefault();
        err.copyAnnotation(ex, anEx);
        err.annotate(ex, "Cannot createClone for " + this); // NOI18N

        throw ex;
    }

    /** Correctly implements the clone operation on this object. In
     * order to work really correctly, the subclass has to implement the
     * Cloneable interface.
     *
     * @return a new cloned instance that does not have any listeners
     * @deprecated Service instance files should instead be copied in order to clone them.
     */
    protected Object clone() throws CloneNotSupportedException {
        ServiceType t = (ServiceType) super.clone();

        // clear listeners
        t.supp = null;

        // clear name
        t.name = null;

        return t;
    }

    /** Set the name of the service type.
    * Usually it suffices to override {@link #displayName},
    * or just to provide a {@link BeanDescriptor} for the class.
    * @param name the new human-presentable name
    */
    public void setName(String name) {
        String old = this.name;
        this.name = name;

        if (supp != null) {
            supp.firePropertyChange(PROP_NAME, old, name);
        }
    }

    /** Get the name of the service type.
    * The default value is given by {@link #displayName}.
    * @return a human-presentable name for the service type
    */
    public String getName() {
        return (name == null) ? displayName() : name;
    }

    /** Get context help for this service type.
    * @return context help
    */
    public abstract HelpCtx getHelpCtx();

    /** Add a property change listener.
    * @param l the listener to add
    */
    public final synchronized void addPropertyChangeListener(PropertyChangeListener l) {
        if (supp == null) {
            supp = new PropertyChangeSupport(this);
        }

        supp.addPropertyChangeListener(l);
    }

    /** Remove a property change listener.
    * @param l the listener to remove
    */
    public final void removePropertyChangeListener(PropertyChangeListener l) {
        if (supp != null) {
            supp.removePropertyChangeListener(l);
        }
    }

    /** Fire information about change of a property in the service type.
    * @param name name of the property
    * @param o old value
    * @param n new value
    */
    protected final void firePropertyChange(String name, Object o, Object n) {
        if (supp != null) {
            supp.firePropertyChange(name, o, n);
        }
    }

    /** The registry of all services. This class is provided by the implementation
    * of the IDE and should hold all of the services registered to the system.
    * <P>
    * This class can be serialized to securely save settings of all
    * services in the system.
    * @deprecated Use lookup instead.
    */
    public static abstract class Registry implements java.io.Serializable {
        /** suid */
        final static long serialVersionUID = 8721000770371416481L;

        /** Get all available services managed by the engine.
        * @return an enumeration of {@link ServiceType}s
        */
        public abstract Enumeration services();

        /** Get all available services that are assignable to the given superclass.
        * @param clazz the class that all services should be subclass of
        * @return an enumeration of all matching {@link ServiceType}s
        */
        public Enumeration services(final Class clazz) {
            class IsInstance implements org.openide.util.Enumerations.Processor {
                public Object process(Object obj, java.util.Collection ignore) {
                    return clazz.isInstance(obj) ? obj : null;
                }
            }

            return org.openide.util.Enumerations.filter(services(), new IsInstance());
        }

        /** Getter for list of all service types.
        * @return a list of {@link ServiceType}s
        */
        public abstract java.util.List getServiceTypes();

        /** Setter for list of service types. This permits changing
        * instances of the objects but only within the types that are already registered
        * in the system by manifest sections. If an instance of any other type
        * is in the list it is ignored.
        *
        * @param arr a list of {@link ServiceType}s
        * @deprecated Better to change service instance files instead.
        */
        public abstract void setServiceTypes(java.util.List arr);

        /** Find the service type implemented as a given class.
         * The whole registry is searched for a service type of that exact class (subclasses do not count).
        * <P>
        * This could be used during (de-)serialization
        * of a service type: only store its class name
        * and then try to find the type implemented by that class later.
        *
        * @param clazz the class of the service type looked for
        * @return the desired type or <code>null</code> if it does not exist
         * @deprecated Just use lookup.
        */
        public ServiceType find(Class clazz) {
            Enumeration en = services();

            while (en.hasMoreElements()) {
                Object o = en.nextElement();

                if (o.getClass() == clazz) {
                    return (ServiceType) o;
                }
            }

            return null;
        }

        /** Find a service type of a supplied name in the registry.
        * <P>
        * This could be used during (de-)serialization
        * of a service type: only store its name
        * and then try to find the type later.
        *
        * @param name (display) name of service type to find
        * @return the desired type or <code>null</code> if it does not exist
        */
        public ServiceType find(String name) {
            Enumeration en = services();

            while (en.hasMoreElements()) {
                ServiceType o = (ServiceType) en.nextElement();

                if (name.equals(o.getName())) {
                    return o;
                }
            }

            return null;
        }
    }

    /** Handle for a service type. This is a serializable class that should be used
    * to store types and to recreate them after deserialization.
    * @deprecated The prefered way to register and lookup services
    *   is now {@link Lookup} as described in <a href="util/doc-files/api.html#lookup">
    *   services registration and lookup</a> page.
    */
    public static final class Handle extends Object implements java.io.Serializable {
        /** generated Serialized Version UID */
        static final long serialVersionUID = 7233109534462148872L;

        /** name executor */
        private String name;

        /** name of class of the executor */
        private String className;

        /** kept ServiceType may be <tt>null</tt> after deserialization */
        private transient ServiceType serviceType;

        /** Create a new handle for an service.
        * @param ex the service to store a handle for
        */
        public Handle(ServiceType ex) {
            name = ex.getName();
            className = ex.getClass().getName();
            serviceType = ex;
        }

        /** Find the service for this handle.
        * @return the reconstituted service type, or <code>null</code> in case of problems
        */
        public ServiceType getServiceType() {
            if (serviceType == null) {
                // the class to search for
                Class clazz;

                // the first subclass of ServiceType to search for
                Class serviceTypeClass;

                // try to find it by class
                try {
                    clazz = Class.forName(className, true, (ClassLoader) Lookup.getDefault().lookup(ClassLoader.class));

                    serviceTypeClass = clazz;

                    while (serviceTypeClass.getSuperclass() != ServiceType.class) {
                        serviceTypeClass = serviceTypeClass.getSuperclass();
                    }
                } catch (ClassNotFoundException ex) {
                    // #32140 - do not notify user about this exception. This exception
                    // should be only thrown when module providing the service
                    // was uninstalled and in that case the exception must be ignored.
                    err.log(ErrorManager.INFORMATIONAL, "Service not found: " + ex.toString()); //NOI18N

                    // nothing better to use
                    clazz = ServiceType.class;
                    serviceTypeClass = ServiceType.class;
                }

                // try to find the executor by name
                ServiceType.Registry r = (ServiceType.Registry) Lookup.getDefault().lookup(ServiceType.Registry.class);
                Enumeration en = r.services(clazz);
                ServiceType some = r.find(clazz);

                while (en.hasMoreElements()) {
                    ServiceType t = (ServiceType) en.nextElement();

                    if (!serviceTypeClass.isInstance(t)) {
                        // ignore non instances
                        continue;
                    }

                    String n = t.getName();

                    if ((n != null) && n.equals(name)) {
                        return t;
                    }

                    // remember it for later use
                    if ((some == null) || ((some.getClass() != clazz) && (t.getClass() == clazz))) {
                        // remember the best match
                        some = t;
                    }
                }

                // if clazz does not exist and there is no service with same name -> return null
                if (serviceTypeClass == ServiceType.class) {
                    return null;
                }

                return some;
            }

            return serviceType;
        }

        /** Old compatibility version.
        */
        private void readObject(ObjectInputStream ois)
        throws IOException, ClassNotFoundException {
            name = (String) ois.readObject();

            String clazz = (String) ois.readObject();
            className = (clazz == null) ? null : org.openide.util.Utilities.translate(clazz);
        }

        /** Has also save the object.
        */
        private void writeObject(ObjectOutputStream oos)
        throws IOException {
            oos.writeObject(name);
            oos.writeObject(className);
        }

        // for debugging purposes
        public String toString() {
            return "Handle[" + className + ":" + name + "]"; // NOI18N
        }
    }
}
