lucene-java-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From uschind...@apache.org
Subject svn commit: r898507 - in /lucene/java/trunk: ./ src/java/org/apache/lucene/analysis/ src/java/org/apache/lucene/analysis/standard/ src/java/org/apache/lucene/util/ src/test/org/apache/lucene/util/
Date Tue, 12 Jan 2010 20:55:21 GMT
Author: uschindler
Date: Tue Jan 12 20:55:21 2010
New Revision: 898507

URL: http://svn.apache.org/viewvc?rev=898507&view=rev
Log:
LUCENE-2188: Add a utility class for tracking deprecated overridden methods in non-final subclasses

Added:
    lucene/java/trunk/src/java/org/apache/lucene/util/VirtualMethod.java   (with props)
    lucene/java/trunk/src/test/org/apache/lucene/util/TestVirtualMethod.java   (with props)
Modified:
    lucene/java/trunk/CHANGES.txt
    lucene/java/trunk/src/java/org/apache/lucene/analysis/Analyzer.java
    lucene/java/trunk/src/java/org/apache/lucene/analysis/KeywordAnalyzer.java
    lucene/java/trunk/src/java/org/apache/lucene/analysis/PerFieldAnalyzerWrapper.java
    lucene/java/trunk/src/java/org/apache/lucene/analysis/StopwordAnalyzerBase.java
    lucene/java/trunk/src/java/org/apache/lucene/analysis/standard/StandardAnalyzer.java

Modified: lucene/java/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/java/trunk/CHANGES.txt?rev=898507&r1=898506&r2=898507&view=diff
==============================================================================
--- lucene/java/trunk/CHANGES.txt (original)
+++ lucene/java/trunk/CHANGES.txt Tue Jan 12 20:55:21 2010
@@ -166,6 +166,10 @@
   directly, instead of Byte/CharBuffers, and modify CollationKeyFilter to
   take advantage of this for faster performance.
   (Steven Rowe, Uwe Schindler, Robert Muir)
+
+* LUCENE-2188: Add a utility class for tracking deprecated overridden
+  methods in non-final subclasses.
+  (Uwe Schindler, Robert Muir)
    
 Build
 

Modified: lucene/java/trunk/src/java/org/apache/lucene/analysis/Analyzer.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/analysis/Analyzer.java?rev=898507&r1=898506&r2=898507&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/analysis/Analyzer.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/analysis/Analyzer.java Tue Jan 12 20:55:21
2010
@@ -20,9 +20,9 @@
 import java.io.Reader;
 import java.io.IOException;
 import java.io.Closeable;
-import java.lang.reflect.Method;
 
 import org.apache.lucene.util.CloseableThreadLocal;
+import org.apache.lucene.util.VirtualMethod;
 import org.apache.lucene.store.AlreadyClosedException;
 
 import org.apache.lucene.document.Fieldable;
@@ -84,22 +84,25 @@
     }
   }
 
-  /** @deprecated */
+  private static final VirtualMethod<Analyzer> tokenStreamMethod =
+    new VirtualMethod<Analyzer>(Analyzer.class, "tokenStream", String.class, Reader.class);
+  private static final VirtualMethod<Analyzer> reusableTokenStreamMethod =
+    new VirtualMethod<Analyzer>(Analyzer.class, "reusableTokenStream", String.class,
Reader.class);
+
+  /** This field contains if the {@link #tokenStream} method was overridden in a
+   * more far away subclass of {@code Analyzer} on the current instance's inheritance path.
+   * If this field is {@code true}, {@link #reusableTokenStream} should delegate to {@link
#tokenStream}
+   * instead of using the own implementation.
+   * @deprecated Please declare all implementations of {@link #reusableTokenStream} and {@link
#tokenStream}
+   * as {@code final}.
+   */
   @Deprecated
-  protected boolean overridesTokenStreamMethod = false;
+  protected final boolean overridesTokenStreamMethod =
+    VirtualMethod.compareImplementationDistance(this.getClass(), tokenStreamMethod, reusableTokenStreamMethod)
> 0;
 
-  /** @deprecated This is only present to preserve
-   *  back-compat of classes that subclass a core analyzer
-   *  and override tokenStream but not reusableTokenStream */
+  /** @deprecated This is a no-op since Lucene 3.1. */
   @Deprecated
   protected void setOverridesTokenStreamMethod(Class<? extends Analyzer> baseClass)
{
-    try {
-      Method m = this.getClass().getMethod("tokenStream", String.class, Reader.class);
-      overridesTokenStreamMethod = m.getDeclaringClass() != baseClass;
-    } catch (NoSuchMethodException nsme) {
-      // cannot happen, as baseClass is subclass of Analyzer through generics
-      overridesTokenStreamMethod = false;
-    }
   }
 
 

Modified: lucene/java/trunk/src/java/org/apache/lucene/analysis/KeywordAnalyzer.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/analysis/KeywordAnalyzer.java?rev=898507&r1=898506&r2=898507&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/analysis/KeywordAnalyzer.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/analysis/KeywordAnalyzer.java Tue Jan 12
20:55:21 2010
@@ -26,7 +26,6 @@
  */
 public class KeywordAnalyzer extends Analyzer {
   public KeywordAnalyzer() {
-    setOverridesTokenStreamMethod(KeywordAnalyzer.class);
   }
   @Override
   public TokenStream tokenStream(String fieldName,

Modified: lucene/java/trunk/src/java/org/apache/lucene/analysis/PerFieldAnalyzerWrapper.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/analysis/PerFieldAnalyzerWrapper.java?rev=898507&r1=898506&r2=898507&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/analysis/PerFieldAnalyzerWrapper.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/analysis/PerFieldAnalyzerWrapper.java Tue
Jan 12 20:55:21 2010
@@ -72,7 +72,6 @@
     if (fieldAnalyzers != null) {
       analyzerMap.putAll(fieldAnalyzers);
     }
-    setOverridesTokenStreamMethod(PerFieldAnalyzerWrapper.class);
   }
   
 

Modified: lucene/java/trunk/src/java/org/apache/lucene/analysis/StopwordAnalyzerBase.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/analysis/StopwordAnalyzerBase.java?rev=898507&r1=898506&r2=898507&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/analysis/StopwordAnalyzerBase.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/analysis/StopwordAnalyzerBase.java Tue Jan
12 20:55:21 2010
@@ -58,11 +58,6 @@
    *          the analyzer's stopword set
    */
   protected StopwordAnalyzerBase(final Version version, final Set<?> stopwords) {
-    /*
-     * no need to call
-     * setOverridesTokenStreamMethod(StopwordAnalyzerBase.class); here, both
-     * tokenStream methods are final in this class.
-     */
     matchVersion = version;
     // analyzers should use char array set for stopwords!
     this.stopwords = stopwords == null ? CharArraySet.EMPTY_SET : CharArraySet

Modified: lucene/java/trunk/src/java/org/apache/lucene/analysis/standard/StandardAnalyzer.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/analysis/standard/StandardAnalyzer.java?rev=898507&r1=898506&r2=898507&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/analysis/standard/StandardAnalyzer.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/analysis/standard/StandardAnalyzer.java Tue
Jan 12 20:55:21 2010
@@ -71,7 +71,6 @@
    * @param stopWords stop words */
   public StandardAnalyzer(Version matchVersion, Set<?> stopWords) {
     stopSet = stopWords;
-    setOverridesTokenStreamMethod(StandardAnalyzer.class);
     replaceInvalidAcronym = matchVersion.onOrAfter(Version.LUCENE_24);
     this.matchVersion = matchVersion;
   }

Added: lucene/java/trunk/src/java/org/apache/lucene/util/VirtualMethod.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/util/VirtualMethod.java?rev=898507&view=auto
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/util/VirtualMethod.java (added)
+++ lucene/java/trunk/src/java/org/apache/lucene/util/VirtualMethod.java Tue Jan 12 20:55:21
2010
@@ -0,0 +1,148 @@
+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.reflect.Method;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Set;
+
+/**
+ * A utility for keeping backwards compatibility on previously abstract methods
+ * (or similar replacements).
+ * <p>Before the replacement method can be made abstract, the old method must kept
deprecated.
+ * If somebody still overrides the deprecated method in a non-final class,
+ * you must keep track, of this and maybe delegate to the old method in the subclass.
+ * The cost of reflection is minimized by the following usage of this class:</p>
+ * <p>Define <strong>static final</strong> fields in the base class ({@code
BaseClass}),
+ * where the old and new method are declared:</p>
+ * <pre>
+ *  static final VirtualMethod&lt;BaseClass&gt; newMethod =
+ *   new VirtualMethod&lt;BaseClass&gt;(BaseClass.class, "newName", parameters...);
+ *  static final VirtualMethod&lt;BaseClass&gt; oldMethod =
+ *   new VirtualMethod&lt;BaseClass&gt;(BaseClass.class, "oldName", parameters...);
+ * </pre>
+ * <p>This enforces the singleton status of these objects, as the maintenance of the
cache would be too costly else.
+ * If you try to create a second instance of for the same method/{@code baseClass} combination,
an exception is thrown.
+ * <p>To detect if e.g. the old method was overridden by a more far subclass on the
inheritance path to the current
+ * instance's class, use a <strong>non-static</strong> field:</p>
+ * <pre>
+ *  final boolean isDeprecatedMethodOverridden =
+ *   oldMethod.getImplementationDistance(this.getClass()) > newMethod.getImplementationDistance(this.getClass());
+ *
+ *  <em>// alternatively (more readable):</em>
+ *  final boolean isDeprecatedMethodOverridden =
+ *   VirtualMethod.compareImplementationDistance(this.getClass(), oldMethod, newMethod) >
0
+ * </pre> 
+ * <p>{@link #getImplementationDistance} returns the distance of the subclass that
overrides this method.
+ * The one with the larger distance should be used preferable.
+ * This way also more complicated method rename scenarios can be handled
+ * (think of 2.9 {@code TokenStream} deprecations).</p>
+ */
+public final class VirtualMethod<C> {
+
+  private static final Set<Method> singletonSet = Collections.synchronizedSet(new HashSet<Method>());
+
+  private final Class<C> baseClass;
+  private final String method;
+  private final Class<?>[] parameters;
+  private final IdentityHashMap<Class<? extends C>, Integer> cache =
+    new IdentityHashMap<Class<? extends C>, Integer>();
+
+  /**
+   * Creates a new instance for the given {@code baseClass} and method declaration.
+   * @throws UnsupportedOperationException if you create a second instance of the same
+   *  {@code baseClass} and method declaration combination. This enforces the singleton status.
+   * @throws IllegalArgumentException if {@code baseClass} does not declare the given method.
+   */
+  public VirtualMethod(Class<C> baseClass, String method, Class<?>... parameters)
{
+    this.baseClass = baseClass;
+    this.method = method;
+    this.parameters = parameters;
+    try {
+      if (!singletonSet.add(baseClass.getDeclaredMethod(method, parameters)))
+        throw new UnsupportedOperationException(
+          "VirtualMethod instances must be singletons and therefore " +
+          "assigned to static final members in the same class, they use as baseClass ctor
param."
+        );
+    } catch (NoSuchMethodException nsme) {
+      throw new IllegalArgumentException(baseClass.getName() + " has no such method: "+nsme.getMessage());
+    }
+  }
+  
+  /**
+   * Returns the distance from the {@code baseClass} in which this method is overridden/implemented
+   * in the inheritance path between {@code baseClass} and the given subclass {@code subclazz}.
+   * @return 0 iff not overridden, else the distance to the base class
+   */
+  public synchronized int getImplementationDistance(final Class<? extends C> subclazz)
{
+    Integer distance = cache.get(subclazz);
+    if (distance == null) {
+      cache.put(subclazz, distance = Integer.valueOf(reflectImplementationDistance(subclazz)));
+    }
+    return distance.intValue();
+  }
+  
+  /**
+   * Returns, if this method is overridden/implemented in the inheritance path between
+   * {@code baseClass} and the given subclass {@code subclazz}.
+   * <p>You can use this method to detect if a method that should normally be final
was overridden
+   * by the given instance's class.
+   * @return {@code false} iff not overridden
+   */
+  public boolean isOverriddenAsOf(final Class<? extends C> subclazz) {
+    return getImplementationDistance(subclazz) > 0;
+  }
+  
+  private int reflectImplementationDistance(final Class<? extends C> subclazz) {
+    if (!baseClass.isAssignableFrom(subclazz))
+      throw new IllegalArgumentException(subclazz.getName() + " is not a subclass of " +
baseClass.getName());
+    boolean overridden = false;
+    int distance = 0;
+    for (Class<?> clazz = subclazz; clazz != baseClass && clazz != null; clazz
= clazz.getSuperclass()) {
+      // lookup method, if success mark as overridden
+      if (!overridden) {
+        try {
+          clazz.getDeclaredMethod(method, parameters);
+          overridden = true;
+        } catch (NoSuchMethodException nsme) {
+        }
+      }
+      
+      // increment distance if overridden
+      if (overridden) distance++;
+    }
+    return distance;
+  }
+  
+  /**
+   * Utility method that compares the implementation/override distance of two methods.
+   * @return <ul>
+   *  <li>&gt; 1, iff {@code m1} is overridden/implemented in a subclass of the
class overriding/declaring {@code m2}
+   *  <li>&lt; 1, iff {@code m2} is overridden in a subclass of the class overriding/declaring
{@code m1}
+   *  <li>0, iff both methods are overridden in the same class (or are not overridden
at all)
+   * </ul>
+   */
+  public static <C> int compareImplementationDistance(final Class<? extends C>
clazz,
+    final VirtualMethod<C> m1, final VirtualMethod<C> m2)
+  {
+    return Integer.valueOf(m1.getImplementationDistance(clazz)).compareTo(m2.getImplementationDistance(clazz));
+  }
+  
+}

Propchange: lucene/java/trunk/src/java/org/apache/lucene/util/VirtualMethod.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: lucene/java/trunk/src/java/org/apache/lucene/util/VirtualMethod.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Added: lucene/java/trunk/src/test/org/apache/lucene/util/TestVirtualMethod.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/test/org/apache/lucene/util/TestVirtualMethod.java?rev=898507&view=auto
==============================================================================
--- lucene/java/trunk/src/test/org/apache/lucene/util/TestVirtualMethod.java (added)
+++ lucene/java/trunk/src/test/org/apache/lucene/util/TestVirtualMethod.java Tue Jan 12 20:55:21
2010
@@ -0,0 +1,102 @@
+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.
+ */
+
+public class TestVirtualMethod extends LuceneTestCase {
+
+  private static final VirtualMethod<TestVirtualMethod> publicTestMethod =
+    new VirtualMethod<TestVirtualMethod>(TestVirtualMethod.class, "publicTest", String.class);
+  private static final VirtualMethod<TestVirtualMethod> protectedTestMethod =
+    new VirtualMethod<TestVirtualMethod>(TestVirtualMethod.class, "protectedTest",
int.class);
+
+  public void publicTest(String test) {}
+  protected void protectedTest(int test) {}
+  
+  static class TestClass1 extends TestVirtualMethod {
+    @Override
+    public void publicTest(String test) {}
+    @Override
+    protected void protectedTest(int test) {}
+  }
+
+  static class TestClass2 extends TestClass1 {
+    @Override // make it public here
+    public void protectedTest(int test) {}
+  }
+
+  static class TestClass3 extends TestClass2 {
+    @Override
+    public void publicTest(String test) {}
+  }
+
+  static class TestClass4 extends TestVirtualMethod {
+  }
+
+  static class TestClass5 extends TestClass4 {
+  }
+
+  public void test() {
+    assertEquals(0, publicTestMethod.getImplementationDistance(this.getClass()));
+    assertEquals(1, publicTestMethod.getImplementationDistance(TestClass1.class));
+    assertEquals(1, publicTestMethod.getImplementationDistance(TestClass2.class));
+    assertEquals(3, publicTestMethod.getImplementationDistance(TestClass3.class));
+    assertFalse(publicTestMethod.isOverriddenAsOf(TestClass4.class));
+    assertFalse(publicTestMethod.isOverriddenAsOf(TestClass5.class));
+    
+    assertEquals(0, protectedTestMethod.getImplementationDistance(this.getClass()));
+    assertEquals(1, protectedTestMethod.getImplementationDistance(TestClass1.class));
+    assertEquals(2, protectedTestMethod.getImplementationDistance(TestClass2.class));
+    assertEquals(2, protectedTestMethod.getImplementationDistance(TestClass3.class));
+    assertFalse(protectedTestMethod.isOverriddenAsOf(TestClass4.class));
+    assertFalse(protectedTestMethod.isOverriddenAsOf(TestClass5.class));
+    
+    assertTrue(VirtualMethod.compareImplementationDistance(TestClass3.class, publicTestMethod,
protectedTestMethod) > 0);
+    assertEquals(0, VirtualMethod.compareImplementationDistance(TestClass5.class, publicTestMethod,
protectedTestMethod));
+    
+    try {
+      // cast to Class to remove generics:
+      @SuppressWarnings("unchecked") int dist = publicTestMethod.getImplementationDistance((Class)
LuceneTestCase.class);
+      fail("LuceneTestCase is not a subclass and can never override publicTest(String)");
+    } catch (IllegalArgumentException arg) {
+      // pass
+    }
+    
+    try {
+      new VirtualMethod<TestVirtualMethod>(TestVirtualMethod.class, "bogus");
+      fail("Method bogus() does not exist, so IAE should be thrown");
+    } catch (IllegalArgumentException arg) {
+      // pass
+    }
+    
+    try {
+      new VirtualMethod<TestClass2>(TestClass2.class, "publicTest", String.class);
+      fail("Method publicTest(String) is not declared in TestClass2, so IAE should be thrown");
+    } catch (IllegalArgumentException arg) {
+      // pass
+    }
+
+    try {
+      // try to create a second instance of the same baseClass / method combination
+      new VirtualMethod<TestVirtualMethod>(TestVirtualMethod.class, "publicTest", String.class);
+      fail("Violating singleton status succeeded");
+    } catch (UnsupportedOperationException arg) {
+      // pass
+    }
+  }
+  
+}

Propchange: lucene/java/trunk/src/test/org/apache/lucene/util/TestVirtualMethod.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: lucene/java/trunk/src/test/org/apache/lucene/util/TestVirtualMethod.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL



Mime
View raw message