/*
 * Copyright (c) 1999, 2004, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.misc;

import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.text.MessageFormat;
import java.lang.Character.*;


/**
 * This class holds all necessary information to install or
 * upgrade a extension on the user's disk
 *
 * @author  Jerome Dochez
 */
public class ExtensionInfo {

    /**
     * <p>
     * public static values returned by the isCompatible method
     * </p>
     */
    public static final int COMPATIBLE = 0;
    public static final int REQUIRE_SPECIFICATION_UPGRADE = 1;
    public static final int REQUIRE_IMPLEMENTATION_UPGRADE = 2;
    public static final int REQUIRE_VENDOR_SWITCH = 3;
    public static final int INCOMPATIBLE = 4;

    /**
     * <p>
     * attributes fully describer an extension. The underlying described
     * extension may be installed and requested.
     * <p>
     */
    public String title;
    public String name;
    public String specVersion;
    public String specVendor;
    public String implementationVersion;
    public String vendor;
    public String vendorId;
    public String url;

    // For I18N support
    private static final ResourceBundle rb =
        ResourceBundle.getBundle("sun.misc.resources.Messages");


    /**
     * <p>
     * Create a new uninitialized extension information object
     * </p>
     */
    public ExtensionInfo() {
    }

    /**
     * <p>
     * Create and initialize an extension information object.
     * The initialization uses the attributes passed as being
     * the content of a manifest file to load the extension
     * information from.
     * Since manifest file may contain information on several
     * extension they may depend on, the extension key parameter
     * is prepanded to the attribute name to make the key used
     * to retrieve the attribute from the manifest file
     * <p>
     * @param extensionKey unique extension key in the manifest
     * @param attr Attributes of a manifest file
     */
    public ExtensionInfo(String extensionKey, Attributes attr)
        throws NullPointerException
    {
        String s;
        if (extensionKey!=null) {
            s = extensionKey + "-";
        } else {
            s ="";
        }

        String attrKey = s + Name.EXTENSION_NAME.toString();
        name = attr.getValue(attrKey);
        if (name != null)
            name = name.trim();

        attrKey = s + Name.SPECIFICATION_TITLE.toString();
        title = attr.getValue(attrKey);
        if (title != null)
            title = title.trim();

        attrKey = s + Name.SPECIFICATION_VERSION.toString();
        specVersion = attr.getValue(attrKey);
        if (specVersion != null)
            specVersion = specVersion.trim();

        attrKey = s + Name.SPECIFICATION_VENDOR.toString();
        specVendor = attr.getValue(attrKey);
        if (specVendor != null)
            specVendor = specVendor.trim();

        attrKey = s + Name.IMPLEMENTATION_VERSION.toString();
        implementationVersion = attr.getValue(attrKey);
        if (implementationVersion != null)
            implementationVersion = implementationVersion.trim();

        attrKey = s + Name.IMPLEMENTATION_VENDOR.toString();
        vendor = attr.getValue(attrKey);
        if (vendor != null)
            vendor = vendor.trim();

        attrKey = s + Name.IMPLEMENTATION_VENDOR_ID.toString();
        vendorId = attr.getValue(attrKey);
        if (vendorId != null)
            vendorId = vendorId.trim();

        attrKey =s + Name.IMPLEMENTATION_URL.toString();
        url = attr.getValue(attrKey);
        if (url != null)
            url = url.trim();
    }

    /**
     * <p>
     * @return true if the extension described by this extension information
     * is compatible with the extension described by the extension
     * information passed as a parameter
     * </p>
     *
     * @param the requested extension information to compare to
     */
    public int isCompatibleWith(ExtensionInfo ei) {

        if (name == null || ei.name == null)
            return INCOMPATIBLE;
        if (name.compareTo(ei.name)==0) {
            // is this true, if not spec version is specified, we consider
            // the value as being "any".
            if (specVersion == null || ei.specVersion == null)
                return COMPATIBLE;

            int version = compareExtensionVersion(specVersion, ei.specVersion);
            if (version<0) {
                // this extension specification is "older"
                if (vendorId != null && ei.vendorId !=null) {
                    if (vendorId.compareTo(ei.vendorId)!=0) {
                        return REQUIRE_VENDOR_SWITCH;
                    }
                }
                return REQUIRE_SPECIFICATION_UPGRADE;
            } else {
                // the extension spec is compatible, let's look at the
                // implementation attributes
                if (vendorId != null && ei.vendorId != null) {
                    // They care who provides the extension
                    if (vendorId.compareTo(ei.vendorId)!=0) {
                        // They want to use another vendor implementation
                        return REQUIRE_VENDOR_SWITCH;
                    } else {
                        // Vendor matches, let's see the implementation version
                        if (implementationVersion != null && ei.implementationVersion != null) {
                            // they care about the implementation version
                            version = compareExtensionVersion(implementationVersion, ei.implementationVersion);
                            if (version<0) {
                                // This extension is an older implementation
                                return REQUIRE_IMPLEMENTATION_UPGRADE;
                            }
                        }
                    }
                }
                // All othe cases, we consider the extensions to be compatible
                return COMPATIBLE;
            }
        }
        return INCOMPATIBLE;
    }

    /**
     * <p>
     * helper method to print sensible information on the undelying described
     * extension
     * </p>
     */
    public String toString() {
        return "Extension : title(" + title + "), name(" + name + "), spec vendor(" +
            specVendor + "), spec version(" + specVersion + "), impl vendor(" +
            vendor + "), impl vendor id(" + vendorId + "), impl version(" +
            implementationVersion + "), impl url(" + url + ")";
    }

    /*
     * <p>
     * helper method to compare two versions.
     * version are in the x.y.z.t pattern.
     * </p>
     * @param source version to compare to
     * @param target version used to compare against
     * @return < 0 if source < version
     *         > 0 if source > version
     *         = 0 if source = version
     */
    private int compareExtensionVersion(String source, String target)
        throws NumberFormatException
    {
        source = source.toLowerCase();
        target = target.toLowerCase();

        return strictCompareExtensionVersion(source, target);
    }


    /*
     * <p>
     * helper method to compare two versions.
     * version are in the x.y.z.t pattern.
     * </p>
     * @param source version to compare to
     * @param target version used to compare against
     * @return < 0 if source < version
     *         > 0 if source > version
     *         = 0 if source = version
     */
    private int strictCompareExtensionVersion(String source, String target)
        throws NumberFormatException
    {
        if (source.equals(target))
            return 0;

        StringTokenizer stk = new StringTokenizer(source, ".,");
        StringTokenizer ttk = new StringTokenizer(target, ".,");

        // Compare number
        int n = 0, m = 0, result = 0;

        // Convert token into meaning number for comparision
        if (stk.hasMoreTokens())
            n = convertToken(stk.nextToken().toString());

        // Convert token into meaning number for comparision
        if (ttk.hasMoreTokens())
            m = convertToken(ttk.nextToken().toString());

        if (n > m)
            return 1;
        else if (m > n)
            return -1;
        else
        {
            // Look for index of "." in the string
            int sIdx = source.indexOf(".");
            int tIdx = target.indexOf(".");

            if (sIdx == -1)
                sIdx = source.length() - 1;

            if (tIdx == -1)
                tIdx = target.length() - 1;

            return strictCompareExtensionVersion(source.substring(sIdx + 1),
                                                 target.substring(tIdx + 1));
        }
    }

    private int convertToken(String token)
    {
        if (token == null || token.equals(""))
            return 0;

        int charValue = 0;
        int charVersion = 0;
        int patchVersion = 0;
        int strLength = token.length();
        int endIndex = strLength;
        char lastChar;

        Object[] args = {name};
        MessageFormat mf = new MessageFormat(rb.getString("optpkg.versionerror"));
        String versionError = mf.format(args);

        // Look for "-" for pre-release
        int prIndex = token.indexOf("-");

        // Look for "_" for patch release
        int patchIndex = token.indexOf("_");

        if (prIndex == -1 && patchIndex == -1)
        {
            // This is a FCS release
            try {
                return Integer.parseInt(token) * 100;
            } catch (NumberFormatException e) {
                System.out.println(versionError);
                return 0;
            }
        }
        else if (patchIndex != -1)
        {
            // This is a patch (update) release
            int prversion;
            try {
                // Obtain the version
                prversion = Integer.parseInt(token.substring(0, patchIndex));

                // Check to see if the patch version is in the n.n.n_nnl format (special release)
                lastChar = token.charAt(strLength-1);
                if (Character.isLetter(lastChar)) {
                    // letters a-z have values from 10-35
                    charValue = Character.getNumericValue(lastChar);
                    endIndex = strLength-1;

                    // Obtain the patch version id
                    patchVersion = Integer.parseInt(token.substring(patchIndex+1, endIndex));

                    if (charValue >= Character.getNumericValue('a') && charValue <= Character.getNumericValue('z')) {
                        // This is a special release
                        charVersion = (patchVersion * 100) + charValue;
                    } else {
                        // character is not a a-z letter, ignore
                        charVersion = 0;
                        System.out.println(versionError);
                    }
                } else {
                    // This is a regular update release. Obtain the patch version id
                    patchVersion = Integer.parseInt(token.substring(patchIndex+1, endIndex));
                }
            } catch (NumberFormatException e) {
                System.out.println(versionError);
                return 0;
            }
            return prversion * 100 + (patchVersion + charVersion);
        }
        else
        {
            //This is a milestone release, either a early access, alpha, beta, or RC

            // Obtain the version
            int mrversion;
            try {
                mrversion = Integer.parseInt(token.substring(0, prIndex));
            } catch (NumberFormatException e) {
                System.out.println(versionError);
                return 0;
            }

            // Obtain the patch version string, including the milestone + version
            String prString = token.substring(prIndex + 1);

            // Milestone version
            String msVersion = "";
            int delta = 0;

            if (prString.indexOf("ea") != -1)
            {
                msVersion = prString.substring(2);
                delta = 50;
            }
            else if (prString.indexOf("alpha") != -1)
            {
                msVersion = prString.substring(5);
                delta = 40;
            }
            else if (prString.indexOf("beta") != -1)
            {
                msVersion = prString.substring(4);
                delta = 30;
            }
            else if (prString.indexOf("rc") != -1)
            {
                msVersion = prString.substring(2);
                delta = 20;
            }

            if (msVersion == null || msVersion.equals(""))
            {
                // No version after the milestone, assume 0
                return mrversion * 100 - delta ;
            }
            else
            {
                // Convert the milestone version
                try {
                    return mrversion * 100 - delta + Integer.parseInt(msVersion);
                } catch (NumberFormatException e) {
                    System.out.println(versionError);
                    return 0;
                }
            }
        }
    }
}
