Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8140442: Add getOutermostTypeElement to javax.lang.model utility class
Reviewed-by: jlahoda
  • Loading branch information
jddarcy committed Aug 10, 2021
1 parent 67869b4 commit 57ae9fb
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 55 deletions.
@@ -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
Expand Down Expand Up @@ -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}.
Expand Down
Expand Up @@ -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.
Expand Down
Expand Up @@ -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.
Expand Down
58 changes: 58 additions & 0 deletions test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java
Expand Up @@ -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.*;

Expand Down Expand Up @@ -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;}
}
}
Expand Up @@ -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");
}
}
Expand All @@ -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;}
}
}
@@ -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;}
}
}

1 comment on commit 57ae9fb

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.