Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8140442: Add getOutermostTypeElement to javax.lang.model utility class #5007

Closed
wants to merge 7 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -126,6 +126,10 @@ public enum ElementKind {
*/
BINDING_VARIABLE;

// Maintenance note: check if the default implementation of
// Elements.getOutermostTypeElement needs updating when new kind
// constants are added.

/**
* Returns {@code true} if this is a kind of class:
* either {@code CLASS} or {@code ENUM} or {@code RECORD}.
Original file line number Diff line number Diff line change
@@ -533,6 +533,67 @@ default ModuleElement getModuleOf(Element e) {
*/
List<? extends Element> getAllMembers(TypeElement type);

/**
* {@return the outermost type element an element is contained in
* if such a containing element exists; otherwise returns {@code
* null}}
*
* {@linkplain ModuleElement Modules} and {@linkplain
* PackageElement packages} do <em>not</em> have a containing type
* element and therefore {@code null} is returned for those kinds
* of elements.
*
* A {@link NestingKind#TOP_LEVEL top-level} class or
* interface is its own outermost type element.
*
* @implSpec
* The default implementation of this method first checks the kind
* of the argument. For elements of kind {@code PACKAGE}, {@code
* MODULE}, and {@code OTHER}, {@code null} is returned. For
* elements of other kinds, the element is examined to see if it
* is a top-level class or interface. If so, that element is
* returned; otherwise, the {@linkplain
* Element#getEnclosingElement enclosing element} chain is
* followed until a top-level class or interface is found. The
* element for the eventual top-level class or interface is
* returned.
*
* @param e the element being examined
* @see Element#getEnclosingElement
* @since 18
*/
default TypeElement getOutermostTypeElement(Element e) {
return switch (e.getKind()) {
case PACKAGE,
MODULE -> null; // Per the general spec above.
case OTHER -> null; // Outside of base model of the javax.lang.model API

// Elements of all remaining kinds should be enclosed in some
// sort of class or interface. Check to see if the element is
// a top-level type; if so, return it. Otherwise, keep going
// up the enclosing element chain until a top-level type is
// found.
default -> {
Element enclosing = e;
// This implementation is susceptible to infinite loops
// for misbehaving element implementations.
while (true) {
// Conceptual instanceof TypeElement check. If the
// argument is a type element, put it into a
// one-element list, otherwise an empty list.
List<TypeElement> possibleTypeElement = ElementFilter.typesIn(List.of(enclosing));
if (!possibleTypeElement.isEmpty()) {
TypeElement typeElement = possibleTypeElement.get(0);
if (typeElement.getNestingKind() == NestingKind.TOP_LEVEL) {
yield typeElement;
}
}
enclosing = enclosing.getEnclosingElement();
}
}
};
}

/**
* Returns all annotations <i>present</i> on an element, whether
* directly present or present via inheritance.
Original file line number Diff line number Diff line change
@@ -571,6 +571,12 @@ private void addMembers(WriteableScope scope, Type type) {
}
}

@DefinedBy(Api.LANGUAGE_MODEL)
public TypeElement getOutermostTypeElement(Element e) {
Symbol sym = cast(Symbol.class, e);
return sym.outermostClass();
}

/**
* Returns all annotations of an element, whether
* inherited or directly present.
58 changes: 58 additions & 0 deletions test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java
Original file line number Diff line number Diff line change
@@ -21,9 +21,11 @@
* questions.
*/

import java.io.Writer;
import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.*;
import static javax.lang.model.SourceVersion.*;

@@ -264,4 +266,60 @@ protected TypeKindVisitor(R defaultValue) {
super(defaultValue);
}
}

/**
* Vacuous implementation of javax.lang.model.util.Elements to aid
* in test development. Methods with defaults in the interface are
* *not* overridden to allow them to be tested.
*/
public static class VacuousElements implements Elements {
public VacuousElements() {}

@Override
public PackageElement getPackageElement(CharSequence name) {return null;}

@Override
public TypeElement getTypeElement(CharSequence name) {return null;}

@Override
public Map<? extends ExecutableElement, ? extends AnnotationValue>
getElementValuesWithDefaults(AnnotationMirror a) {return null;}
@Override
public String getDocComment(Element e) {return null;}

@Override
public boolean isDeprecated(Element e) {return false;}

@Override
public Name getBinaryName(TypeElement type) {return null;}

@Override
public PackageElement getPackageOf(Element e) {return null;}

@Override
public List<? extends Element> getAllMembers(TypeElement type) {return null;}

@Override
public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) {return null;}

@Override
public boolean hides(Element hider, Element hidden) {return false;}

@Override
public boolean overrides(ExecutableElement overrider,
ExecutableElement overridden,
TypeElement type) {return false;}

@Override
public String getConstantExpression(Object value) {return null;}

@Override
public void printElements(Writer w, Element... elements) {}

@Override
public Name getName(CharSequence cs) {return null;}

@Override
public boolean isFunctionalInterface(TypeElement type) {return false;}
}
}
Original file line number Diff line number Diff line change
@@ -52,7 +52,7 @@ public boolean process(Set<? extends TypeElement> annotations,
checkMod(enclosing, false);
}

if ((new TestElements()).isAutomaticModule(null) != false) {
if ((new VacuousElements()).isAutomaticModule(null) != false) {
throw new RuntimeException("Bad behavior from default isAutomaticModule method");
}
}
@@ -68,57 +68,4 @@ private void checkMod(ModuleElement mod, boolean expectedIsAuto) {
expectedIsAuto));
}
}

// Use default methods of javax.lang.model.util.Elements; define
// vacuous methods to override the abstract methods.
private static class TestElements implements Elements {
public TestElements() {}

@Override
public PackageElement getPackageElement(CharSequence name) {return null;}

@Override
public TypeElement getTypeElement(CharSequence name) {return null;}

@Override
public Map<? extends ExecutableElement, ? extends AnnotationValue>
getElementValuesWithDefaults(AnnotationMirror a) {return null;}
@Override
public String getDocComment(Element e) {return null;}

@Override
public boolean isDeprecated(Element e) {return false;}

@Override
public Name getBinaryName(TypeElement type) {return null;}

@Override
public PackageElement getPackageOf(Element e) {return null;}

@Override
public List<? extends Element> getAllMembers(TypeElement type) {return null;}

@Override
public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) {return null;}

@Override
public boolean hides(Element hider, Element hidden) {return false;}

@Override
public boolean overrides(ExecutableElement overrider,
ExecutableElement overridden,
TypeElement type) {return false;}

@Override
public String getConstantExpression(Object value) {return null;}

@Override
public void printElements(Writer w, Element... elements) {}

@Override
public Name getName(CharSequence cs) {return null;}

@Override
public boolean isFunctionalInterface(TypeElement type) {return false;}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
* @test
* @bug 8140442
* @summary Test Elements.getOutermostTypeElement
* @library /tools/javac/lib
* @build JavacTestingAbstractProcessor TestOutermostTypeElement
* @compile -processor TestOutermostTypeElement -proc:only TestOutermostTypeElement.java
*/

import java.io.Writer;
import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.lang.model.util.*;

/**
* Test basic workings of Elements.getOutermostTypeElement
*/
public class TestOutermostTypeElement extends JavacTestingAbstractProcessor {
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
Elements vacuousElts = new VacuousElements();

ModuleElement javaBaseMod = eltUtils.getModuleElement("java.base");
checkOuter(javaBaseMod, null, vacuousElts);
checkOuter(javaBaseMod, null, eltUtils);

PackageElement javaLangPkg = eltUtils.getPackageElement("java.lang");
checkOuter(javaLangPkg, null, vacuousElts);
checkOuter(javaLangPkg, null, eltUtils);

// Starting from the root elements, traverse over all
// enclosed elements and type parameters. The outermost
// enclosing type element should equal the root
// element. This traversal does *not* hit elements
// corresponding to structures inside of a method.
for (TypeElement e : ElementFilter.typesIn(roundEnv.getRootElements()) ) {
var outerScaner = new OuterScanner(e);
outerScaner.scan(e, vacuousElts);
outerScaner.scan(e, eltUtils);
}
}
return true;
}

private class OuterScanner extends ElementScanner<Void, Elements> {
private TypeElement expectedOuter;
public OuterScanner(TypeElement expectedOuter) {
this.expectedOuter = expectedOuter;
}

@Override
public Void scan(Element e, Elements elts) {
checkOuter(e, expectedOuter, elts);
super.scan(e, elts);
return null;
}
}

private void checkOuter(Element e, TypeElement expectedOuter, Elements elts) {
var actualOuter = elts.getOutermostTypeElement(e);
if (!Objects.equals(actualOuter, expectedOuter)) {
throw new RuntimeException(String.format("Unexpected outermost ``%s''' for %s, expected ``%s.''%n",
actualOuter,
e,
expectedOuter));
}
}
}

/**
* Outer class to host a variety of kinds of inner elements with Outer
* as their outermost class.
*/
class Outer {
private Outer() {}

public enum InnerEnum {
VALUE1,
VALUE2;

private int field;
}

public static class InnerClass {
private static int field;
static {
field = 5;
}

public <C> InnerClass(C c) {}

void foo() {return;}
static void bar() {return;}
static <R> R baz(Class<? extends R> clazz) {return null;}

private class InnerInnerClass {
public InnerInnerClass() {}
}
}

public interface InnerInterface {
final int field = 42;
void foo();
}

public @interface InnerAnnotation {
int value() default 1;
}

public record InnerRecord(double rpm, double diameter) {
void foo() {return;}
}
}