blob: d1e300ae606a7cc35517947033db998df9f323fc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ui.tests.core;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.util.Hashtable;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.eclipse.jdt.testplugin.JavaProjectHelper;
import org.eclipse.jdt.testplugin.TestOptions;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.corext.dom.ASTFlattener;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.internal.corext.util.MethodOverrideTester;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.tests.core.rules.ProjectTestSetup;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider;
public class MethodOverrideTest extends CoreTests {
/**
* See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=111093
*/
@Rule
public ProjectTestSetup pts= new ProjectTestSetup();
private static final boolean DEBUG_SHOWRESULTS= true;
private IJavaProject fJProject1;
@Before
public void setUp() throws Exception {
fJProject1= pts.getProject();
Hashtable<String, String> options= TestOptions.getDefaultOptions();
JavaCore.setOptions(options);
}
@After
public void tearDown() throws Exception {
JavaProjectHelper.clear(fJProject1, pts.getDefaultClasspath());
}
@Test
public void testOverride0() throws Exception {
IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null);
StringBuilder buf= new StringBuilder();
buf.append("package test1;\n");
buf.append("public class A<S> {\n");
buf.append(" public A() {}\n");
buf.append(" public void o0_foo(S t) {}\n");
buf.append("}\n");
buf.append("class B<T> extends A<T> {\n");
buf.append(" public B() {}\n");
buf.append(" @Override public void o0_foo(T t) {}\n");
buf.append("}\n");
buf.append("class C extends B<String> {\n");
buf.append(" public C() {}\n");
buf.append(" @Override public void o0_foo(String t) {}\n");
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("A.java", buf.toString(), false, null);
doOverrideTests(cu, 2, 2, 1); // C and B
doOverrideTests(cu, 2, 2, 0); // C and A
doOverrideTests(cu, 2, 1, 0); // B and A
}
@Test
public void testOverride1() throws Exception {
IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null);
StringBuilder buf= new StringBuilder();
buf.append("package test1;\n");
buf.append("public class A<T> {\n");
buf.append(" public A() {}\n");
buf.append(" public void o1_foo1(T t) {}\n");
buf.append(" public void o1_foo2(int i, String s) {}\n");
buf.append(" public void o1_foo3(String s) {}\n");
buf.append(" public void o1_foo4(T... t) {}\n");
buf.append(" public void o1_foo5(A<T> s) {}\n");
buf.append(" public void o1_foo6(A<? super T> s) {}\n");
buf.append(" public void o1_foo7(T... t) {}\n");
buf.append(" public void o1_xoo1(T[] t) {}\n");
buf.append(" public void o1_xoo2(A<?> s) {}\n");
buf.append(" public void o1_xoo3(A<? extends T> s) {}\n");
buf.append("}\n");
buf.append("class B<S> extends A<S> {\n");
buf.append(" public B() {}\n");
buf.append(" @Override public void o1_foo1(S t) {}\n");
buf.append(" @Override public void o1_foo2(int i, String s) {}\n");
buf.append(" @Override public void o1_foo3(String s) {}\n");
buf.append(" @Override public void o1_foo4(S... t) {}\n");
buf.append(" @Override public void o1_foo5(A<S> s) {}\n");
buf.append(" @Override public void o1_foo6(A<? super S> s) {}\n");
buf.append(" @Override public void o1_foo7(S[] t) {}\n");
buf.append(" @Override public void o1_xoo1(S[][] t) {}\n");
buf.append(" @Override public void o1_xoo2(A<Object> s) {}\n");
buf.append(" @Override public void o1_xoo3(A<? super S> s) {}\n");
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("A.java", buf.toString(), false, null);
doOverrideTests(cu, 1, 1, 0); // B and A
}
@Test
public void testOverride2() throws Exception {
IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null);
StringBuilder buf= new StringBuilder();
buf.append("package test1;\n");
buf.append("import java.util.List;\n");
buf.append("import java.util.Map;\n");
buf.append("public class A<S, T> {\n");
buf.append(" public A() {}\n");
buf.append(" public void o2_foo1(Map<S, T> t) {}\n");
buf.append(" public void o2_xoo1(List<? extends T> t) {}\n");
buf.append("}\n");
buf.append("class B<V, W> extends A<W, V> {\n");
buf.append(" public B() {}\n");
buf.append(" @Override public void o2_foo1(Map<W, V> t) {}\n");
buf.append(" @Override public void o2_xoo1(List<? extends W> t) {}\n");
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("A.java", buf.toString(), false, null);
doOverrideTests(cu, 1, 1, 0); // B and A
}
@Test
public void testOverride3() throws Exception {
IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null);
StringBuilder buf= new StringBuilder();
buf.append("package test1;\n");
buf.append("import java.util.List;\n");
buf.append("public class A {\n");
buf.append(" public A() {}\n");
buf.append(" public void o3_foo1(List<Object> t) {}\n");
buf.append(" public void o3_xoo1(List t) {}\n");
buf.append("}\n");
buf.append("class B extends A {\n");
buf.append(" public B() {}\n");
buf.append(" @Override public void o3_foo1(List t) {}\n");
buf.append(" @Override public void o3_xoo1(List<Object> t) {}\n");
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("A.java", buf.toString(), false, null);
doOverrideTests(cu, 1, 1, 0); // B and A
}
@Test
public void testOverride4() throws Exception {
IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null);
StringBuilder buf= new StringBuilder();
buf.append("package test1;\n");
buf.append("import java.util.List;\n");
buf.append("public class A<T> {\n");
buf.append(" public A() {}\n");
buf.append(" public void o4_foo1(T[] t) {}\n");
buf.append(" public void o4_foo2(T t) {}\n");
buf.append(" public void o4_foo3(T t) {}\n");
buf.append(" public void o4_xoo1(T t) {}\n");
buf.append("}\n");
buf.append("class B extends A<List<String>> {\n");
buf.append(" public B() {}\n");
buf.append(" @Override public void o4_foo1(List<String>[] t) {}\n");
buf.append(" @Override public void o4_foo2(List<String> t) {}\n");
buf.append(" @Override public void o4_foo3(List t) {}\n");
buf.append(" @Override public void o4_xoo1(List<?> t) {}\n");
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("A.java", buf.toString(), false, null);
doOverrideTests(cu, 1, 1, 0); // B and A
}
@Test
public void testOverrideMethodTypeParams1() throws Exception {
IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
// ATTENTION: Method names in this test must be in alphabetic order!
IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null);
StringBuilder buf= new StringBuilder();
buf.append("package test1;\n");
buf.append("public class A<S> {\n");
buf.append(" public A() {}\n");
buf.append(" public <T extends Enum<T>> T getEnum(String name, Class<T> clazz) { return null; }\n");
buf.append(" public <X> void tp1_foo1(S s, X x) {}\n");
buf.append(" public <X, Y> void tp1_foo2(S s, X x, Y y) {}\n");
buf.append(" public <X, Y> void tp1_foo3(X x, Y y) {}\n");
buf.append(" public <X extends Number> void tp1_foo4(X x) {}\n");
buf.append(" public <X extends Object> void tp1_foo5(X x) {}\n");
buf.append(" public <X, Y> void tp1_xoo1(S s, X x, Y y) {}\n");
buf.append(" public void tp1_xoo2() {}\n");
buf.append(" public <X, Y> void tp1_xoo3(X x, Y y) {}\n");
// buf.append(" public <T extends Number & Runnable> void tp1_xoo4() {}\n");
buf.append(" public <X extends Number> void tp1_xoo5(X x) {}\n");
buf.append("}\n");
buf.append("class B extends A<String> {\n");
buf.append(" public B() {}\n");
buf.append(" @Override public <E extends Enum<E>> E getEnum(String name, Class<E> clazz) { return null; }\n");
buf.append(" @Override public <X> void tp1_foo1(String s, X x) {}\n");
buf.append(" @Override public void tp1_foo2(String s, Object x, Object y) {}\n");
buf.append(" @Override public <V, W> void tp1_foo3(V x, W y) {}\n");
buf.append(" @Override public <X extends Number> void tp1_foo4(X x) {}\n");
buf.append(" @Override public <X> void tp1_foo5(X x) {}\n");
buf.append(" @Override public <X> void tp1_xoo1(String s, X x, Object y) {}\n");
buf.append(" @Override public <X> void tp1_xoo2() {}\n");
buf.append(" @Override public <W, V> void tp1_xoo3(V x, W y) {}\n");
// buf.append(" @Override public <T extends Number> void tp1_xoo4() {}\n"); // jdt.core bug, need to compare all bounds
buf.append(" @Override public <X> void tp1_xoo5(Number x) {}\n");
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("A.java", buf.toString(), false, null);
doOverrideTests(cu, 1, 1, 0); // B and A
}
@Test
public void testOverrideRaw1() throws Exception {
IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null);
StringBuilder buf= new StringBuilder();
buf.append("package test1;\n");
buf.append("public class A<S> {\n");
buf.append(" public A() {}\n");
buf.append(" public void r1_foo1(S s) {}\n");
buf.append(" public void r1_foo2(A<S> s) {}\n");
// buf.append(" public void r1_xoo1(A<S> s) {}\n");
buf.append("}\n");
buf.append("class B extends A {\n");
buf.append(" public B() {}\n");
buf.append(" @Override public void r1_foo1(Object s) {}\n");
buf.append(" @Override public void r1_foo2(A s) {}\n");
// buf.append(" @Override public void r1_xoo1(A<Object> s) {}\n"); // bug in our implementation: extended raw type has all types raw
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("A.java", buf.toString(), false, null);
doOverrideTests(cu, 1, 1, 0); // B and A
}
protected void doOverrideTests(ICompilationUnit cu, int focusIndex, int overridingIndex, int overriddenIndex) throws JavaModelException {
CompilationUnit root= assertNoCompilationError(cu);
IType[] types= cu.getTypes();
ITypeBinding[] typeBindings= new ITypeBinding[types.length];
typeBindings[types.length - 1]= ((TypeDeclaration) root.types().get(types.length - 1)).resolveBinding();
for (int i= types.length - 2; i >= 0; i--) {
typeBindings[i]= typeBindings[i + 1].getSuperclass();
}
IType focusType= types[focusIndex];
IType overridingType= types[overridingIndex];
ITypeBinding overridingTypeBinding= typeBindings[overridingIndex];
assertSameType(overridingType, overridingTypeBinding);
IType overriddenType= types[overriddenIndex];
ITypeBinding overriddenTypeBinding= typeBindings[overriddenIndex];
assertSameType(overriddenType, overriddenTypeBinding);
doOverrideTests(root, focusType, overridingType, overridingTypeBinding, overriddenType, overriddenTypeBinding);
}
protected void doOverrideTests(CompilationUnit root, IType focusType, IType overridingType, ITypeBinding overridingTypeBinding, IType overriddenType, ITypeBinding overriddenTypeBinding)
throws JavaModelException {
ITypeHierarchy hierarchy= focusType.newTypeHierarchy(null);
MethodOverrideTester tester= new MethodOverrideTester(focusType, hierarchy);
IMethod[] overridingMethods= overridingType.getMethods();
IMethod[] overriddenMethods= overriddenType.getMethods();
IMethodBinding[] overridingBindings= overridingTypeBinding.getDeclaredMethods();
IMethodBinding[] overriddenBindings= overriddenTypeBinding.getDeclaredMethods();
for (int i= 0; i < overridingMethods.length; i++) {
IMethod overriding= overridingMethods[i];
IMethodBinding overridingBinding= overridingBindings[i];
IMethod overriddenMethod= overriddenMethods[i];
IMethodBinding overriddenBinding= overriddenBindings[i];
Boolean overrideAnnotation= isOverrideAnnotationOK(root, overridingBinding);
if (overrideAnnotation == null) {
continue; // only look at methods with the override annotation
}
boolean overrideAnnotationResult= overrideAnnotation.booleanValue();
boolean testerOverrides= tester.isSubsignature(overriding, overriddenMethod);
boolean uiBindingsIsSubsignature= Bindings.isSubsignature(overridingBinding, overriddenBinding);
if (DEBUG_SHOWRESULTS) {
boolean bindingOverrides= overridingBinding.getMethodDeclaration().overrides(overriddenBinding.getMethodDeclaration());
boolean bindingIsSubsignature= overridingBinding.isSubsignature(overriddenBinding);
if (testerOverrides != overrideAnnotationResult || testerOverrides != bindingOverrides
|| testerOverrides != bindingIsSubsignature || testerOverrides != uiBindingsIsSubsignature) {
System.out.println();
System.out.println("====================================");
System.out.println("getName()");
System.out.println("====================================");
System.out.println("IMethodBinding.overrides(): " + String.valueOf(bindingOverrides));
System.out.println("IMethodBinding.isSubsignature(): " + String.valueOf(bindingIsSubsignature));
System.out.println("MethodOverrideTester.isSubsignature(): " + String.valueOf(testerOverrides));
System.out.println("Bindings.isSubsignature(): " + String.valueOf(uiBindingsIsSubsignature));
System.out.println("Override Annotation: " + String.valueOf(overrideAnnotationResult));
System.out.println();
System.out.println(getCodeString(overridingBinding, overriddenBinding, root));
System.out.println();
}
// else {
// System.out.println(getDebugString(overridingBinding, overriddenBinding));
// System.out.println(" " + String.valueOf(overrideAnnotationResult));
// }
}
if (overrideAnnotationResult != testerOverrides) {
assertEquals(getDebugString(overridingBinding, overriddenBinding), overrideAnnotationResult, testerOverrides);
}
/*if (overrideAnnotationResult != uiBindingsIsSubsignature) {
assertEquals(getDebugString(overridingBinding, overriddenBinding), overrideAnnotationResult, uiBindingsIsSubsignature);
}*/
}
}
private static String getDebugString(IMethodBinding overriding, IMethodBinding overriddenMethod) {
StringBuilder buf= new StringBuilder();
buf.append(BindingLabelProvider.getBindingLabel(overriding, JavaElementLabels.M_PARAMETER_TYPES | JavaElementLabels.M_FULLY_QUALIFIED | JavaElementLabels.T_TYPE_PARAMETERS));
buf.append(" - ");
buf.append(BindingLabelProvider.getBindingLabel(overriddenMethod, JavaElementLabels.M_PARAMETER_TYPES | JavaElementLabels.M_FULLY_QUALIFIED | JavaElementLabels.T_TYPE_PARAMETERS));
buf.append(" (");
buf.append(BindingLabelProvider.getBindingLabel(overriddenMethod.getMethodDeclaration(), JavaElementLabels.M_PARAMETER_TYPES | JavaElementLabels.T_TYPE_PARAMETERS));
buf.append(")");
return buf.toString();
}
private static String getCodeString(IMethodBinding overriding, IMethodBinding overriddenMethod, CompilationUnit root) {
StringBuilder buf= new StringBuilder();
buf.append("// Overridden: ----------------------------------\n");
buf.append(getCode(overriddenMethod, root)).append('\n');
buf.append("// Overriding: ----------------------------------\n");
buf.append(getCode(overriding, root)).append('\n');
return buf.toString();
}
private static String getCode(IMethodBinding binding, CompilationUnit root) {
final MethodDeclaration method= (MethodDeclaration) root.findDeclaringNode(binding.getMethodDeclaration());
ASTFlattener flattener= new ASTFlattener() {
@Override
public boolean visit(MethodDeclaration node) {
if (node == method) {
super.visit(node);
}
return false;
}
};
method.getParent().accept(flattener);
String unformatted= flattener.getResult();
TextEdit edit= CodeFormatterUtil.format2(method.getParent(), unformatted, 0, "\n", null);
if (edit != null) {
Document document= new Document(unformatted);
try {
edit.apply(document, TextEdit.NONE);
} catch (BadLocationException e) {
JavaPlugin.log(e);
}
return document.get();
}
return unformatted; // unknown node
}
private void assertSameType(IType type, ITypeBinding binding) throws JavaModelException {
assertNotNull(binding);
assertEquals(type.getElementName(), binding.getTypeDeclaration().getName());
IMethod[] methods= type.getMethods();
IMethodBinding[] bindings= binding.getDeclaredMethods();
assertEquals(methods.length, bindings.length);
}
protected CompilationUnit assertNoCompilationError(ICompilationUnit cu) {
ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
parser.setSource(cu);
parser.setResolveBindings(true);
CompilationUnit root= (CompilationUnit) parser.createAST(null);
IProblem[] problems= root.getProblems();
boolean hasProblems= false;
for (IProblem prob : problems) {
if (prob.isWarning() || prob.isInfo()) {
continue;
}
switch (prob.getID()) {
// ignored problems
case IProblem.MethodNameClash:
continue;
case IProblem.MethodMustOverride:
continue;
default:
hasProblems= true;
break;
}
}
if (hasProblems) {
StringBuilder buf= new StringBuilder();
buf.append(cu.getElementName() + " has compilation problems: \n");
for (IProblem prob : problems) {
buf.append(prob.getMessage()).append('\n');
}
fail(buf.toString());
}
return root;
}
private Boolean isOverrideAnnotationOK(CompilationUnit root, IMethodBinding overriding) {
ASTNode node= root.findDeclaringNode(overriding);
if (node instanceof MethodDeclaration && hasOverrideAnnotation((MethodDeclaration) node)) {
int start= node.getStartPosition();
int end= start + node.getLength();
for (IProblem prob : root.getProblems()) {
if (prob.getID() == IProblem.MethodMustOverride) {
int pos= prob.getSourceStart();
if (start <= pos && pos < end) {
return Boolean.FALSE;
}
}
}
return Boolean.TRUE;
}
return null;
}
private boolean hasOverrideAnnotation(MethodDeclaration declaration) {
List<IExtendedModifier> list= declaration.modifiers();
for (IExtendedModifier element : list) {
if (element instanceof Annotation) {
return "Override".equals(((Annotation) element).getTypeName().getFullyQualifiedName());
}
}
return false;
}
}