/*
* Copyright 2017-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;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementScanner8;
import javax.lang.model.util.Elements;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
class ClassVisitorDriverFromElement {
private final DescriptorFactory descriptorFactory;
private final SignatureFactory signatureFactory;
private final SourceVersion targetVersion;
private final Elements elements;
/**
* @param targetVersion the class file version to target, expressed as the corresponding Java
* source version
*/
ClassVisitorDriverFromElement(SourceVersion targetVersion, Elements elements) {
this.targetVersion = targetVersion;
this.elements = elements;
descriptorFactory = new DescriptorFactory(elements);
signatureFactory = new SignatureFactory(descriptorFactory);
}
public void driveVisitor(TypeElement fullClass, ClassVisitor visitor) throws IOException {
fullClass.accept(new ElementVisitorAdapter(), visitor);
visitor.visitEnd();
}
/** Gets the class file version corresponding to the given source version constant. */
private static int sourceVersionToClassFileVersion(SourceVersion version) {
switch (version) {
case RELEASE_0:
return Opcodes.V1_1; // JVMS8 4.1: 1.0 and 1.1 both support version 45.3 (Opcodes.V1_1)
case RELEASE_1:
return Opcodes.V1_1;
case RELEASE_2:
return Opcodes.V1_2;
case RELEASE_3:
return Opcodes.V1_3;
case RELEASE_4:
return Opcodes.V1_4;
case RELEASE_5:
return Opcodes.V1_5;
case RELEASE_6:
return Opcodes.V1_6;
case RELEASE_7:
return Opcodes.V1_7;
case RELEASE_8:
return Opcodes.V1_8;
default:
throw new IllegalArgumentException(String.format("Unexpected source version: %s", version));
}
}
private interface VisitorWithAnnotations {
AnnotationVisitor visitAnnotation(String desc, boolean visible);
}
private class ElementVisitorAdapter extends ElementScanner8<Void, ClassVisitor> {
boolean classVisitorStarted = false;
List<TypeElement> innerMembers = new ArrayList<>();
// TODO(jkeljo): Type annotations
@Override
public Void visitType(TypeElement e, ClassVisitor visitor) {
if (classVisitorStarted) {
// Collect inner classes/interfaces and visit them at the end
innerMembers.add(e);
return null;
}
TypeMirror superclass = e.getSuperclass();
if (superclass.getKind() == TypeKind.NONE) {
superclass =
Preconditions.checkNotNull(elements.getTypeElement("java.lang.Object")).asType();
}
// Static never makes it into the file for classes
int accessFlags = AccessFlags.getAccessFlags(e) & ~Opcodes.ACC_STATIC;
if (e.getNestingKind() != NestingKind.TOP_LEVEL) {
if (e.getModifiers().contains(Modifier.PROTECTED)) {
// It looks like inner classes with protected visibility get marked as public, and then
// their InnerClasses attributes override that more specifically
accessFlags = (accessFlags & ~Opcodes.ACC_PROTECTED) | Opcodes.ACC_PUBLIC;
} else if (e.getModifiers().contains(Modifier.PRIVATE)) {
// It looks like inner classes with private visibility get marked as package, and then
// their InnerClasses attributes override that more specifically
accessFlags = (accessFlags & ~Opcodes.ACC_PRIVATE);
}
}
visitor.visit(
sourceVersionToClassFileVersion(targetVersion),
accessFlags,
descriptorFactory.getInternalName(e),
signatureFactory.getSignature(e),
descriptorFactory.getInternalName(superclass),
e.getInterfaces()
.stream()
.map(descriptorFactory::getInternalName)
.toArray(size -> new String[size]));
classVisitorStarted = true;
if (e.getNestingKind() == NestingKind.MEMBER) {
visitMemberClass(e, visitor);
}
visitAnnotations(e.getAnnotationMirrors(), visitor::visitAnnotation);
super.visitType(e, visitor);
// We visit all inner members in reverse to match the order in original, full jars
for (TypeElement element : Lists.reverse(innerMembers)) {
visitMemberClass(element, visitor);
}
return null;
}
private void visitMemberClass(TypeElement e, ClassVisitor visitor) {
visitor.visitInnerClass(
descriptorFactory.getInternalName(e),
descriptorFactory.getInternalName((TypeElement) e.getEnclosingElement()),
e.getSimpleName().toString(),
AccessFlags.getAccessFlags(e) & ~Opcodes.ACC_SUPER);
// We remove ACC_SUPER above because javac does as well for InnerClasses entries.
}
@Override
public Void visitExecutable(ExecutableElement e, ClassVisitor visitor) {
if (e.getModifiers().contains(Modifier.PRIVATE)) {
return null;
}
// TODO(jkeljo): Bridge methods: Look at superclasses, then interfaces, checking whether
// method types change in the new class
String[] exceptions =
e.getThrownTypes()
.stream()
.map(descriptorFactory::getInternalName)
.toArray(count -> new String[count]);
MethodVisitor methodVisitor =
visitor.visitMethod(
AccessFlags.getAccessFlags(e),
e.getSimpleName().toString(),
descriptorFactory.getDescriptor(e),
signatureFactory.getSignature(e),
exceptions);
visitParameters(e.getParameters(), methodVisitor);
visitDefaultValue(e, methodVisitor);
visitAnnotations(e.getAnnotationMirrors(), methodVisitor::visitAnnotation);
methodVisitor.visitEnd();
return null;
}
private void visitParameters(
List<? extends VariableElement> parameters, MethodVisitor methodVisitor) {
for (int i = 0; i < parameters.size(); i++) {
VariableElement parameter = parameters.get(i);
for (AnnotationMirror annotationMirror : parameter.getAnnotationMirrors()) {
if (MoreElements.isSourceRetention(annotationMirror)) {
continue;
}
visitAnnotationValues(
annotationMirror,
methodVisitor.visitParameterAnnotation(
i,
descriptorFactory.getDescriptor(annotationMirror.getAnnotationType()),
MoreElements.isRuntimeRetention(annotationMirror)));
}
}
}
private void visitDefaultValue(ExecutableElement e, MethodVisitor methodVisitor) {
AnnotationValue defaultValue = e.getDefaultValue();
if (defaultValue == null) {
return;
}
AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
visitAnnotationValue(null, defaultValue.getValue(), annotationVisitor);
annotationVisitor.visitEnd();
}
@Override
public Void visitVariable(VariableElement e, ClassVisitor classVisitor) {
if (e.getModifiers().contains(Modifier.PRIVATE)) {
return null;
}
FieldVisitor fieldVisitor =
classVisitor.visitField(
AccessFlags.getAccessFlags(e),
e.getSimpleName().toString(),
descriptorFactory.getDescriptor(e),
signatureFactory.getSignature(e),
e.getConstantValue());
visitAnnotations(e.getAnnotationMirrors(), fieldVisitor::visitAnnotation);
fieldVisitor.visitEnd();
return null;
}
private void visitAnnotations(
List<? extends AnnotationMirror> annotations, VisitorWithAnnotations visitor) {
annotations.forEach(annotation -> visitAnnotation(annotation, visitor));
}
private void visitAnnotation(AnnotationMirror annotation, VisitorWithAnnotations visitor) {
if (MoreElements.isSourceRetention(annotation)) {
return;
}
AnnotationVisitor annotationVisitor =
visitor.visitAnnotation(
descriptorFactory.getDescriptor(annotation.getAnnotationType()),
MoreElements.isRuntimeRetention(annotation));
visitAnnotationValues(annotation, annotationVisitor);
annotationVisitor.visitEnd();
}
private void visitAnnotationValues(
AnnotationMirror annotation, AnnotationVisitor annotationVisitor) {
visitAnnotationValues(annotation.getElementValues(), annotationVisitor);
}
private void visitAnnotationValues(
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues,
AnnotationVisitor visitor) {
elementValues
.entrySet()
.forEach(
entry ->
visitAnnotationValue(
entry.getKey().getSimpleName().toString(),
entry.getValue().getValue(),
visitor));
}
private void visitAnnotationValue(String name, Object value, AnnotationVisitor visitor) {
if (value instanceof Boolean
|| value instanceof Byte
|| value instanceof Character
|| value instanceof Short
|| value instanceof Integer
|| value instanceof Long
|| value instanceof Float
|| value instanceof Double
|| value instanceof String) {
visitAnnotationPrimitiveValue(name, value, visitor);
} else if (value instanceof TypeMirror) {
visitAnnotationTypeValue(name, (TypeMirror) value, visitor);
} else if (value instanceof VariableElement) {
visitAnnotationEnumValue(name, (VariableElement) value, visitor);
} else if (value instanceof AnnotationMirror) {
visitAnnotationAnnotationValue(name, (AnnotationMirror) value, visitor);
} else if (value instanceof List) {
@SuppressWarnings("unchecked") // See docs for AnnotationValue
List<? extends AnnotationValue> listValue = (List<? extends AnnotationValue>) value;
visitAnnotationArrayValue(name, listValue, visitor);
} else {
throw new IllegalArgumentException(
String.format("Unexpected annotaiton value type: %s", value.getClass()));
}
}
private void visitAnnotationPrimitiveValue(
String name, Object value, AnnotationVisitor visitor) {
visitor.visit(name, value);
}
private void visitAnnotationTypeValue(
String name, TypeMirror value, AnnotationVisitor visitor) {
visitor.visit(name, descriptorFactory.getType(value));
}
private void visitAnnotationEnumValue(
String name, VariableElement value, AnnotationVisitor visitor) {
visitor.visitEnum(
name,
descriptorFactory.getDescriptor(value.getEnclosingElement().asType()),
value.getSimpleName().toString());
}
private void visitAnnotationAnnotationValue(
String name, AnnotationMirror value, AnnotationVisitor visitor) {
AnnotationVisitor annotationValueVisitor =
visitor.visitAnnotation(name, descriptorFactory.getDescriptor(value.getAnnotationType()));
visitAnnotationValues(value, annotationValueVisitor);
annotationValueVisitor.visitEnd();
}
private void visitAnnotationArrayValue(
String name, List<? extends AnnotationValue> value, AnnotationVisitor visitor) {
AnnotationVisitor arrayMemberVisitor = visitor.visitArray(name);
value.forEach(
annotationValue ->
visitAnnotationValue(null, annotationValue.getValue(), arrayMemberVisitor));
arrayMemberVisitor.visitEnd();
}
}
}