-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LUCENE-2188: Add a utility class for tracking deprecated overridden m…
…ethods in non-final subclasses git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@898507 13f79535-47bb-0310-9956-ffa450edef68
- Loading branch information
1 parent
eaed8e6
commit a33e867
Showing
8 changed files
with
270 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<BaseClass> newMethod = | ||
* new VirtualMethod<BaseClass>(BaseClass.class, "newName", parameters...); | ||
* static final VirtualMethod<BaseClass> oldMethod = | ||
* new VirtualMethod<BaseClass>(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>> 1, iff {@code m1} is overridden/implemented in a subclass of the class overriding/declaring {@code m2} | ||
* <li>< 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)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
|
||
} |