X-Git-Url: https://git.mdrn.pl/pylucene.git/blobdiff_plain/a2e61f0c04805cfcb8706176758d1283c7e3a55c..aaeed5504b982cf3545252ab528713250aa33eed:/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/util/AttributeSource.java?ds=sidebyside diff --git a/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/util/AttributeSource.java b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/util/AttributeSource.java new file mode 100644 index 0000000..6a14b90 --- /dev/null +++ b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/util/AttributeSource.java @@ -0,0 +1,536 @@ +package org.apache.lucene.util; + +/** + * 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. + */ + +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.WeakHashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.lucene.analysis.TokenStream; // for javadocs +import org.apache.lucene.analysis.tokenattributes.TermAttribute; +import org.apache.lucene.analysis.tokenattributes.CharTermAttributeImpl; + +/** + * An AttributeSource contains a list of different {@link AttributeImpl}s, + * and methods to add and get them. There can only be a single instance + * of an attribute in the same AttributeSource instance. This is ensured + * by passing in the actual type of the Attribute (Class<Attribute>) to + * the {@link #addAttribute(Class)}, which then checks if an instance of + * that type is already present. If yes, it returns the instance, otherwise + * it creates a new instance and returns it. + */ +public class AttributeSource { + /** + * An AttributeFactory creates instances of {@link AttributeImpl}s. + */ + public static abstract class AttributeFactory { + /** + * returns an {@link AttributeImpl} for the supplied {@link Attribute} interface class. + */ + public abstract AttributeImpl createAttributeInstance(Class attClass); + + /** + * This is the default factory that creates {@link AttributeImpl}s using the + * class name of the supplied {@link Attribute} interface class by appending Impl to it. + */ + public static final AttributeFactory DEFAULT_ATTRIBUTE_FACTORY = new DefaultAttributeFactory(); + + private static final class DefaultAttributeFactory extends AttributeFactory { + private static final WeakHashMap, WeakReference>> attClassImplMap = + new WeakHashMap, WeakReference>>(); + + private DefaultAttributeFactory() {} + + @Override + public AttributeImpl createAttributeInstance(Class attClass) { + try { + return getClassForInterface(attClass).newInstance(); + } catch (InstantiationException e) { + throw new IllegalArgumentException("Could not instantiate implementing class for " + attClass.getName()); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Could not instantiate implementing class for " + attClass.getName()); + } + } + + private static Class getClassForInterface(Class attClass) { + synchronized(attClassImplMap) { + final WeakReference> ref = attClassImplMap.get(attClass); + Class clazz = (ref == null) ? null : ref.get(); + if (clazz == null) { + try { + // TODO: Remove when TermAttribute is removed! + // This is a "sophisticated backwards compatibility hack" + // (enforce new impl for this deprecated att): + if (TermAttribute.class.equals(attClass)) { + clazz = CharTermAttributeImpl.class; + } else { + clazz = Class.forName(attClass.getName() + "Impl", true, attClass.getClassLoader()) + .asSubclass(AttributeImpl.class); + } + attClassImplMap.put(attClass, + new WeakReference>(clazz) + ); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Could not find implementing class for " + attClass.getName()); + } + } + return clazz; + } + } + } + } + + /** + * This class holds the state of an AttributeSource. + * @see #captureState + * @see #restoreState + */ + public static final class State implements Cloneable { + AttributeImpl attribute; + State next; + + @Override + public Object clone() { + State clone = new State(); + clone.attribute = (AttributeImpl) attribute.clone(); + + if (next != null) { + clone.next = (State) next.clone(); + } + + return clone; + } + } + + // These two maps must always be in sync!!! + // So they are private, final and read-only from the outside (read-only iterators) + private final Map, AttributeImpl> attributes; + private final Map, AttributeImpl> attributeImpls; + private final State[] currentState; + + private AttributeFactory factory; + + /** + * An AttributeSource using the default attribute factory {@link AttributeSource.AttributeFactory#DEFAULT_ATTRIBUTE_FACTORY}. + */ + public AttributeSource() { + this(AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY); + } + + /** + * An AttributeSource that uses the same attributes as the supplied one. + */ + public AttributeSource(AttributeSource input) { + if (input == null) { + throw new IllegalArgumentException("input AttributeSource must not be null"); + } + this.attributes = input.attributes; + this.attributeImpls = input.attributeImpls; + this.currentState = input.currentState; + this.factory = input.factory; + } + + /** + * An AttributeSource using the supplied {@link AttributeFactory} for creating new {@link Attribute} instances. + */ + public AttributeSource(AttributeFactory factory) { + this.attributes = new LinkedHashMap, AttributeImpl>(); + this.attributeImpls = new LinkedHashMap, AttributeImpl>(); + this.currentState = new State[1]; + this.factory = factory; + } + + /** + * returns the used AttributeFactory. + */ + public AttributeFactory getAttributeFactory() { + return this.factory; + } + + /** Returns a new iterator that iterates the attribute classes + * in the same order they were added in. + */ + public Iterator> getAttributeClassesIterator() { + return Collections.unmodifiableSet(attributes.keySet()).iterator(); + } + + /** Returns a new iterator that iterates all unique Attribute implementations. + * This iterator may contain less entries that {@link #getAttributeClassesIterator}, + * if one instance implements more than one Attribute interface. + */ + public Iterator getAttributeImplsIterator() { + final State initState = getCurrentState(); + if (initState != null) { + return new Iterator() { + private State state = initState; + + public void remove() { + throw new UnsupportedOperationException(); + } + + public AttributeImpl next() { + if (state == null) + throw new NoSuchElementException(); + final AttributeImpl att = state.attribute; + state = state.next; + return att; + } + + public boolean hasNext() { + return state != null; + } + }; + } else { + return Collections.emptySet().iterator(); + } + } + + /** a cache that stores all interfaces for known implementation classes for performance (slow reflection) */ + private static final WeakHashMap,LinkedList>>> knownImplClasses = + new WeakHashMap,LinkedList>>>(); + + static LinkedList>> getAttributeInterfaces(final Class clazz) { + synchronized(knownImplClasses) { + LinkedList>> foundInterfaces = knownImplClasses.get(clazz); + if (foundInterfaces == null) { + // we have a strong reference to the class instance holding all interfaces in the list (parameter "att"), + // so all WeakReferences are never evicted by GC + knownImplClasses.put(clazz, foundInterfaces = new LinkedList>>()); + // find all interfaces that this attribute instance implements + // and that extend the Attribute interface + Class actClazz = clazz; + do { + for (Class curInterface : actClazz.getInterfaces()) { + if (curInterface != Attribute.class && Attribute.class.isAssignableFrom(curInterface)) { + foundInterfaces.add(new WeakReference>(curInterface.asSubclass(Attribute.class))); + } + } + actClazz = actClazz.getSuperclass(); + } while (actClazz != null); + } + return foundInterfaces; + } + } + + /** Expert: Adds a custom AttributeImpl instance with one or more Attribute interfaces. + *

Please note: It is not guaranteed, that att is added to + * the AttributeSource, because the provided attributes may already exist. + * You should always retrieve the wanted attributes using {@link #getAttribute} after adding + * with this method and cast to your class. + * The recommended way to use custom implementations is using an {@link AttributeFactory}. + *

+ */ + public void addAttributeImpl(final AttributeImpl att) { + final Class clazz = att.getClass(); + if (attributeImpls.containsKey(clazz)) return; + final LinkedList>> foundInterfaces = + getAttributeInterfaces(clazz); + + // add all interfaces of this AttributeImpl to the maps + for (WeakReference> curInterfaceRef : foundInterfaces) { + final Class curInterface = curInterfaceRef.get(); + assert (curInterface != null) : + "We have a strong reference on the class holding the interfaces, so they should never get evicted"; + // Attribute is a superclass of this interface + if (!attributes.containsKey(curInterface)) { + // invalidate state to force recomputation in captureState() + this.currentState[0] = null; + attributes.put(curInterface, att); + attributeImpls.put(clazz, att); + } + } + } + + /** + * The caller must pass in a Class<? extends Attribute> value. + * This method first checks if an instance of that class is + * already in this AttributeSource and returns it. Otherwise a + * new instance is created, added to this AttributeSource and returned. + */ + public A addAttribute(Class attClass) { + AttributeImpl attImpl = attributes.get(attClass); + if (attImpl == null) { + if (!(attClass.isInterface() && Attribute.class.isAssignableFrom(attClass))) { + throw new IllegalArgumentException( + "addAttribute() only accepts an interface that extends Attribute, but " + + attClass.getName() + " does not fulfil this contract." + ); + } + addAttributeImpl(attImpl = this.factory.createAttributeInstance(attClass)); + } + return attClass.cast(attImpl); + } + + /** Returns true, iff this AttributeSource has any attributes */ + public boolean hasAttributes() { + return !this.attributes.isEmpty(); + } + + /** + * The caller must pass in a Class<? extends Attribute> value. + * Returns true, iff this AttributeSource contains the passed-in Attribute. + */ + public boolean hasAttribute(Class attClass) { + return this.attributes.containsKey(attClass); + } + + /** + * The caller must pass in a Class<? extends Attribute> value. + * Returns the instance of the passed in Attribute contained in this AttributeSource + * + * @throws IllegalArgumentException if this AttributeSource does not contain the + * Attribute. It is recommended to always use {@link #addAttribute} even in consumers + * of TokenStreams, because you cannot know if a specific TokenStream really uses + * a specific Attribute. {@link #addAttribute} will automatically make the attribute + * available. If you want to only use the attribute, if it is available (to optimize + * consuming), use {@link #hasAttribute}. + */ + public A getAttribute(Class attClass) { + AttributeImpl attImpl = attributes.get(attClass); + if (attImpl == null) { + throw new IllegalArgumentException("This AttributeSource does not have the attribute '" + attClass.getName() + "'."); + } + return attClass.cast(attImpl); + } + + private State getCurrentState() { + State s = currentState[0]; + if (s != null || !hasAttributes()) { + return s; + } + State c = s = currentState[0] = new State(); + final Iterator it = attributeImpls.values().iterator(); + c.attribute = it.next(); + while (it.hasNext()) { + c.next = new State(); + c = c.next; + c.attribute = it.next(); + } + return s; + } + + /** + * Resets all Attributes in this AttributeSource by calling + * {@link AttributeImpl#clear()} on each Attribute implementation. + */ + public void clearAttributes() { + for (State state = getCurrentState(); state != null; state = state.next) { + state.attribute.clear(); + } + } + + /** + * Captures the state of all Attributes. The return value can be passed to + * {@link #restoreState} to restore the state of this or another AttributeSource. + */ + public State captureState() { + final State state = this.getCurrentState(); + return (state == null) ? null : (State) state.clone(); + } + + /** + * Restores this state by copying the values of all attribute implementations + * that this state contains into the attributes implementations of the targetStream. + * The targetStream must contain a corresponding instance for each argument + * contained in this state (e.g. it is not possible to restore the state of + * an AttributeSource containing a TermAttribute into a AttributeSource using + * a Token instance as implementation). + *

+ * Note that this method does not affect attributes of the targetStream + * that are not contained in this state. In other words, if for example + * the targetStream contains an OffsetAttribute, but this state doesn't, then + * the value of the OffsetAttribute remains unchanged. It might be desirable to + * reset its value to the default, in which case the caller should first + * call {@link TokenStream#clearAttributes()} on the targetStream. + */ + public void restoreState(State state) { + if (state == null) return; + + do { + AttributeImpl targetImpl = attributeImpls.get(state.attribute.getClass()); + if (targetImpl == null) { + throw new IllegalArgumentException("State contains AttributeImpl of type " + + state.attribute.getClass().getName() + " that is not in in this AttributeSource"); + } + state.attribute.copyTo(targetImpl); + state = state.next; + } while (state != null); + } + + @Override + public int hashCode() { + int code = 0; + for (State state = getCurrentState(); state != null; state = state.next) { + code = code * 31 + state.attribute.hashCode(); + } + return code; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof AttributeSource) { + AttributeSource other = (AttributeSource) obj; + + if (hasAttributes()) { + if (!other.hasAttributes()) { + return false; + } + + if (this.attributeImpls.size() != other.attributeImpls.size()) { + return false; + } + + // it is only equal if all attribute impls are the same in the same order + State thisState = this.getCurrentState(); + State otherState = other.getCurrentState(); + while (thisState != null && otherState != null) { + if (otherState.attribute.getClass() != thisState.attribute.getClass() || !otherState.attribute.equals(thisState.attribute)) { + return false; + } + thisState = thisState.next; + otherState = otherState.next; + } + return true; + } else { + return !other.hasAttributes(); + } + } else + return false; + } + + /** + * Returns a string representation of the object. In general, the {@code toString} method + * returns a string that "textually represents" this object. + * + *

WARNING: For backwards compatibility this method is implemented as + * in Lucene 2.9/3.0. In Lucene 4.0 this default implementation + * will be removed. + * + *

It is recommeneded to use {@link #reflectAsString} or {@link #reflectWith} + * to get a well-defined output of AttributeSource's internals. + */ + // TODO: @deprecated remove this method in 4.0 + @Override + public String toString() { + final StringBuilder sb = new StringBuilder().append('('); + if (hasAttributes()) { + for (State state = getCurrentState(); state != null; state = state.next) { + if (sb.length() > 1) sb.append(','); + sb.append(state.attribute.toString()); + } + } + return sb.append(')').toString(); + } + + /** + * This method returns the current attribute values as a string in the following format + * by calling the {@link #reflectWith(AttributeReflector)} method: + * + *

    + *
  • iff {@code prependAttClass=true}: {@code "AttributeClass#key=value,AttributeClass#key=value"} + *
  • iff {@code prependAttClass=false}: {@code "key=value,key=value"} + *
+ * + * @see #reflectWith(AttributeReflector) + */ + public final String reflectAsString(final boolean prependAttClass) { + final StringBuilder buffer = new StringBuilder(); + reflectWith(new AttributeReflector() { + public void reflect(Class attClass, String key, Object value) { + if (buffer.length() > 0) { + buffer.append(','); + } + if (prependAttClass) { + buffer.append(attClass.getName()).append('#'); + } + buffer.append(key).append('=').append((value == null) ? "null" : value); + } + }); + return buffer.toString(); + } + + /** + * This method is for introspection of attributes, it should simply + * add the key/values this AttributeSource holds to the given {@link AttributeReflector}. + * + *

This method iterates over all Attribute implementations and calls the + * corresponding {@link AttributeImpl#reflectWith} method.

+ * + * @see AttributeImpl#reflectWith + */ + public final void reflectWith(AttributeReflector reflector) { + for (State state = getCurrentState(); state != null; state = state.next) { + state.attribute.reflectWith(reflector); + } + } + + /** + * Performs a clone of all {@link AttributeImpl} instances returned in a new + * {@code AttributeSource} instance. This method can be used to e.g. create another TokenStream + * with exactly the same attributes (using {@link #AttributeSource(AttributeSource)}). + * You can also use it as a (non-performant) replacement for {@link #captureState}, if you need to look + * into / modify the captured state. + */ + public AttributeSource cloneAttributes() { + final AttributeSource clone = new AttributeSource(this.factory); + + if (hasAttributes()) { + // first clone the impls + for (State state = getCurrentState(); state != null; state = state.next) { + clone.attributeImpls.put(state.attribute.getClass(), (AttributeImpl) state.attribute.clone()); + } + + // now the interfaces + for (Entry, AttributeImpl> entry : this.attributes.entrySet()) { + clone.attributes.put(entry.getKey(), clone.attributeImpls.get(entry.getValue().getClass())); + } + } + + return clone; + } + + /** + * Copies the contents of this {@code AttributeSource} to the given target {@code AttributeSource}. + * The given instance has to provide all {@link Attribute}s this instance contains. + * The actual attribute implementations must be identical in both {@code AttributeSource} instances; + * ideally both AttributeSource instances should use the same {@link AttributeFactory}. + * You can use this method as a replacement for {@link #restoreState}, if you use + * {@link #cloneAttributes} instead of {@link #captureState}. + */ + public final void copyTo(AttributeSource target) { + for (State state = getCurrentState(); state != null; state = state.next) { + final AttributeImpl targetImpl = target.attributeImpls.get(state.attribute.getClass()); + if (targetImpl == null) { + throw new IllegalArgumentException("This AttributeSource contains AttributeImpl of type " + + state.attribute.getClass().getName() + " that is not in the target"); + } + state.attribute.copyTo(targetImpl); + } + } + +}