/*
 * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package com.sun.servicetag;

import java.util.Date;
import java.io.IOException;
import static com.sun.servicetag.RegistrationDocument.*;

/**
 * A service tag is an XML-based data structure that identifies a product or
 * a component on a system. The service tag schema is defined by the
 * Service Tags Technology. The location of the DTD file is platform dependent.
 * On Solaris, see <tt>/usr/share/lib/xml/dtd/servicetag.dtd</tt>.
 * <p>
 * A valid {@code ServiceTag} instance must comply to the service tag schema
 * and contain the following fields:
 * <ul>
 *   <li>{@link #getInstanceURN <tt>instance_urn</tt>}</li>
 *   <li>{@link #getProductName <tt>product_name</tt>}</li>
 *   <li>{@link #getProductVersion <tt>product_version</tt>}</li>
 *   <li>{@link #getProductURN <tt>product_urn</tt>}</li>
 *   <li>{@link #getProductParent <tt>product_parent</tt>}</li>
 *   <li>{@link #getProductParentURN <tt>product_parent_urn</tt>}</li>
 *   <li>{@link #getProductDefinedInstanceID <tt>product_defined_inst_id</tt>}</li>
 *   <li>{@link #getProductVendor <tt>product_vendor</tt>}</li>
 *   <li>{@link #getPlatformArch <tt>platform_arch</tt>}</li>
 *   <li>{@link #getContainer <tt>container</tt>}</li>
 *   <li>{@link #getSource <tt>source</tt>}</li>
 *   <li>{@link #getInstallerUID <tt>installer_uid</tt>}</li>
 *   <li>{@link #getTimestamp <tt>timestamp</tt>}</li>
 * </ul>
 *
 * The <tt>instance_urn</tt> can be specified when a {@code ServiceTag}
 * object is created, or it can be generated when it is added to
 * a {@link RegistrationData} object, or {@link Registry
 * system service tag registry}. The <tt>installer_uid</tt> and
 * <tt>timestamp</tt> are set when a {@code ServiceTag} object
 * is added to a {@link RegistrationData} object, or {@link Registry
 * system service tag registry}.
 *
 * @see <a href="https://sunconnection.sun.com/FAQ/sc_faq.html">Service Tags FAQ</a>
 */
public class ServiceTag {

    private String instanceURN;
    private String productName;
    private String productVersion;
    private String productURN;
    private String productParent;
    private String productParentURN;
    private String productDefinedInstanceID;
    private String productVendor;
    private String platformArch;
    private String container;
    private String source;
    private int installerUID;
    private Date timestamp;

    // Service Tag Field Lengths (defined in sthelper.h)
    // Since the constants defined in sthelper.h includes the null-terminated
    // character, so minus 1 from the sthelper.h defined values.
    private final int MAX_URN_LEN             = 256 - 1;
    private final int MAX_PRODUCT_NAME_LEN    = 256 - 1;
    private final int MAX_PRODUCT_VERSION_LEN = 64 - 1;
    private final int MAX_PRODUCT_PARENT_LEN  = 256 - 1;
    private final int MAX_PRODUCT_VENDOR_LEN  = 64 - 1;
    private final int MAX_PLATFORM_ARCH_LEN   = 64 - 1;
    private final int MAX_CONTAINER_LEN       = 64 - 1;
    private final int MAX_SOURCE_LEN          = 64 - 1;

    // private constructors
    private ServiceTag() {
    }
    // package private
    ServiceTag(String instanceURN,
               String productName,
               String productVersion,
               String productURN,
               String productParent,
               String productParentURN,
               String productDefinedInstanceID,
               String productVendor,
               String platformArch,
               String container,
               String source,
               int installerUID,
               Date timestamp) {
        setInstanceURN(instanceURN);
        setProductName(productName);
        setProductVersion(productVersion);
        setProductURN(productURN);
        setProductParentURN(productParentURN);
        setProductParent(productParent);
        setProductDefinedInstanceID(productDefinedInstanceID);
        setProductVendor(productVendor);
        setPlatformArch(platformArch);
        setContainer(container);
        setSource(source);
        setInstallerUID(installerUID);
        setTimestamp(timestamp);
    }

    /**
     * Creates a service tag object with no <tt>instance_urn</tt>.
     *
     * @param productName               the name of the product.
     * @param productVersion            the version of the product.
     * @param productURN                the uniform resource name of the product
     * @param productParent             the name of the product's parent.
     * @param productParentURN          the uniform resource name of the product's parent.
     * @param productDefinedInstanceID  the instance identifier.
     * @param productVendor             the vendor of the product.
     * @param platformArch              the operating system architecture.
     * @param container                 the container of the product.
     * @param source                    the source of the product.
     *
     * @throws IllegalArgumentException if any value of the input fields
     *    does not conform to the service tag XML schema.
     */
    public static ServiceTag newInstance(String productName,
                                         String productVersion,
                                         String productURN,
                                         String productParent,
                                         String productParentURN,
                                         String productDefinedInstanceID,
                                         String productVendor,
                                         String platformArch,
                                         String container,
                                         String source) {
          return new ServiceTag("", /* empty instance_urn */
                                productName,
                                productVersion,
                                productURN,
                                productParent,
                                productParentURN,
                                productDefinedInstanceID,
                                productVendor,
                                platformArch,
                                container,
                                source,
                                -1,
                                null);
    }

    /**
     * Creates a service tag object with a specified <tt>instance_urn</tt>.
     *
     * @param instanceURN               the uniform resource name of this instance.
     * @param productName               the name of the product.
     * @param productVersion            the version of the product.
     * @param productURN                the uniform resource name of the product
     * @param productParent             the name of the product's parent.
     * @param productParentURN          the uniform resource name of the product's parent.
     * @param productDefinedInstanceID  the instance identifier.
     * @param productVendor             the vendor of the product.
     * @param platformArch              the operating system architecture.
     * @param container                 the container of the product.
     * @param source                    the source of the product.
     *
     * @throws IllegalArgumentException if any value of the input fields
     *    does not conform to the service tag XML schema.
     */
    public static ServiceTag newInstance(String instanceURN,
                                         String productName,
                                         String productVersion,
                                         String productURN,
                                         String productParent,
                                         String productParentURN,
                                         String productDefinedInstanceID,
                                         String productVendor,
                                         String platformArch,
                                         String container,
                                         String source) {
          return new ServiceTag(instanceURN,
                                productName,
                                productVersion,
                                productURN,
                                productParent,
                                productParentURN,
                                productDefinedInstanceID,
                                productVendor,
                                platformArch,
                                container,
                                source,
                                -1,
                                null);
    }

    // Creates a copy of the ServiceTag instance
    // with instance_urn and timestamp initialized
    static ServiceTag newInstanceWithUrnTimestamp(ServiceTag st) {
        String instanceURN =
            (st.getInstanceURN().length() == 0 ? Util.generateURN() :
                                                 st.getInstanceURN());
        ServiceTag svcTag = new ServiceTag(instanceURN,
                                           st.getProductName(),
                                           st.getProductVersion(),
                                           st.getProductURN(),
                                           st.getProductParent(),
                                           st.getProductParentURN(),
                                           st.getProductDefinedInstanceID(),
                                           st.getProductVendor(),
                                           st.getPlatformArch(),
                                           st.getContainer(),
                                           st.getSource(),
                                           st.getInstallerUID(),
                                           new Date());
        return svcTag;
    }

    /**
     * Returns a uniform resource name (URN) in this format:
     * <blockquote>
     * "<tt>urn:st:<32-char {@link java.util.UUID uuid}></tt>"
     * </blockquote>
     * @return a URN.
     */
    public static String generateInstanceURN() {
        return Util.generateURN();
    }

    /**
     * Returns the uniform resource name of this service tag instance.
     *
     * @return  the <tt>instance_urn</tt> of this service tag.
     */
    public String getInstanceURN() {
        return instanceURN;
    }

    /**
     * Returns the name of the product.
     *
     * @return the product name.
     */
    public String getProductName() {
        return productName;
    }

    /**
     * Returns the version of the product.
     *
     * @return the product version.
     */
    public String getProductVersion() {
        return productVersion;
    }

    /**
     * Returns the uniform resource name of the product.
     *
     * @return the product URN.
     */
    public String getProductURN() {
        return productURN;
    }

    /**
     * Returns the uniform resource name of the product's parent.
     *
     * @return the product's parent URN.
     */
    public String getProductParentURN() {
        return productParentURN;
    }

    /**
     * Returns the name of the product's parent.
     *
     * @return the product's parent name.
     */
    public String getProductParent() {
        return productParent;
    }

    /**
     * Returns the identifier defined for this product instance.
     *
     * @return  the identifier defined for this product instance.
     */
    public String getProductDefinedInstanceID() {
        return productDefinedInstanceID;
    }

    /**
     * Returns the vendor of the product.
     *
     * @return the product vendor.
     */
    public String getProductVendor() {
        return productVendor;
    }

    /**
     * Returns the platform architecture on which the product
     * is running on.
     *
     * @return the platform architecture on which the product is running on.
     */
    public String getPlatformArch() {
        return platformArch;
    }

    /**
     * Returns the timestamp.  This timestamp is set when this service tag
     * is added to or updated in a {@code RegistrationData} object or
     * the system service tag registry.
     * This method may return {@code null}.
     *
     * @return timestamp when this service tag
     * is added to or updated in a {@code RegistrationData} object or
     * the system service tag registry, or {@code null}.
     */
    public Date getTimestamp() {
        if (timestamp != null) {
            return (Date) timestamp.clone();
        } else {
            return null;
        }
    }


    /**
     * Returns the container of the product.
     *
     * @return the container of the product.
     */
    public String getContainer() {
        return container;
    }

    /**
     * Returns the source of this service tag.
     *
     * @return  source of this service tag.
     */
    public String getSource() {
        return source;
    }

    /**
     * Returns the UID. The UID is set when this service tag
     * is added to or updated in the system service tag registry.
     * This is platform dependent whose default value is {@code -1}.
     * When this service tag is added to a {@code RegistrationData},
     * the UID is not set.
     *
     * @return the UID of whom this service tag
     * is added to or updated in the system service tag registry,
     * or {@code -1}.
     */
    public int getInstallerUID() {
        return installerUID;
    }

    // The following setter methods are used to validate the
    // input field when constructing a ServiceTag instance

    private void setInstanceURN(String instanceURN) {
        if (instanceURN == null) {
            throw new NullPointerException("Parameter instanceURN cannot be null");
        }
        if (instanceURN.length() > MAX_URN_LEN) {
            throw new IllegalArgumentException("instanceURN \"" + instanceURN +
                "\" exceeds maximum length " + MAX_URN_LEN);
        }
        this.instanceURN = instanceURN;
    }

    private void setProductName(String productName) {
        if (productName == null) {
            throw new NullPointerException("Parameter productName cannot be null");
        }
        if (productName.length() == 0) {
            throw new IllegalArgumentException("product name cannot be empty");
        }
        if (productName.length() > MAX_PRODUCT_NAME_LEN) {
            throw new IllegalArgumentException("productName \"" + productName +
                "\" exceeds maximum length " + MAX_PRODUCT_NAME_LEN);
        }
        this.productName = productName;
    }

    private void setProductVersion(String productVersion) {
        if (productVersion == null) {
            throw new NullPointerException("Parameter productVersion cannot be null");
        }

        if (productVersion.length() == 0) {
            throw new IllegalArgumentException("product version cannot be empty");
        }
        if (productVersion.length() > MAX_PRODUCT_VERSION_LEN) {
            throw new IllegalArgumentException("productVersion \"" +
                productVersion + "\" exceeds maximum length " +
                MAX_PRODUCT_VERSION_LEN);
        }
        this.productVersion = productVersion;
    }

    private void setProductURN(String productURN) {
        if (productURN == null) {
            throw new NullPointerException("Parameter productURN cannot be null");
        }
        if (productURN.length() == 0) {
            throw new IllegalArgumentException("product URN cannot be empty");
        }
        if (productURN.length() > MAX_URN_LEN) {
            throw new IllegalArgumentException("productURN \"" + productURN +
                "\" exceeds maximum length " + MAX_URN_LEN);
        }
        this.productURN = productURN;
    }

    private void setProductParentURN(String productParentURN) {
        if (productParentURN == null) {
            throw new NullPointerException("Parameter productParentURN cannot be null");
        }
        // optional field - can be empty
        if (productParentURN.length() > MAX_URN_LEN) {
            throw new IllegalArgumentException("productParentURN \"" +
                productParentURN + "\" exceeds maximum length " +
                MAX_URN_LEN);
        }
        this.productParentURN = productParentURN;
    }

    private void setProductParent(String productParent) {
        if (productParent == null) {
            throw new NullPointerException("Parameter productParent cannot be null");
        }
        if (productParent.length() == 0) {
            throw new IllegalArgumentException("product parent cannot be empty");
        }
        if (productParent.length() > MAX_PRODUCT_PARENT_LEN) {
            throw new IllegalArgumentException("productParent \"" +
                productParent + "\" exceeds maximum length " +
                MAX_PRODUCT_PARENT_LEN);
        }
        this.productParent = productParent;
    }

    void setProductDefinedInstanceID(String productDefinedInstanceID) {
        if (productDefinedInstanceID == null) {
            throw new NullPointerException("Parameter productDefinedInstanceID cannot be null");
        }
        if (productDefinedInstanceID.length() > MAX_URN_LEN) {
            throw new IllegalArgumentException("productDefinedInstanceID \"" +
                productDefinedInstanceID + "\" exceeds maximum length " +
                MAX_URN_LEN);
        }
        // optional field - can be empty
        this.productDefinedInstanceID = productDefinedInstanceID;
    }

    private void setProductVendor(String productVendor) {
        if (productVendor == null) {
            throw new NullPointerException("Parameter productVendor cannot be null");
        }
        if (productVendor.length() == 0) {
            throw new IllegalArgumentException("product vendor cannot be empty");
        }
        if (productVendor.length() > MAX_PRODUCT_VENDOR_LEN) {
            throw new IllegalArgumentException("productVendor \"" +
                productVendor + "\" exceeds maximum length " +
                MAX_PRODUCT_VENDOR_LEN);
        }
        this.productVendor = productVendor;
    }

    private void setPlatformArch(String platformArch) {
        if (platformArch == null) {
            throw new NullPointerException("Parameter platformArch cannot be null");
        }
        if (platformArch.length() == 0) {
            throw new IllegalArgumentException("platform architecture cannot be empty");
        }
        if (platformArch.length() > MAX_PLATFORM_ARCH_LEN) {
            throw new IllegalArgumentException("platformArch \"" +
                platformArch + "\" exceeds maximum length " +
                MAX_PLATFORM_ARCH_LEN);
        }
        this.platformArch = platformArch;
    }

    private void setTimestamp(Date timestamp) {
        // can be null
        this.timestamp = timestamp;
    }

    private void setContainer(String container) {
        if (container == null) {
            throw new NullPointerException("Parameter container cannot be null");
        }
        if (container.length() == 0) {
            throw new IllegalArgumentException("container cannot be empty");
        }
        if (container.length() > MAX_CONTAINER_LEN) {
            throw new IllegalArgumentException("container \"" +
                container + "\" exceeds maximum length " +
                MAX_CONTAINER_LEN);
        }
        this.container = container;
    }

    private void setSource(String source) {
        if (source == null) {
            throw new NullPointerException("Parameter source cannot be null");
        }
        if (source.length() == 0) {
            throw new IllegalArgumentException("source cannot be empty");
        }
        if (source.length() > MAX_SOURCE_LEN) {
            throw new IllegalArgumentException("source \"" + source +
                "\" exceeds maximum length " + MAX_SOURCE_LEN);
        }
        this.source = source;
    }

    private void setInstallerUID(int installerUID) {
        this.installerUID = installerUID;
    }

    /**
     * Compares this service tag to the specified object.
     * The result is {@code true} if and only if the argument is
     * not {@code null} and is a {@code ServiceTag} object whose
     * <tt>instance_urn</tt> is the same as the
     * <tt>instance_urn</tt> of this service tag.
     *
     * @return {@code true} if this service tag is the same as
     * the specified object.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof ServiceTag)) {
            return false;
        }
        ServiceTag st = (ServiceTag) obj;
        if (st == this) {
            return true;
        }
        return st.getInstanceURN().equals(getInstanceURN());
    }

    /**
     * Returns the hash code value for this service tag.
     * @return the hash code value for this service tag.
     */
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 19 * hash + (this.instanceURN != null ? this.instanceURN.hashCode() : 0);
        return hash;
    }

    /**
     * Returns the string representation of this service tag.
     * The format is implementation specific.
     *
     * @return the string representation of this service tag.
     */
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(ST_NODE_INSTANCE_URN).append("=").append(instanceURN).append("\n");
        sb.append(ST_NODE_PRODUCT_NAME).append("=").append(productName).append("\n");
        sb.append(ST_NODE_PRODUCT_VERSION).append("=").append(productVersion).append("\n");
        sb.append(ST_NODE_PRODUCT_URN).append("=").append(productURN).append("\n");
        sb.append(ST_NODE_PRODUCT_PARENT_URN).append("=").append(productParentURN).append("\n");
        sb.append(ST_NODE_PRODUCT_PARENT).append("=").append(productParent).append("\n");
        sb.append(ST_NODE_PRODUCT_DEFINED_INST_ID).append("=").append(productDefinedInstanceID).append("\n");
        sb.append(ST_NODE_PRODUCT_VENDOR).append("=").append(productVendor).append("\n");
        sb.append(ST_NODE_PLATFORM_ARCH).append("=").append(platformArch).append("\n");
        sb.append(ST_NODE_TIMESTAMP).append("=").append(Util.formatTimestamp(timestamp)).append("\n");
        sb.append(ST_NODE_CONTAINER).append("=").append(container).append("\n");
        sb.append(ST_NODE_SOURCE).append("=").append(source).append("\n");
        sb.append(ST_NODE_INSTALLER_UID).append("=").append(String.valueOf(installerUID)).append("\n");
        return sb.toString();
    }


    /**
     * Returns the {@link ServiceTag} instance for the running Java
     * platform. The {@link ServiceTag#setSource source} field
     * of the {@code ServiceTag} will be set to the given {@code source}.
     * This method will return {@code null} if there is no service tag
     * for the running Java platform.
     * <p>
     * This method is designed for Sun software that bundles the JDK
     * or the JRE to use. It is recommended that the {@code source}
     * string contains information about the bundling software
     * such as the name and the version of the software bundle,
     * for example,
     * <blockquote>
     * <tt>NetBeans IDE 6.0 with JDK 6 Update 5 Bundle</tt>
     * </blockquote>
     * in a NetBeans/JDK bundle.
     * <p>
     * At the first time to call this method the application
     * is required to have the write permission to the installed
     * directory of this running JDK or JRE instance.
     *
     * @param source the source that bundles the JDK or the JRE.
     * @return a {@code ServiceTag} object for the Java platform,
     *         or {@code null} if not supported.
     * @throws IOException if an error occurs in this operation.
     */
    public static ServiceTag getJavaServiceTag(String source) throws IOException {
        return Installer.getJavaServiceTag(source);
    }

}
