/*
* 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.event.api.BuckTracing;
import com.facebook.buck.jvm.java.abi.source.api.BootClasspathOracle;
import com.facebook.buck.jvm.java.plugin.adapter.BuckJavacTask;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
/**
* Validates the type and compile-time-constant references in the non-private interface of classes
* being compiled in the current compilation run, ensuring that they are compatible with correctly
* generating ABIs from source. When generating ABIs from source, we may be missing some (or most)
* dependencies.
*
* <p>In order to be compatible, type references to missing classes in the non-private interface of
* a class being compiled must be one of the following:
*
* <ul>
* <li>The fully-qualified name of the type
* <li>The simple name of a type that appears in an import statement
* <li>The simple name of a top-level type in the same package as the type being compiled
* </ul>
*
* <p>In order to be compatible, compile-time constants in missing classes may not be referenced
* from the non-private interface of a class being compiled.
*/
class InterfaceValidator {
private static final BuckTracing BUCK_TRACING =
BuckTracing.getInstance("ExpressionTreeResolutionValidator");
private final Elements elements;
private final Diagnostic.Kind messageKind;
private final Trees trees;
private final BootClasspathOracle bootClasspathOracle;
public InterfaceValidator(
Diagnostic.Kind messageKind, BuckJavacTask task, BootClasspathOracle bootClasspathOracle) {
this.messageKind = messageKind;
trees = task.getTrees();
elements = task.getElements();
this.bootClasspathOracle = bootClasspathOracle;
}
public void validate(List<? extends CompilationUnitTree> compilationUnits) {
try (BuckTracing.TraceSection trace = BUCK_TRACING.traceSection("buck.abi.validate")) {
new InterfaceTypeAndConstantReferenceFinder(
trees,
new InterfaceTypeAndConstantReferenceFinder.Listener() {
private final Set<Element> importedTypes = new HashSet<>();
@Override
public void onTypeImported(TypeElement type) {
importedTypes.add(type);
}
@Override
public void onTypeReferenceFound(
TypeElement referencedType, TreePath path, Element enclosingElement) {
PackageElement enclosingPackage = getPackageElement(enclosingElement);
if (typeWillBeAvailable(referencedType)
|| referenceIsLegalForMissingTypes(path, enclosingPackage, referencedType)) {
// All good!
return;
}
String minimalQualifiedName =
findMinimalQualifiedName(path, enclosingPackage, referencedType);
// TODO(jkeljo): Clearer message
trees.printMessage(
messageKind,
String.format("Must qualify the name: %s", minimalQualifiedName),
path.getLeaf(),
path.getCompilationUnit());
}
@Override
public void onConstantReferenceFound(
VariableElement constant, TreePath path, Element enclosingElement) {
TypeElement constantEnclosingType = (TypeElement) constant.getEnclosingElement();
if (typeWillBeAvailable(constantEnclosingType)) {
// All good!
return;
}
// TODO(jkeljo): Clearer message
trees.printMessage(
messageKind,
String.format(
"Must inline the constant value: %s", constant.getConstantValue()),
path.getLeaf(),
path.getCompilationUnit());
}
private boolean typeWillBeAvailable(TypeElement type) {
return isCompiledInCurrentRun(type) || isOnBootClasspath(type);
}
private boolean isCompiledInCurrentRun(TypeElement typeElement) {
return trees.getPath(typeElement) != null;
}
private boolean isOnBootClasspath(TypeElement typeElement) {
return bootClasspathOracle.isOnBootClasspath(
elements.getBinaryName(typeElement).toString());
}
private boolean referenceIsLegalForMissingTypes(
TreePath path,
PackageElement enclosingPackage,
TypeElement referencedTypeElement) {
return isImported(referencedTypeElement)
|| isTopLevelTypeInPackage(referencedTypeElement, enclosingPackage)
|| isFullyQualified(path, referencedTypeElement);
}
private boolean isImported(TypeElement referencedTypeElement) {
return importedTypes.contains(referencedTypeElement);
}
private boolean isTopLevelTypeInPackage(
TypeElement referencedTypeElement, PackageElement enclosingPackage) {
return enclosingPackage == referencedTypeElement.getEnclosingElement();
}
private boolean isFullyQualified(TreePath path, TypeElement referencedTypeElement) {
return referencedTypeElement
.getQualifiedName()
.contentEquals(TreeBackedTrees.treeToName(path.getLeaf()));
}
private String findMinimalQualifiedName(
TreePath path, PackageElement enclosingPackage, TypeElement typeElement) {
List<QualifiedNameable> enclosingElements = new ArrayList<>();
QualifiedNameable walker = typeElement;
while (walker.getKind() != ElementKind.PACKAGE
&& !referenceIsLegalForMissingTypes(
path, enclosingPackage, (TypeElement) walker)) {
enclosingElements.add(walker);
walker = (QualifiedNameable) walker.getEnclosingElement();
}
enclosingElements.add(walker);
StringBuilder resultBuilder = new StringBuilder();
for (int i = enclosingElements.size() - 1; i >= 0; i--) {
QualifiedNameable element = enclosingElements.get(i);
if (element.getKind() == ElementKind.PACKAGE) {
resultBuilder.append(element.getQualifiedName());
} else {
resultBuilder.append(element.getSimpleName());
}
if (i > 0) {
resultBuilder.append(".");
}
}
return resultBuilder.toString();
}
})
.findReferences(compilationUnits);
}
}
private static PackageElement getPackageElement(Element element) {
Element walker = element;
while (walker.getEnclosingElement() != null) {
walker = walker.getEnclosingElement();
}
return (PackageElement) walker;
}
}