| /* |
| * Copyright 2014-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.java.abi; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ComparisonChain; |
| import com.google.common.collect.Sets; |
| import com.google.common.io.ByteSource; |
| |
| import org.objectweb.asm.AnnotationVisitor; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.ClassWriter; |
| import org.objectweb.asm.FieldVisitor; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| |
| import java.io.IOException; |
| import java.util.SortedSet; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarOutputStream; |
| |
| import javax.annotation.Nullable; |
| |
| class ClassMirror extends ClassVisitor implements Comparable<ClassMirror> { |
| |
| private final String fileName; |
| private final SortedSet<AnnotationMirror> annotations; |
| private final SortedSet<FieldMirror> fields; |
| private final SortedSet<InnerClass> innerClasses; |
| private final SortedSet<MethodMirror> methods; |
| @Nullable |
| private OuterClass outerClass; |
| private int version; |
| private int access; |
| @Nullable |
| private String signature; |
| @Nullable |
| private String[] interfaces; |
| @Nullable |
| private String superName; |
| @Nullable |
| private String name; |
| |
| public ClassMirror(String name) { |
| super(Opcodes.ASM5); |
| |
| this.fileName = name; |
| this.annotations = Sets.newTreeSet(); |
| this.fields = Sets.newTreeSet(); |
| this.innerClasses = Sets.newTreeSet(); |
| this.methods = Sets.newTreeSet(); |
| } |
| |
| @Override |
| public void visit( |
| int version, |
| int access, |
| String name, |
| String signature, |
| String superName, |
| String[] interfaces) { |
| this.name = name; |
| this.version = version; |
| this.access = access; |
| this.signature = signature; |
| this.interfaces = interfaces; |
| this.superName = superName; |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotation(String desc, boolean visible) { |
| AnnotationMirror mirror = new AnnotationMirror(desc, visible); |
| annotations.add(mirror); |
| return mirror; |
| } |
| |
| @Override |
| public FieldVisitor visitField( |
| int access, |
| String name, |
| String desc, |
| String signature, |
| Object value) { |
| if ((access & Opcodes.ACC_PRIVATE) > 0) { |
| return super.visitField(access, name, desc, signature, value); |
| } |
| |
| FieldMirror mirror = new FieldMirror(access, name, desc, signature, value); |
| fields.add(mirror); |
| return mirror; |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String desc, String signature, String[] exceptions) { |
| |
| if ((access & Opcodes.ACC_PRIVATE) > 0) { |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| |
| // Bridge methods are created by the compiler, and don't appear in source. It would be nice to |
| // skip them, but they're used by the compiler to cover the fact that type erasure has occurred. |
| // Normally the compiler adds these as public methods, but if you're compiling against a stub |
| // produced using our ABI generator, we don't want people calling it accidentally. Oh well, I |
| // guess it happens IRL too. |
| // |
| // Synthetic methods are also generated by the compiler, unless it's one of the methods named in |
| // section 4.7.8 of the JVM spec, which are "<init>" and "Enum.valueOf()" and "Enum.values". |
| // None of these are actually harmful to the ABI, so we allow synthetic methods through. |
| // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.8 |
| MethodMirror mirror = new MethodMirror(access, name, desc, signature, exceptions); |
| methods.add(mirror); |
| |
| return mirror; |
| } |
| |
| @Override |
| public void visitOuterClass(String owner, String name, String desc) { |
| outerClass = new OuterClass(owner, name, desc); |
| super.visitOuterClass(owner, name, desc); |
| } |
| |
| @Override |
| public void visitInnerClass(String name, String outerName, String innerName, int access) { |
| if ((access & Opcodes.ACC_PRIVATE) > 0) { |
| return; |
| } |
| |
| innerClasses.add(new InnerClass(name, outerName, innerName, access)); |
| super.visitInnerClass(name, outerName, innerName, access); |
| } |
| |
| @Override |
| public int compareTo(ClassMirror o) { |
| return fileName.compareTo(o.fileName); |
| } |
| |
| public void writeTo(JarOutputStream jar) throws IOException { |
| JarEntry entry = new JarEntry(fileName); |
| entry.setTime(0); |
| |
| jar.putNextEntry(entry); |
| ClassWriter writer = new ClassWriter(0); |
| writer.visit(version, access, name, signature, superName, interfaces); |
| |
| if (outerClass != null) { |
| writer.visitOuterClass(outerClass.owner, outerClass.name, outerClass.desc); |
| } |
| |
| for (InnerClass inner : innerClasses) { |
| writer.visitInnerClass(inner.name, inner.outerName, inner.innerName, inner.access); |
| } |
| |
| for (AnnotationMirror annotation : annotations) { |
| annotation.appendTo(writer); |
| } |
| |
| for (FieldMirror field : fields) { |
| field.accept(writer); |
| } |
| |
| for (MethodMirror method : methods) { |
| method.appendTo(writer); |
| } |
| writer.visitEnd(); |
| ByteSource.wrap(writer.toByteArray()).copyTo(jar); |
| jar.closeEntry(); |
| } |
| |
| private static class InnerClass implements Comparable<InnerClass> { |
| |
| private final String name; |
| @Nullable |
| private final String outerName; |
| @Nullable |
| private final String innerName; |
| private final int access; |
| |
| public InnerClass( |
| String name, |
| @Nullable String outerName, |
| @Nullable String innerName, |
| int access) { |
| this.name = name; |
| this.outerName = outerName; |
| this.innerName = innerName; |
| this.access = access; |
| } |
| |
| @Override |
| public int compareTo(InnerClass o) { |
| Preconditions.checkNotNull(o); |
| return ComparisonChain.start() |
| .compare(name, o.name) |
| .compare(Strings.nullToEmpty(outerName), Strings.nullToEmpty(o.outerName)) |
| .compare(Strings.nullToEmpty(innerName), Strings.nullToEmpty(o.innerName)) |
| .result(); |
| } |
| } |
| |
| private static class OuterClass implements Comparable<OuterClass> { |
| |
| private final String owner; |
| private final String name; |
| private final String desc; |
| |
| public OuterClass(String owner, String name, String desc) { |
| this.owner = owner; |
| this.name = name; |
| this.desc = desc; |
| } |
| |
| @Override |
| public int compareTo(OuterClass o) { |
| Preconditions.checkNotNull(o); |
| return ComparisonChain.start() |
| .compare(owner, o.owner) |
| .compare(name, o.name) |
| .compare(desc, o.desc) |
| .result(); |
| } |
| } |
| } |