/*
 * Copyright (c) 2003, 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.security.pkcs11;

import java.util.*;
import java.util.concurrent.*;

import sun.security.pkcs11.wrapper.*;
import static sun.security.pkcs11.wrapper.PKCS11Constants.*;

/**
 * TemplateManager class.
 *
 * Not all PKCS#11 tokens are created equal. One token may require that one
 * value is specified when creating a certain type of object. Another token
 * may require a different value. Yet another token may only work if the
 * attribute is not specified at all.
 *
 * In order to allow an application to work unmodified with all those
 * different tokens, the SunPKCS11 provider makes the attributes that are
 * specified and their value configurable. Hence, only the SunPKCS11
 * configuration file has to be tweaked at deployment time to allow all
 * existing applications to be used.
 *
 * The template manager is responsible for reading the attribute configuration
 * information and to make it available to the various internal components
 * of the SunPKCS11 provider.
 *
 * @author  Andreas Sterbenz
 * @since   1.5
 */
final class TemplateManager {

    private final static boolean DEBUG = false;

    // constant for any operation (either O_IMPORT or O_GENERATE)
    final static String O_ANY      = "*";
    // constant for operation create ("importing" existing key material)
    final static String O_IMPORT   = "import";
    // constant for operation generate (generating new key material)
    final static String O_GENERATE = "generate";

    private static class KeyAndTemplate {
        final TemplateKey key;
        final Template template;

        KeyAndTemplate(TemplateKey key, Template template) {
            this.key = key;
            this.template = template;
        }
    }

    // primitive templates contains the individual template configuration
    // entries from the configuration file
    private final List<KeyAndTemplate> primitiveTemplates;

    // composite templates is a cache of the exact configuration template for
    // each specific TemplateKey (no wildcards). the entries are created
    // on demand during first use by compositing all applicable
    // primitive template entries. the result is then stored in this map
    // for performance
    private final Map<TemplateKey,Template> compositeTemplates;

    TemplateManager() {
        primitiveTemplates = new ArrayList<KeyAndTemplate>();
        compositeTemplates = new ConcurrentHashMap<TemplateKey,Template>();
    }

    // add a template. Called by Config.
    void addTemplate(String op, long objectClass, long keyAlgorithm,
            CK_ATTRIBUTE[] attrs) {
        TemplateKey key = new TemplateKey(op, objectClass, keyAlgorithm);
        Template template = new Template(attrs);
        if (DEBUG) {
            System.out.println("Adding " + key + " -> " + template);
        }
        primitiveTemplates.add(new KeyAndTemplate(key, template));
    }

    private Template getTemplate(TemplateKey key) {
        Template template = compositeTemplates.get(key);
        if (template == null) {
            template = buildCompositeTemplate(key);
            compositeTemplates.put(key, template);
        }
        return template;
    }

    // Get the attributes for the requested op and combine them with attrs.
    // This is the method called by the implementation to obtain the
    // attributes.
    CK_ATTRIBUTE[] getAttributes(String op, long type, long alg,
            CK_ATTRIBUTE[] attrs) {
        TemplateKey key = new TemplateKey(op, type, alg);
        Template template = getTemplate(key);
        CK_ATTRIBUTE[] newAttrs = template.getAttributes(attrs);
        if (DEBUG) {
            System.out.println(key + " -> " + Arrays.asList(newAttrs));
        }
        return newAttrs;
    }

    // build a composite template for the given key
    private Template buildCompositeTemplate(TemplateKey key) {
        Template comp = new Template();
        // iterate through primitive templates and add all that apply
        for (KeyAndTemplate entry : primitiveTemplates) {
            if (entry.key.appliesTo(key)) {
                comp.add(entry.template);
            }
        }
        return comp;
    }

    /**
     * Nested class representing a template identifier.
     */
    private static final class TemplateKey {
        final String operation;
        final long keyType;
        final long keyAlgorithm;
        TemplateKey(String operation, long keyType, long keyAlgorithm) {
            this.operation = operation;
            this.keyType = keyType;
            this.keyAlgorithm = keyAlgorithm;
        }
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof TemplateKey == false) {
                return false;
            }
            TemplateKey other = (TemplateKey)obj;
            boolean match = this.operation.equals(other.operation)
                        && (this.keyType == other.keyType)
                        && (this.keyAlgorithm == other.keyAlgorithm);
            return match;
        }
        public int hashCode() {
            return operation.hashCode() + (int)keyType + (int)keyAlgorithm;
        }
        boolean appliesTo(TemplateKey key) {
            if (operation.equals(O_ANY) || operation.equals(key.operation)) {
                if ((keyType == PCKO_ANY) || (keyType == key.keyType)) {
                    if ((keyAlgorithm == PCKK_ANY)
                                || (keyAlgorithm == key.keyAlgorithm)) {
                        return true;
                    }
                }
            }
            return false;
        }
        public String toString() {
            return "(" + operation + ","
                + Functions.getObjectClassName(keyType)
                + "," + Functions.getKeyName(keyAlgorithm) + ")";
        }
    }

    /**
     * Nested class representing template attributes.
     */
    private static final class Template {

        private final static CK_ATTRIBUTE[] A0 = new CK_ATTRIBUTE[0];

        private CK_ATTRIBUTE[] attributes;

        Template() {
            attributes = A0;
        }

        Template(CK_ATTRIBUTE[] attributes) {
            this.attributes = attributes;
        }

        void add(Template template) {
            attributes = getAttributes(template.attributes);
        }

        CK_ATTRIBUTE[] getAttributes(CK_ATTRIBUTE[] attrs) {
            return combine(attributes, attrs);
        }

        /**
         * Combine two sets of attributes. The second set has precedence
         * over the first and overrides its settings.
         */
        private static CK_ATTRIBUTE[] combine(CK_ATTRIBUTE[] attrs1,
                CK_ATTRIBUTE[] attrs2) {
            List<CK_ATTRIBUTE> attrs = new ArrayList<CK_ATTRIBUTE>();
            for (CK_ATTRIBUTE attr : attrs1) {
                if (attr.pValue != null) {
                    attrs.add(attr);
                }
            }
            for (CK_ATTRIBUTE attr2 : attrs2) {
                long type = attr2.type;
                for (CK_ATTRIBUTE attr1 : attrs1) {
                    if (attr1.type == type) {
                        attrs.remove(attr1);
                    }
                }
                if (attr2.pValue != null) {
                    attrs.add(attr2);
                }
            }
            return attrs.toArray(A0);
        }

        public String toString() {
            return Arrays.asList(attributes).toString();
        }

    }

}
