/*
* Copyright 2016-present Facebook, Inc.
*
* Licensed 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.
*/
package com.facebook.buck.jvm.java.abi.source;
import com.facebook.buck.util.liteinfersupport.Nullable;
import com.facebook.buck.util.liteinfersupport.Preconditions;
import com.sun.source.util.Trees;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
/**
* An implementation of {@link Elements} using just the AST of a single module, without its
* dependencies. Of necessity, such an implementation will need to make assumptions about the
* meanings of some names, and thus must be used with care. See documentation for individual methods
* and {@link com.facebook.buck.jvm.java.abi.source} for more information.
*/
class TreeBackedElements implements Elements {
private final Elements javacElements;
private final Trees javacTrees;
private final Map<Element, TreeBackedElement> treeBackedElements = new HashMap<>();
private final Map<Name, TypeElement> knownTypes = new HashMap<>();
private final Map<Name, TreeBackedPackageElement> knownPackages = new HashMap<>();
@Nullable private TreeBackedElementResolver resolver;
public TreeBackedElements(Elements javacElements, Trees javacTrees) {
this.javacElements = javacElements;
this.javacTrees = javacTrees;
}
/* package */ void setResolver(TreeBackedElementResolver resolver) {
this.resolver = resolver;
}
/* package */ void clear() {
treeBackedElements.clear();
knownTypes.clear();
knownPackages.clear();
}
public TreeBackedElement enterElement(Element underlyingElement) {
TreeBackedElement result = treeBackedElements.get(underlyingElement);
if (result != null) {
return result;
}
ElementKind kind = underlyingElement.getKind();
switch (kind) {
case PACKAGE:
result = newTreeBackedPackage((PackageElement) underlyingElement);
break;
case ANNOTATION_TYPE:
case CLASS:
case ENUM:
case INTERFACE:
result = newTreeBackedType((TypeElement) underlyingElement);
break;
case TYPE_PARAMETER:
result = newTreeBackedTypeParameter((TypeParameterElement) underlyingElement);
break;
case ENUM_CONSTANT:
case FIELD:
case PARAMETER:
result = newTreeBackedVariable((VariableElement) underlyingElement);
break;
case CONSTRUCTOR:
case METHOD:
result = newTreeBackedExecutable((ExecutableElement) underlyingElement);
break;
// $CASES-OMITTED$
default:
throw new UnsupportedOperationException(String.format("Element kind %s NYI", kind));
}
treeBackedElements.put(underlyingElement, result);
return result;
}
private TreeBackedPackageElement newTreeBackedPackage(PackageElement underlyingPackage) {
TreeBackedPackageElement treeBackedPackage =
new TreeBackedPackageElement(underlyingPackage, Preconditions.checkNotNull(resolver));
knownPackages.put(treeBackedPackage.getQualifiedName(), treeBackedPackage);
return treeBackedPackage;
}
private TreeBackedTypeElement newTreeBackedType(TypeElement underlyingType) {
TreeBackedTypeElement treeBackedType =
new TreeBackedTypeElement(
underlyingType,
enterElement(underlyingType.getEnclosingElement()),
Preconditions.checkNotNull(javacTrees.getPath(underlyingType)),
Preconditions.checkNotNull(resolver));
knownTypes.put(treeBackedType.getQualifiedName(), treeBackedType);
return treeBackedType;
}
private TreeBackedTypeParameterElement newTreeBackedTypeParameter(
TypeParameterElement underlyingTypeParameter) {
TreeBackedParameterizable enclosingElement =
(TreeBackedParameterizable) enterElement(underlyingTypeParameter.getEnclosingElement());
return new TreeBackedTypeParameterElement(
underlyingTypeParameter,
Preconditions.checkNotNull(javacTrees.getPath(underlyingTypeParameter)),
enclosingElement,
Preconditions.checkNotNull(resolver));
}
private TreeBackedExecutableElement newTreeBackedExecutable(
ExecutableElement underlyingExecutable) {
return new TreeBackedExecutableElement(
underlyingExecutable,
enterElement(underlyingExecutable.getEnclosingElement()),
javacTrees.getPath(underlyingExecutable),
Preconditions.checkNotNull(resolver));
}
private TreeBackedVariableElement newTreeBackedVariable(VariableElement underlyingVariable) {
return new TreeBackedVariableElement(
underlyingVariable,
enterElement(underlyingVariable.getEnclosingElement()),
javacTrees.getPath(underlyingVariable),
Preconditions.checkNotNull(resolver));
}
@Nullable
/* package */ PackageElement getCanonicalElement(@Nullable PackageElement element) {
return (PackageElement) getCanonicalElement((Element) element);
}
@Nullable
/* package */ TypeElement getCanonicalElement(@Nullable TypeElement element) {
return (TypeElement) getCanonicalElement((Element) element);
}
@Nullable
/* package */ ExecutableElement getCanonicalElement(@Nullable ExecutableElement element) {
return (ExecutableElement) getCanonicalElement((Element) element);
}
/**
* Given a javac Element, gets the element that should be used by callers to refer to it. For
* elements that have ASTs, that will be a TreeBackedElement; otherwise the javac Element itself.
*/
@Nullable
/* package */ Element getCanonicalElement(@Nullable Element element) {
Element result = treeBackedElements.get(element);
if (result == null) {
result = element;
}
return result;
}
/* package */ Element[] getJavacElements(Element[] elements) {
return Arrays.stream(elements).map(this::getJavacElement).toArray(Element[]::new);
}
/* package */ TypeElement getJavacElement(TypeElement element) {
return (TypeElement) getJavacElement((Element) element);
}
/* package */ Element getJavacElement(Element element) {
if (element instanceof TreeBackedElement) {
TreeBackedElement treeBackedElement = (TreeBackedElement) element;
return treeBackedElement.getUnderlyingElement();
}
return element;
}
/**
* Gets the package element with the given name. If a package with the given name is referenced in
* the code or exists in the classpath, returns the corresponding element. Otherwise returns null.
*/
@Override
@Nullable
public TreeBackedPackageElement getPackageElement(CharSequence qualifiedNameString) {
Name qualifiedName = getName(qualifiedNameString);
if (!knownPackages.containsKey(qualifiedName)) {
PackageElement javacPackageElement = javacElements.getPackageElement(qualifiedName);
if (javacPackageElement != null) {
enterElement(javacPackageElement);
}
}
return knownPackages.get(qualifiedName);
}
/**
* Gets the type element with the given name. If a class with the given name is referenced in the
* code or exists in the classpath, returns the corresponding element. Otherwise returns null.
*/
@Override
@Nullable
public TypeElement getTypeElement(CharSequence fullyQualifiedCharSequence) {
Name fullyQualifiedName = getName(fullyQualifiedCharSequence);
if (!knownTypes.containsKey(fullyQualifiedName)) {
// If none of the types for which we have parse trees matches this fully-qualified name,
// ask javac. javac will check the classpath, which will pick up built-ins (like java.lang)
// and any types from dependency targets that are already compiled and on the classpath.
// Because our tree-backed elements and javac's elements are sharing a name table, we
// should be able to mix implementations without causing too much trouble.
TypeElement javacElement = javacElements.getTypeElement(fullyQualifiedName);
if (javacElement != null) {
knownTypes.put(fullyQualifiedName, javacElement);
}
}
return knownTypes.get(fullyQualifiedName);
}
@Override
public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults(
AnnotationMirror a) {
Map<ExecutableElement, AnnotationValue> result = new HashMap<>();
result.putAll(a.getElementValues());
TypeElement annotationType = (TypeElement) a.getAnnotationType().asElement();
List<ExecutableElement> parameters =
ElementFilter.methodsIn(annotationType.getEnclosedElements());
for (ExecutableElement parameter : parameters) {
if (!result.containsKey(parameter) && parameter.getDefaultValue() != null) {
result.put(parameter, parameter.getDefaultValue());
}
}
return result;
}
@Override
@Nullable
public String getDocComment(Element e) {
return javacElements.getDocComment(getJavacElement(e));
}
@Override
public boolean isDeprecated(Element e) {
return javacElements.isDeprecated(getJavacElement(e));
}
@Override
public Name getBinaryName(TypeElement type) {
return javacElements.getBinaryName(getJavacElement(type));
}
@Override
public PackageElement getPackageOf(Element type) {
return Preconditions.checkNotNull(
getCanonicalElement(javacElements.getPackageOf(getJavacElement(type))));
}
@Override
public List<? extends Element> getAllMembers(TypeElement type) {
throw new UnsupportedOperationException();
}
@Override
public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) {
throw new UnsupportedOperationException();
}
@Override
public boolean hides(Element hider, Element hidden) {
throw new UnsupportedOperationException();
}
@Override
public boolean overrides(
ExecutableElement overrider, ExecutableElement overridden, TypeElement type) {
throw new UnsupportedOperationException();
}
@Override
public String getConstantExpression(Object value) {
return javacElements.getConstantExpression(value);
}
@Override
public void printElements(Writer w, Element... elements) {
throw new UnsupportedOperationException();
}
@Override
public Name getName(CharSequence cs) {
return javacElements.getName(cs);
}
@Override
public boolean isFunctionalInterface(TypeElement type) {
throw new UnsupportedOperationException();
}
}