/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.jcr2spi.nodetype;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;

import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QItemDefinition;
import org.apache.jackrabbit.spi.QNodeDefinition;
import org.apache.jackrabbit.spi.QNodeTypeDefinition;
import org.apache.jackrabbit.spi.QPropertyDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An <code>EffectiveNodeType</code> represents one or more
 * <code>NodeType</code>s as one 'effective' node type where inheritance
 * is resolved.
 * <p/>
 * Instances of <code>EffectiveNodeType</code> are immutable.
 */
public class EffectiveNodeTypeImpl implements Cloneable, EffectiveNodeType {
    private static Logger log = LoggerFactory.getLogger(EffectiveNodeTypeImpl.class);

    // list of explicitly aggregated {i.e. merged) node types
    private final TreeSet<Name> mergedNodeTypes = new TreeSet<Name>();
    // list of implicitly aggregated {through inheritance) node types
    private final TreeSet<Name> inheritedNodeTypes = new TreeSet<Name>();
    // list of all either explicitly (through aggregation) or implicitly
    // (through inheritance) included node types.
    private final TreeSet<Name> allNodeTypes = new TreeSet<Name>();
    // map of named item definitions (maps name to list of definitions)
    private final Map<Name, List<QItemDefinition>> namedItemDefs = new HashMap<Name, List<QItemDefinition>>();
    // list of unnamed item definitions (i.e. residual definitions)
    private final List<QItemDefinition> unnamedItemDefs = new ArrayList<QItemDefinition>();
    // (optional) set of additional mixins supported on node type
    private Set<Name> supportedMixins;

    /**
     * constructor.
     */
    EffectiveNodeTypeImpl(TreeSet<Name> mergedNodeTypes, TreeSet<Name> inheritedNodeTypes,
                          TreeSet<Name> allNodeTypes, Map<Name, List<QItemDefinition>> namedItemDefs,
                          List<QItemDefinition> unnamedItemDefs, Set<Name> supportedMixins) {
        this.mergedNodeTypes.addAll(mergedNodeTypes);
        this.inheritedNodeTypes.addAll(inheritedNodeTypes);
        this.allNodeTypes.addAll(allNodeTypes);
        for (Map.Entry<Name, List<QItemDefinition>> entry : namedItemDefs.entrySet()) {
            this.namedItemDefs.put(entry.getKey(), new ArrayList<QItemDefinition>(entry.getValue()));
        }
        this.unnamedItemDefs.addAll(unnamedItemDefs);

        if (supportedMixins != null) {
            this.supportedMixins = new HashSet<Name>();
            this.supportedMixins.addAll(supportedMixins);
        }
    }

    //--------------------------------------------------< EffectiveNodeType >---
    /**
     * @see EffectiveNodeType#getInheritedNodeTypes()
     */
    public Name[] getInheritedNodeTypes() {
        return inheritedNodeTypes.toArray(new Name[inheritedNodeTypes.size()]);
    }

    /**
     * @see EffectiveNodeType#getAllNodeTypes()
     */
    public Name[] getAllNodeTypes() {
        return allNodeTypes.toArray(new Name[allNodeTypes.size()]);
    }

    /**
     * @see EffectiveNodeType#getMergedNodeTypes()
     */
    public Name[] getMergedNodeTypes() {
        return mergedNodeTypes.toArray(new Name[mergedNodeTypes.size()]);
    }

    /**
     * @see EffectiveNodeType#getAllQNodeDefinitions()
     */
    public QNodeDefinition[] getAllQNodeDefinitions() {
        if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size() + unnamedItemDefs.size());
        for (QItemDefinition qDef : unnamedItemDefs) {
            if (qDef.definesNode()) {
                defs.add(qDef);
            }
        }

        for (List<QItemDefinition> list : namedItemDefs.values()) {
            for (QItemDefinition qDef : list) {
                if (qDef.definesNode()) {
                    defs.add(qDef);
                }
            }
        }
        if (defs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QNodeDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getAllQPropertyDefinitions()
     */
    public QPropertyDefinition[] getAllQPropertyDefinitions() {
        if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size() + unnamedItemDefs.size());
        for (QItemDefinition qDef : unnamedItemDefs) {
            if (!qDef.definesNode()) {
                defs.add(qDef);
            }
        }
        for (List<QItemDefinition> list : namedItemDefs.values()) {
            for (QItemDefinition qDef : list) {
                if (!qDef.definesNode()) {
                    defs.add(qDef);
                }
            }
        }
        if (defs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QPropertyDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getAutoCreateQNodeDefinitions()
     */
    public QNodeDefinition[] getAutoCreateQNodeDefinitions() {
        // since auto-create items must have a name,
        // we're only searching the named item definitions
        if (namedItemDefs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size());
        for (List<QItemDefinition> list : namedItemDefs.values()) {
            for (QItemDefinition qDef : list) {
                if (qDef.definesNode() && qDef.isAutoCreated()) {
                    defs.add(qDef);
                }
            }
        }
        if (defs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QNodeDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getAutoCreateQPropertyDefinitions()
     */
    public QPropertyDefinition[] getAutoCreateQPropertyDefinitions() {
        // since auto-create items must have a name,
        // we're only searching the named item definitions
        if (namedItemDefs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size());
        for (List<QItemDefinition> list : namedItemDefs.values()) {
            for (QItemDefinition qDef : list) {
                if (!qDef.definesNode() && qDef.isAutoCreated()) {
                    defs.add(qDef);
                }
            }
        }
        if (defs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QPropertyDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getMandatoryQPropertyDefinitions()
     */
    public QPropertyDefinition[] getMandatoryQPropertyDefinitions() {
        // since mandatory items must have a name,
        // we're only searching the named item definitions
        if (namedItemDefs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size());
        for (List<QItemDefinition> list : namedItemDefs.values()) {
            for (QItemDefinition qDef : list) {
                if (!qDef.definesNode() && qDef.isMandatory()) {
                    defs.add(qDef);
                }
            }
        }
        if (defs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QPropertyDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getMandatoryQNodeDefinitions()
     */
    public QNodeDefinition[] getMandatoryQNodeDefinitions() {
        // since mandatory items must have a name,
        // we're only searching the named item definitions
        if (namedItemDefs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size());
        for (List<QItemDefinition> list : namedItemDefs.values()) {
            for (QItemDefinition qDef : list) {
                if (qDef.definesNode() && qDef.isMandatory()) {
                    defs.add(qDef);
                }
            }
        }
        if (defs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QNodeDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getNamedQNodeDefinitions(Name)
     */
    public QNodeDefinition[] getNamedQNodeDefinitions(Name name) {
        List<QItemDefinition> list = namedItemDefs.get(name);
        if (list == null || list.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(list.size());
        for (QItemDefinition qDef : list) {
            if (qDef.definesNode()) {
                defs.add(qDef);
            }
        }
        if (defs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QNodeDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getUnnamedQNodeDefinitions()
     */
    public QNodeDefinition[] getUnnamedQNodeDefinitions() {
        if (unnamedItemDefs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(unnamedItemDefs.size());
        for (QItemDefinition qDef : unnamedItemDefs) {
            if (qDef.definesNode()) {
                defs.add(qDef);
            }
        }
        if (defs.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QNodeDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getNamedQPropertyDefinitions(Name)
     */
    public QPropertyDefinition[] getNamedQPropertyDefinitions(Name name) {
        List<QItemDefinition> list = namedItemDefs.get(name);
        if (list == null || list.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(list.size());
        for (QItemDefinition qDef : list) {
            if (!qDef.definesNode()) {
                defs.add(qDef);
            }
        }
        if (defs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QPropertyDefinition[defs.size()]);
    }

    /**
     * @see EffectiveNodeType#getUnnamedQPropertyDefinitions()
     */
    public QPropertyDefinition[] getUnnamedQPropertyDefinitions() {
        if (unnamedItemDefs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(unnamedItemDefs.size());
        for (QItemDefinition qDef : unnamedItemDefs) {
            if (!qDef.definesNode()) {
                defs.add(qDef);
            }
        }
        if (defs.size() == 0) {
            return QPropertyDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QPropertyDefinition[defs.size()]);
    }

    public boolean includesNodeType(Name nodeTypeName) {
        return allNodeTypes.contains(nodeTypeName);
    }

    public boolean includesNodeTypes(Name[] nodeTypeNames) {
        return allNodeTypes.containsAll(Arrays.asList(nodeTypeNames));
    }

    /**
     * @see EffectiveNodeType#supportsMixin(Name)
     */
    public boolean supportsMixin(Name mixin) {
        if (supportedMixins == null) {
            return true;
        }
        else {
            return supportedMixins.contains(mixin);
        }
    }

    /**
     * @see EffectiveNodeType#checkAddNodeConstraints(Name, ItemDefinitionProvider)
     */
    public void checkAddNodeConstraints(Name name, ItemDefinitionProvider definitionProvider)
            throws ConstraintViolationException {
        try {
            definitionProvider.getQNodeDefinition(this, name, null);
        } catch (NoSuchNodeTypeException e) {
            String msg = "internal error: inconsistent node type";
            log.debug(msg);
            throw new ConstraintViolationException(msg, e);
        }
    }

    /**
     * @see EffectiveNodeType#checkAddNodeConstraints(org.apache.jackrabbit.spi.Name,QNodeTypeDefinition, ItemDefinitionProvider)
     */
    public void checkAddNodeConstraints(Name name, QNodeTypeDefinition nodeTypeDefinition, ItemDefinitionProvider definitionProvider)
            throws ConstraintViolationException, NoSuchNodeTypeException {
        if (nodeTypeDefinition.isAbstract()) {
            throw new ConstraintViolationException("not allowed to add node " + name + ": " + nodeTypeDefinition.getName() + " is abstract and cannot be used as primary node type.");
        }
        if (nodeTypeDefinition.isMixin()) {
            throw new ConstraintViolationException("not allowed to add node " + name + ":" + nodeTypeDefinition.getName() + " is a mixin and cannot be used as primary node type.");
        }
        QNodeDefinition nd = definitionProvider.getQNodeDefinition(this, name, nodeTypeDefinition.getName());
        if (nd.isProtected()) {
            throw new ConstraintViolationException(name + " is protected.");
        }
        if (nd.isAutoCreated()) {
            throw new ConstraintViolationException(name + " is auto-created and can not be manually added");
        }
    }

    /**
     * @see EffectiveNodeType#checkRemoveItemConstraints(Name)
     */
    public void checkRemoveItemConstraints(Name name) throws ConstraintViolationException {
        /**
         * as there might be multiple definitions with the same name and we
         * don't know which one is applicable, we check all of them
         */
        QItemDefinition[] defs = getNamedItemDefs(name);
        if (hasRemoveConstraint(defs)) {
            throw new ConstraintViolationException("can't remove mandatory or protected item");
        }
    }

    /**
     * @see EffectiveNodeType#hasRemoveNodeConstraint(Name)
     */
    public boolean hasRemoveNodeConstraint(Name nodeName) {
        QNodeDefinition[] defs = getNamedQNodeDefinitions(nodeName);
        return hasRemoveConstraint(defs);
    }

    /**
     * @see EffectiveNodeType#hasRemovePropertyConstraint(Name)
     */
    public boolean hasRemovePropertyConstraint(Name propertyName) {
        QPropertyDefinition[] defs = getNamedQPropertyDefinitions(propertyName);
        return hasRemoveConstraint(defs);
    }

    //---------------------------------------------< impl. specific methods >---
    /**
     * Loop over the specified definitions and return <code>true</code> as soon
     * as the first mandatory or protected definition is encountered.
     *
     * @param defs
     * @return <code>true</code> if a mandatory or protected definition is present.
     */
    private static boolean hasRemoveConstraint(QItemDefinition[] defs) {
        /**
         * as there might be multiple definitions with the same name that may be
         * applicable, return true as soon as the first mandatory or protected
         * definition is encountered.
         */
        if (defs != null) {
            for (int i = 0; i < defs.length; i++) {
                if (defs[i].isMandatory()) {
                    return true;
                }
                if (defs[i].isProtected()) {
                    return true;
                }
            }
        }
        return false;
    }

    private QItemDefinition[] getNamedItemDefs() {
        if (namedItemDefs.size() == 0) {
            return QItemDefinition.EMPTY_ARRAY;
        }
        ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size());
        for (List<QItemDefinition> list : namedItemDefs.values()) {
            defs.addAll(list);
        }
        if (defs.size() == 0) {
            return QItemDefinition.EMPTY_ARRAY;
        }
        return defs.toArray(new QItemDefinition[defs.size()]);
    }

    private QItemDefinition[] getNamedItemDefs(Name name) {
        List<QItemDefinition> list = namedItemDefs.get(name);
        if (list == null || list.size() == 0) {
            return QNodeDefinition.EMPTY_ARRAY;
        }
        return list.toArray(new QItemDefinition[list.size()]);
    }

    private QItemDefinition[] getUnnamedItemDefs() {
        if (unnamedItemDefs.size() == 0) {
            return QItemDefinition.EMPTY_ARRAY;
        }
        return unnamedItemDefs.toArray(new QItemDefinition[unnamedItemDefs.size()]);
    }

    /**
     * Merges another <code>EffectiveNodeType</code> with this one.
     * Checks for merge conflicts.
     *
     * @param other
     * @return
     * @throws ConstraintViolationException
     */
    EffectiveNodeTypeImpl merge(EffectiveNodeTypeImpl other)
            throws ConstraintViolationException {
        // create a clone of this instance and perform the merge on
        // the 'clone' to avoid a potentially inconsistent state
        // of this instance if an exception is thrown during
        // the merge.
        EffectiveNodeTypeImpl copy = (EffectiveNodeTypeImpl) clone();
        copy.internalMerge(other, false);
        return copy;
    }

    /**
     * Internal helper method which merges another <code>EffectiveNodeType</code>
     * instance with <i>this</i> instance.
     * <p/>
     * Warning: This instance might be in an inconsistent state if an exception
     * is thrown.
     *
     * @param other
     * @param supertype true if the merge is a result of inheritance, i.e. <code>other</code>
     *                  represents one or more supertypes of this instance; otherwise false, i.e.
     *                  the merge is the result of an explicit aggregation
     * @throws ConstraintViolationException
     */
    synchronized void internalMerge(EffectiveNodeTypeImpl other, boolean supertype)
            throws ConstraintViolationException {
        Name[] nta = other.getAllNodeTypes();
        int includedCount = 0;
        for (int i = 0; i < nta.length; i++) {
            if (includesNodeType(nta[i])) {
                // redundant node type
                log.debug("node type '" + nta[i] + "' is already contained.");
                includedCount++;
            }
        }
        if (includedCount == nta.length) {
            // total overlap, ignore
            return;
        }

        // named item definitions
        QItemDefinition[] defs = other.getNamedItemDefs();
        for (int i = 0; i < defs.length; i++) {
            QItemDefinition qDef = defs[i];
            if (includesNodeType(qDef.getDeclaringNodeType())) {
                // ignore redundant definitions
                continue;
            }
            Name name = qDef.getName();
            List<QItemDefinition> existingDefs = namedItemDefs.get(name);
            if (existingDefs != null) {
                if (existingDefs.size() > 0) {
                    // there already exists at least one definition with that name
                    for (int j = 0; j < existingDefs.size(); j++) {
                        QItemDefinition qItemDef = existingDefs.get(j);
                        // make sure none of them is auto-create
                        if (qDef.isAutoCreated() || qItemDef.isAutoCreated()) {
                            // conflict
                            String msg = "The item definition for '" + name
                                    + "' in node type '"
                                    + qDef.getDeclaringNodeType()
                                    + "' conflicts with the one of node type '"
                                    + qItemDef.getDeclaringNodeType()
                                    + "': name collision with auto-create definition";
                            log.debug(msg);
                            throw new ConstraintViolationException(msg);
                        }
                        // check ambiguous definitions
                        if (qDef.definesNode() == qItemDef.definesNode()) {
                            if (!qDef.definesNode()) {
                                // property definition
                                QPropertyDefinition pd = (QPropertyDefinition) qDef;
                                QPropertyDefinition epd = (QPropertyDefinition) qItemDef;
                                // compare type & multiValued flag
                                if (pd.getRequiredType() == epd.getRequiredType()
                                        && pd.isMultiple() == epd.isMultiple()) {
                                    // conflict
                                    String msg = "The property definition for '"
                                            + name + "' in node type '"
                                            + qDef.getDeclaringNodeType()
                                            + "' conflicts with the one of node type '"
                                            + qItemDef.getDeclaringNodeType()
                                            + "': ambiguous property definition. "
                                            + "they must differ in required type "
                                            + "or cardinality.";
                                    log.debug(msg);
                                    throw new ConstraintViolationException(msg);
                                }
                            } else {
                                // child node definition
                                // conflict
                                String msg = "The child node definition for '"
                                        + name + "' in node type '"
                                        + qDef.getDeclaringNodeType()
                                        + "' conflicts with the one of node type '"
                                        + qItemDef.getDeclaringNodeType()
                                        + "': ambiguous child node definition. name must differ.";
                                log.debug(msg);
                                throw new ConstraintViolationException(msg);
                            }
                        }
                    }
                }
            } else {
                existingDefs = new ArrayList<QItemDefinition>();
                namedItemDefs.put(name, existingDefs);
            }
            existingDefs.add(qDef);
        }

        // residual item definitions
        defs = other.getUnnamedItemDefs();
        for (int i = 0; i < defs.length; i++) {
            QItemDefinition qDef = defs[i];
            if (includesNodeType(qDef.getDeclaringNodeType())) {
                // ignore redundant definitions
                continue;
            }
            for (QItemDefinition existing : unnamedItemDefs) {
                // compare with existing definition
                if (qDef.definesNode() == existing.definesNode()) {
                    if (!qDef.definesNode()) {
                        // property definition
                        QPropertyDefinition pd = (QPropertyDefinition) qDef;
                        QPropertyDefinition epd = (QPropertyDefinition) existing;
                        // compare type & multiValued flag
                        if (pd.getRequiredType() == epd.getRequiredType()
                                && pd.isMultiple() == epd.isMultiple()
                                && pd.getOnParentVersion() == epd.getOnParentVersion()) {
                            // conflict
                            // TODO: need to take more aspects into account
                            // TODO: getMatchingPropDef needs to check this as well
                            String msg = "A property definition in node type '"
                                    + qDef.getDeclaringNodeType()
                                    + "' conflicts with node type '"
                                    + existing.getDeclaringNodeType()
                                    + "': ambiguous residual property definition";
                            log.debug(msg);
                            throw new ConstraintViolationException(msg);
                        }
                    } else {
                        // child node definition
                        QNodeDefinition nd = (QNodeDefinition) qDef;
                        QNodeDefinition end = (QNodeDefinition) existing;
                        // compare required & default primary types
                        if (Arrays.equals(nd.getRequiredPrimaryTypes(), end.getRequiredPrimaryTypes())
                                && (nd.getDefaultPrimaryType() == null
                                ? end.getDefaultPrimaryType() == null
                                : nd.getDefaultPrimaryType().equals(end.getDefaultPrimaryType()))) {
                            // conflict
                            String msg = "A child node definition in node type '"
                                    + qDef.getDeclaringNodeType()
                                    + "' conflicts with node type '"
                                    + existing.getDeclaringNodeType()
                                    + "': ambiguous residual child node definition";
                            log.debug(msg);
                            throw new ConstraintViolationException(msg);
                        }
                    }
                }
            }
            unnamedItemDefs.add(qDef);
        }
        for (int i = 0; i < nta.length; i++) {
            allNodeTypes.add(nta[i]);
        }

        if (supertype) {
            // implicit merge as result of inheritance

            // add other merged node types as supertypes
            nta = other.getMergedNodeTypes();
            for (int i = 0; i < nta.length; i++) {
                inheritedNodeTypes.add(nta[i]);
            }
            // add supertypes of other merged node types as supertypes
            nta = other.getInheritedNodeTypes();
            for (int i = 0; i < nta.length; i++) {
                inheritedNodeTypes.add(nta[i]);
            }
        } else {
            // explicit merge

            // merge with other merged node types
            nta = other.getMergedNodeTypes();
            for (int i = 0; i < nta.length; i++) {
                mergedNodeTypes.add(nta[i]);
            }
            // add supertypes of other merged node types as supertypes
            nta = other.getInheritedNodeTypes();
            for (int i = 0; i < nta.length; i++) {
                inheritedNodeTypes.add(nta[i]);
            }
        }
    }

    @Override
    protected Object clone() {
        EffectiveNodeTypeImpl clone = new EffectiveNodeTypeImpl(mergedNodeTypes,
                inheritedNodeTypes, allNodeTypes, namedItemDefs, unnamedItemDefs,
                supportedMixins);
        return clone;
    }
}
