blob: 631de15f3a5e8f13a5e4dd2084c79b4b3f6d3455 [file] [log] [blame]
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
/**
* 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<? extends Attribute> attClass);
/**
* This is the default factory that creates {@link AttributeImpl}s using the
* class name of the supplied {@link Attribute} interface class by appending <code>Impl</code> to it.
*/
public static final AttributeFactory DEFAULT_ATTRIBUTE_FACTORY = new DefaultAttributeFactory();
private static final class DefaultAttributeFactory extends AttributeFactory {
private static final WeakHashMap<Class<? extends Attribute>, WeakReference<Class<? extends AttributeImpl>>> attClassImplMap =
new WeakHashMap<Class<? extends Attribute>, WeakReference<Class<? extends AttributeImpl>>>();
private DefaultAttributeFactory() {}
@Override
public AttributeImpl createAttributeInstance(Class<? extends Attribute> 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<? extends AttributeImpl> getClassForInterface(Class<? extends Attribute> attClass) {
synchronized(attClassImplMap) {
final WeakReference<Class<? extends AttributeImpl>> ref = attClassImplMap.get(attClass);
Class<? extends AttributeImpl> clazz = (ref == null) ? null : ref.get();
if (clazz == null) {
try {
attClassImplMap.put(attClass,
new WeakReference<Class<? extends AttributeImpl>>(
clazz = Class.forName(attClass.getName() + "Impl", true, attClass.getClassLoader())
.asSubclass(AttributeImpl.class)
)
);
} 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<Class<? extends Attribute>, AttributeImpl> attributes;
private final Map<Class<? extends AttributeImpl>, 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<Class<? extends Attribute>, AttributeImpl>();
this.attributeImpls = new LinkedHashMap<Class<? extends AttributeImpl>, AttributeImpl>();
this.currentState = new State[1];
this.factory = factory;
}
/**
* returns the used AttributeFactory.
*/
public final AttributeFactory getAttributeFactory() {
return this.factory;
}
/** Returns a new iterator that iterates the attribute classes
* in the same order they were added in.
*/
public final Iterator<Class<? extends Attribute>> 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 final Iterator<AttributeImpl> getAttributeImplsIterator() {
final State initState = getCurrentState();
if (initState != null) {
return new Iterator<AttributeImpl>() {
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.<AttributeImpl>emptySet().iterator();
}
}
/** a cache that stores all interfaces for known implementation classes for performance (slow reflection) */
private static final WeakHashMap<Class<? extends AttributeImpl>,LinkedList<WeakReference<Class<? extends Attribute>>>> knownImplClasses =
new WeakHashMap<Class<? extends AttributeImpl>,LinkedList<WeakReference<Class<? extends Attribute>>>>();
static LinkedList<WeakReference<Class<? extends Attribute>>> getAttributeInterfaces(final Class<? extends AttributeImpl> clazz) {
synchronized(knownImplClasses) {
LinkedList<WeakReference<Class<? extends Attribute>>> 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<WeakReference<Class<? extends Attribute>>>());
// 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<Class<? extends Attribute>>(curInterface.asSubclass(Attribute.class)));
}
}
actClazz = actClazz.getSuperclass();
} while (actClazz != null);
}
return foundInterfaces;
}
}
/** <b>Expert:</b> Adds a custom AttributeImpl instance with one or more Attribute interfaces.
* <p><font color="red"><b>Please note:</b> It is not guaranteed, that <code>att</code> is added to
* the <code>AttributeSource</code>, 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}.
* </font></p>
*/
public final void addAttributeImpl(final AttributeImpl att) {
final Class<? extends AttributeImpl> clazz = att.getClass();
if (attributeImpls.containsKey(clazz)) return;
final LinkedList<WeakReference<Class<? extends Attribute>>> foundInterfaces =
getAttributeInterfaces(clazz);
// add all interfaces of this AttributeImpl to the maps
for (WeakReference<Class<? extends Attribute>> curInterfaceRef : foundInterfaces) {
final Class<? extends Attribute> 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&lt;? extends Attribute&gt; 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 final <A extends Attribute> A addAttribute(Class<A> 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 final boolean hasAttributes() {
return !this.attributes.isEmpty();
}
/**
* The caller must pass in a Class&lt;? extends Attribute&gt; value.
* Returns true, iff this AttributeSource contains the passed-in Attribute.
*/
public final boolean hasAttribute(Class<? extends Attribute> attClass) {
return this.attributes.containsKey(attClass);
}
/**
* The caller must pass in a Class&lt;? extends Attribute&gt; 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 final <A extends Attribute> A getAttribute(Class<A> 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<AttributeImpl> 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 final 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 final 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).
* <p>
* 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 final 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;
}
/**
* This method returns the current attribute values as a string in the following format
* by calling the {@link #reflectWith(AttributeReflector)} method:
*
* <ul>
* <li><em>iff {@code prependAttClass=true}:</em> {@code "AttributeClass#key=value,AttributeClass#key=value"}
* <li><em>iff {@code prependAttClass=false}:</em> {@code "key=value,key=value"}
* </ul>
*
* @see #reflectWith(AttributeReflector)
*/
public final String reflectAsString(final boolean prependAttClass) {
final StringBuilder buffer = new StringBuilder();
reflectWith(new AttributeReflector() {
public void reflect(Class<? extends Attribute> 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}.
*
* <p>This method iterates over all Attribute implementations and calls the
* corresponding {@link AttributeImpl#reflectWith} method.</p>
*
* @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 final 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<Class<? extends Attribute>, 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);
}
}
}