Index: lucene/contrib/icu/src/java/org/apache/lucene/analysis/icu/tokenattributes/ScriptAttributeImpl.java =================================================================== --- lucene/contrib/icu/src/java/org/apache/lucene/analysis/icu/tokenattributes/ScriptAttributeImpl.java (revision 1059709) +++ lucene/contrib/icu/src/java/org/apache/lucene/analysis/icu/tokenattributes/ScriptAttributeImpl.java (working copy) @@ -20,6 +20,7 @@ import java.io.Serializable; import org.apache.lucene.util.AttributeImpl; +import org.apache.lucene.util.AttributeReflector; import com.ibm.icu.lang.UScript; @@ -77,7 +78,7 @@ } @Override - public String toString() { - return "script=" + getName(); + public void reflectWith(AttributeReflector reflector) { + reflector.reflect(ScriptAttribute.class, "script", getName()); } } Index: lucene/src/java/org/apache/lucene/analysis/Token.java =================================================================== --- lucene/src/java/org/apache/lucene/analysis/Token.java (revision 1059709) +++ lucene/src/java/org/apache/lucene/analysis/Token.java (working copy) @@ -28,6 +28,7 @@ import org.apache.lucene.util.Attribute; import org.apache.lucene.util.AttributeSource; import org.apache.lucene.util.AttributeImpl; +import org.apache.lucene.util.AttributeReflector; /** A Token is an occurrence of a term from the text of a field. It consists of @@ -589,6 +590,17 @@ } } + @Override + public void reflectWith(AttributeReflector reflector) { + super.reflectWith(reflector); + reflector.reflect(OffsetAttribute.class, "startOffset", startOffset); + reflector.reflect(OffsetAttribute.class, "endOffset", endOffset); + reflector.reflect(PositionIncrementAttribute.class, "positionIncrement", positionIncrement); + reflector.reflect(PayloadAttribute.class, "payload", payload); + reflector.reflect(FlagsAttribute.class, "flags", flags); + reflector.reflect(TypeAttribute.class, "type", type); + } + /** Convenience factory that returns Token as implementation for the basic * attributes and return the default impl (with "Impl" appended) for all other * attributes. Index: lucene/src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttributeImpl.java =================================================================== --- lucene/src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttributeImpl.java (revision 1059709) +++ lucene/src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttributeImpl.java (working copy) @@ -22,6 +22,7 @@ import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.AttributeImpl; +import org.apache.lucene.util.AttributeReflector; import org.apache.lucene.util.RamUsageEstimator; /** @@ -286,6 +287,11 @@ } @Override + public void reflectWith(AttributeReflector reflector) { + reflector.reflect(CharTermAttribute.class, "term", term()); + } + + @Override public void copyTo(AttributeImpl target) { if (target instanceof CharTermAttribute) { CharTermAttribute t = (CharTermAttribute) target; Index: lucene/src/java/org/apache/lucene/util/AttributeImpl.java =================================================================== --- lucene/src/java/org/apache/lucene/util/AttributeImpl.java (revision 1059709) +++ lucene/src/java/org/apache/lucene/util/AttributeImpl.java (working copy) @@ -20,7 +20,11 @@ import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import org.apache.lucene.analysis.tokenattributes.CharTermAttributeImpl; // deprecated + /** * Base class for Attributes that can be added to a * {@link org.apache.lucene.util.AttributeSource}. @@ -37,72 +41,129 @@ public abstract void clear(); /** - * The default implementation of this method accesses all declared - * fields of this object and prints the values in the following syntax: + * 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 + * {@code return reflectAsString(false)}. In Lucene 4.0 this default implementation + * will be removed. The reason for this is the problem of + * {@link CharTermAttributeImpl#toString} that must return a string representation + * of the term's char sequence. + * + *

It is recommeneded to use {@link #reflectAsString} or {@link #reflectWith} + * to get a well-defined output of AttributeImpl's internals. + */ + // TODO: @deprecated remove this method in 4.0 + @Override + public String toString() { + return reflectAsString(false); + } + + /** + * This method returns the current attribute values as a string in the following format + * by calling the {@link #reflectWith(AttributeReflector)} method: * + *

+ * + * @see #reflectWith(AttributeReflector) + * @see #toString() + */ + 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(); + } + + /** + * @deprecated this will be removed in Lucene 4.0 + */ + @Deprecated + private static final VirtualMethod toStringMethod = + new VirtualMethod(AttributeImpl.class, "toString"); + + /** + * @deprecated this will be removed in Lucene 4.0 + */ + @Deprecated + private boolean assertExternalClass(Class clazz) { + final String name = clazz.getName(); + return !name.startsWith("org.apache.lucene.") && !name.startsWith("org.apache.solr."); + } + + /** + * This method is for introspection of attributes, it should simply + * add the key/values this attribute holds to the given {@link AttributeReflector}. + * + *

The default implementation calls {@link AttributeReflector#reflect} for all + * non-static fields from the implementing class, using the field name as key + * and the field value as value. The Attribute class is also determined by reflection. + * Please note that the default implementation can only handle single-Attribute + * implementations. + * + *

Custom implementations look like this (e.g. for a combined attribute implementation): *

-   *   public String toString() {
-   *     return "start=" + startOffset + ",end=" + endOffset;
+   *   public void reflectWith(AttributeReflector reflector) {
+   *     reflector.reflect(CharTermAttribute.class, "term", term());
+   *     reflector.reflect(PositionIncrementAttribute.class, "positionIncrement", getPositionIncrement());
    *   }
    * 
- * - * This method may be overridden by subclasses. + * + * @see #reflectAsString(boolean) */ - @Override - public String toString() { - StringBuilder buffer = new StringBuilder(); - Class clazz = this.getClass(); - Field[] fields = clazz.getDeclaredFields(); + public void reflectWith(AttributeReflector reflector) { + final Class clazz = this.getClass(); + final LinkedList>> interfaces = AttributeSource.getAttributeInterfaces(clazz); + if (interfaces.size() != 1) { + throw new UnsupportedOperationException(clazz.getName() + + " implements more than one Attribute interface, the default reflectWith() implementation cannot handle this."); + } + final Class interf = interfaces.getFirst().get(); + + // TODO: @deprecated sophisticated(TM) backwards + if (!(this instanceof CharTermAttributeImpl) && toStringMethod.isOverriddenAsOf(clazz)) { + assert assertExternalClass(clazz) : "no Lucene/Solr classes should fallback to toString() parsing"; + // this class overrides toString and for backwards compatibility we try to parse the string returned by this method: + for (String part : toString().split(",")) { + final int pos = part.indexOf('='); + if (pos < 0) { + throw new UnsupportedOperationException("The backwards compatibility layer to support reflectWith() " + + "on old AtributeImpls expects the toString() implementation to return a correct format as specified for method reflectAsString(false)"); + } + reflector.reflect(interf, part.substring(0, pos).trim(), part.substring(pos + 1)); + } + return; + } + // end sophisticated(TM) backwards + + final Field[] fields = clazz.getDeclaredFields(); try { for (int i = 0; i < fields.length; i++) { - Field f = fields[i]; + final Field f = fields[i]; if (Modifier.isStatic(f.getModifiers())) continue; f.setAccessible(true); - Object value = f.get(this); - if (buffer.length()>0) { - buffer.append(','); - } - if (value == null) { - buffer.append(f.getName() + "=null"); - } else { - buffer.append(f.getName() + "=" + value); - } + reflector.reflect(interf, f.getName(), f.get(this)); } } catch (IllegalAccessException e) { // this should never happen, because we're just accessing fields // from 'this' throw new RuntimeException(e); } - - return buffer.toString(); } /** - * Subclasses must implement this method and should compute - * a hashCode similar to this: - *
-   *   public int hashCode() {
-   *     int code = startOffset;
-   *     code = code * 31 + endOffset;
-   *     return code;
-   *   }
-   * 
- * - * see also {@link #equals(Object)} - */ - @Override - public abstract int hashCode(); - - /** - * All values used for computation of {@link #hashCode()} - * should be checked here for equality. - * - * see also {@link Object#equals(Object)} - */ - @Override - public abstract boolean equals(Object other); - - /** * Copies the values from this Attribute into the passed-in * target attribute. The target implementation must support all the * Attributes this implementation supports. Index: lucene/src/java/org/apache/lucene/util/AttributeReflector.java =================================================================== --- lucene/src/java/org/apache/lucene/util/AttributeReflector.java (revision 0) +++ lucene/src/java/org/apache/lucene/util/AttributeReflector.java (revision 0) @@ -0,0 +1,30 @@ +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. + */ + +/** + * TODO + */ +public interface AttributeReflector { + + /** + * TODO + */ + public void reflect(Class attClass, String key, Object value); + +} Property changes on: lucene\src\java\org\apache\lucene\util\AttributeReflector.java ___________________________________________________________________ Added: svn:keywords + Date Author Id Revision HeadURL Added: svn:eol-style + native Index: lucene/src/java/org/apache/lucene/util/AttributeSource.java =================================================================== --- lucene/src/java/org/apache/lucene/util/AttributeSource.java (revision 1059709) +++ lucene/src/java/org/apache/lucene/util/AttributeSource.java (working copy) @@ -188,20 +188,9 @@ private static final WeakHashMap,LinkedList>>> knownImplClasses = new WeakHashMap,LinkedList>>>(); - /** 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; - LinkedList>> foundInterfaces; + static LinkedList>> getAttributeInterfaces(final Class clazz) { synchronized(knownImplClasses) { - foundInterfaces = knownImplClasses.get(clazz); + 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 @@ -218,7 +207,23 @@ 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) { @@ -439,9 +444,21 @@ 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() { - StringBuilder sb = new StringBuilder().append('('); + final StringBuilder sb = new StringBuilder().append('('); if (hasAttributes()) { if (currentState == null) { computeCurrentState(); @@ -455,6 +472,53 @@ } /** + * 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) { + if (hasAttributes()) { + if (currentState == null) { + computeCurrentState(); + } + for (State state = currentState; 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)}).