/* * 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.core.nodetype; import org.apache.jackrabbit.spi.Name; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; import java.util.ArrayList; /** * <code>EffectiveNodeTypeCache</code> implementation that uses an array of * node type names as key for caching the effective node types. */ public class EffectiveNodeTypeCacheImpl implements EffectiveNodeTypeCache { /** * ordered set of keys */ private final TreeSet<Key> sortedKeys; /** * cache of pre-built aggregations of node types */ private final HashMap<Key, EffectiveNodeType> aggregates; /** * Creates a new effective node type cache. */ EffectiveNodeTypeCacheImpl() { sortedKeys = new TreeSet<Key>(); aggregates = new HashMap<Key, EffectiveNodeType>(); } /** * {@inheritDoc} */ public Key getKey(Name[] ntNames) { return new WeightedKey(ntNames); } /** * {@inheritDoc} */ public void put(EffectiveNodeType ent) { // we define the weight as the total number of included node types // (through aggregation and inheritance) int weight = ent.getMergedNodeTypes().length; // the effective node type is identified by the list of merged // (i.e. aggregated) node types WeightedKey k = new WeightedKey(ent.getMergedNodeTypes(), weight); put(k, ent); } /** * {@inheritDoc} */ public void put(Key key, EffectiveNodeType ent) { aggregates.put(key, ent); sortedKeys.add(key); } /** * {@inheritDoc} */ public boolean contains(Key key) { return aggregates.containsKey(key); } /** * {@inheritDoc} */ public EffectiveNodeType get(Key key) { return aggregates.get(key); } /** * Removes the effective node type for the given key from the cache. * @param key the key of the effective node type to remove * @return the removed effective node type or <code>null</code> if it was * never cached. */ private EffectiveNodeType remove(Key key) { EffectiveNodeType removed = aggregates.remove(key); if (removed != null) { // remove index entry // FIXME: can't simply call TreeSet.remove(key) because the entry // in sortedKeys might have a different weight and would thus // not be found Iterator<Key> iter = sortedKeys.iterator(); while (iter.hasNext()) { Key k = iter.next(); // WeightedKey.equals(Object) ignores the weight if (key.equals(k)) { sortedKeys.remove(k); break; } } } return removed; } /** * {@inheritDoc} */ public void invalidate(Name name) { // remove all affected effective node types from aggregates cache // (copy keys first to prevent ConcurrentModificationException) ArrayList<Key> keys = new ArrayList<Key>(sortedKeys); for (Iterator<Key> keysIter = keys.iterator(); keysIter.hasNext();) { Key k = keysIter.next(); EffectiveNodeType ent = get(k); if (ent.includesNodeType(name)) { remove(k); } } } /** * {@inheritDoc} */ public Key findBest(Key key) { // quick check for already cached key if (contains(key)) { return key; } Iterator<Key> iter = sortedKeys.iterator(); while (iter.hasNext()) { Key k = iter.next(); // check if the existing aggregate is a 'subset' of the one we're // looking for if (key.contains(k)) { return k; } } return null; } //-------------------------------------------< java.lang.Object overrides > /** * {@inheritDoc} */ @Override public Object clone() { EffectiveNodeTypeCacheImpl clone = new EffectiveNodeTypeCacheImpl(); clone.sortedKeys.addAll(sortedKeys); clone.aggregates.putAll(aggregates); return clone; } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public String toString() { StringBuilder builder = new StringBuilder(); builder.append("EffectiveNodeTypeCache (" + super.toString() + ")\n"); builder.append("EffectiveNodeTypes in cache:\n"); for (Key key : sortedKeys) { builder.append(key); builder.append("\n"); } return builder.toString(); } //--------------------------------------------------------< inner classes > /** * A <code>WeightedKey</code> uniquely identifies * a combination (i.e. an aggregation) of one or more node types. * The weight is an indicator for the cost involved in building such an * aggregate (e.g. an aggregation of multiple complex node types with deep * inheritance trees is more costly to build/validate than an aggregation * of two very simple node types with just one property definition each). * <p> * A very simple (and not very accurate) approximation of the weight would * be the number of explicitly aggregated node types (ignoring inheritance * and complexity of each involved node type). A better approximation would * be the number of <b>all</b>, explicitly and implicitly (note that * inheritance is also an aggregation) aggregated node types. * <p> * The more accurate the weight definition, the more efficient is the * the building of new aggregates. * <p> * It is important to note that the weight is not part of the key value, * i.e. it is not considered by the <code>hashCode()</code> and * <code>equals(Object)</code> methods. It does however affect the order * of <code>WeightedKey</code> instances. See * <code>{@link #compareTo(Object)}</code> for more information. * <p> * Let's assume we have an aggregation of node types named "b", "a" and "c". * Its key would be "[a, b, c]" and the weight 3 (using the simple * approximation). */ private static class WeightedKey implements Key { /** * array of node type names, sorted in ascending order */ private final Name[] names; /** * the weight of this key */ private final int weight; /** * @param ntNames */ WeightedKey(Name[] ntNames) { this(ntNames, ntNames.length); } /** * @param ntNames * @param weight */ WeightedKey(Name[] ntNames, int weight) { this.weight = weight; names = new Name[ntNames.length]; System.arraycopy(ntNames, 0, names, 0, names.length); Arrays.sort(names); } /** * @param ntNames */ WeightedKey(Collection<Name> ntNames) { this(ntNames, ntNames.size()); } /** * @param ntNames * @param weight */ WeightedKey(Collection<Name> ntNames, int weight) { this((Name[]) ntNames.toArray(new Name[ntNames.size()]), weight); } /** * @return the node type names of this key */ public Name[] getNames() { return names; } /** * {@inheritDoc} */ public boolean contains(Key otherKey) { WeightedKey key = (WeightedKey) otherKey; Set<Name> tmp = new HashSet<Name>(Arrays.asList(names)); for (int i = 0; i < key.names.length; i++) { if (!tmp.contains(key.names[i])) { return false; } } return true; } /** * {@inheritDoc} */ public Key subtract(Key otherKey) { WeightedKey key = (WeightedKey) otherKey; Set<Name> tmp = new HashSet<Name>(Arrays.asList(names)); tmp.removeAll(Arrays.asList(key.names)); return new WeightedKey(tmp); } //-------------------------------------------------------< Comparable > /** * The resulting sort-order is: 1. descending weight, 2. ascending key * (i.e. string representation of this sorted set). * * @param o the other key to compare * @return the result of the comparison */ public int compareTo(Key o) { WeightedKey other = (WeightedKey) o; // compare weights if (weight > other.weight) { return -1; } else if (weight < other.weight) { return 1; } // compare arrays of names int len1 = names.length; int len2 = other.names.length; int len = Math.min(len1, len2); for (int i = 0; i < len; i++) { Name name1 = names[i]; Name name2 = other.names[i]; int result = name1.compareTo(name2); if (result != 0) { return result; } } return len1 - len2; } //---------------------------------------< java.lang.Object overrides > /** * {@inheritDoc} */ public int hashCode() { int h = 17; // ignore weight for (Name name : names) { h *= 37; h += name.hashCode(); } return h; } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof WeightedKey) { WeightedKey other = (WeightedKey) obj; // ignore weight return Arrays.equals(names, other.names); } return false; } /** * {@inheritDoc} */ public String toString() { return Arrays.asList(names).toString() + " (" + weight + ")"; } } }