Index: lucene/src/java/org/apache/lucene/util/AttributeSource.java =================================================================== --- lucene/src/java/org/apache/lucene/util/AttributeSource.java (revision 1096005) +++ lucene/src/java/org/apache/lucene/util/AttributeSource.java (working copy) @@ -93,10 +93,33 @@ } } + /** + * 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; @@ -116,6 +139,7 @@ } this.attributes = input.attributes; this.attributeImpls = input.attributeImpls; + this.currentState = input.currentState; this.factory = input.factory; } @@ -125,6 +149,7 @@ public AttributeSource(AttributeFactory factory) { this.attributes = new LinkedHashMap, AttributeImpl>(); this.attributeImpls = new LinkedHashMap, AttributeImpl>(); + this.currentState = new State[1]; this.factory = factory; } @@ -147,11 +172,8 @@ * if one instance implements more than one Attribute interface. */ public final Iterator getAttributeImplsIterator() { - if (hasAttributes()) { - if (currentState == null) { - computeCurrentState(); - } - final State initState = currentState; + final State initState = getCurrentState(); + if (initState != null) { return new Iterator() { private State state = initState; @@ -225,7 +247,7 @@ // Attribute is a superclass of this interface if (!attributes.containsKey(curInterface)) { // invalidate state to force recomputation in captureState() - this.currentState = null; + this.currentState[0] = null; attributes.put(curInterface, att); attributeImpls.put(clazz, att); } @@ -283,41 +305,21 @@ } return attClass.cast(attImpl); } - - /** - * 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; + private State getCurrentState() { + State s = currentState[0]; + if (s != null || !hasAttributes()) { + return s; } - } - - private State currentState = null; - - private void computeCurrentState() { - currentState = new State(); - State c = currentState; + 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; } /** @@ -325,13 +327,8 @@ * {@link AttributeImpl#clear()} on each Attribute implementation. */ public final void clearAttributes() { - if (hasAttributes()) { - if (currentState == null) { - computeCurrentState(); - } - for (State state = currentState; state != null; state = state.next) { - state.attribute.clear(); - } + for (State state = getCurrentState(); state != null; state = state.next) { + state.attribute.clear(); } } @@ -340,14 +337,8 @@ * {@link #restoreState} to restore the state of this or another AttributeSource. */ public final State captureState() { - if (!hasAttributes()) { - return null; - } - - if (currentState == null) { - computeCurrentState(); - } - return (State) this.currentState.clone(); + final State state = this.getCurrentState(); + return (state == null) ? null : (State) state.clone(); } /** @@ -382,15 +373,9 @@ @Override public int hashCode() { int code = 0; - if (hasAttributes()) { - if (currentState == null) { - computeCurrentState(); - } - for (State state = currentState; state != null; state = state.next) { - code = code * 31 + state.attribute.hashCode(); - } + for (State state = getCurrentState(); state != null; state = state.next) { + code = code * 31 + state.attribute.hashCode(); } - return code; } @@ -413,14 +398,8 @@ } // it is only equal if all attribute impls are the same in the same order - if (this.currentState == null) { - this.computeCurrentState(); - } - State thisState = this.currentState; - if (other.currentState == null) { - other.computeCurrentState(); - } - State otherState = other.currentState; + 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; @@ -473,13 +452,8 @@ * @see AttributeImpl#reflectWith */ public final void reflectWith(AttributeReflector reflector) { - if (hasAttributes()) { - if (currentState == null) { - computeCurrentState(); - } - for (State state = currentState; state != null; state = state.next) { - state.attribute.reflectWith(reflector); - } + for (State state = getCurrentState(); state != null; state = state.next) { + state.attribute.reflectWith(reflector); } } @@ -495,10 +469,7 @@ if (hasAttributes()) { // first clone the impls - if (currentState == null) { - computeCurrentState(); - } - for (State state = currentState; state != null; state = state.next) { + for (State state = getCurrentState(); state != null; state = state.next) { clone.attributeImpls.put(state.attribute.getClass(), (AttributeImpl) state.attribute.clone()); } @@ -520,18 +491,13 @@ * {@link #cloneAttributes} instead of {@link #captureState}. */ public final void copyTo(AttributeSource target) { - if (hasAttributes()) { - if (currentState == null) { - computeCurrentState(); + 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"); } - for (State state = currentState; 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); - } + state.attribute.copyTo(targetImpl); } } Index: lucene/src/test/org/apache/lucene/analysis/TestMockAnalyzer.java =================================================================== --- lucene/src/test/org/apache/lucene/analysis/TestMockAnalyzer.java (revision 1096005) +++ lucene/src/test/org/apache/lucene/analysis/TestMockAnalyzer.java (working copy) @@ -1,5 +1,6 @@ package org.apache.lucene.analysis; +import java.io.StringReader; import java.util.Arrays; import org.apache.lucene.util.automaton.Automaton; @@ -95,4 +96,19 @@ new String[] { "ok", "fine" }, new int[] { 1, 2 }); } + + public void testLUCENE_3042() throws Exception { + String testString = "t"; + + Analyzer analyzer = new MockAnalyzer(random); + TokenStream stream = analyzer.reusableTokenStream("dummy", new StringReader(testString)); + stream.reset(); + while (stream.incrementToken()) { + // consume + } + stream.end(); + + assertAnalyzesToReuse(analyzer, testString, new String[] { "t" }); + } + }