com.facebook.buck.jvm.java.abi.StubJarClassEntry.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.jvm.java.abi.StubJarClassEntry.java

Source

/*
 * 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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InnerClassNode;

class StubJarClassEntry extends StubJarEntry {
    @Nullable
    private final Set<String> referencedClassNames;
    private final Path path;
    private final ClassNode stub;

    @Nullable
    public static StubJarClassEntry of(LibraryReader input, Path path, boolean sourceAbiCompatible)
            throws IOException {
        ClassNode stub = new ClassNode(Opcodes.ASM5);

        // As we read the class in, we create a partial stub that removes non-ABI methods and fields
        // but leaves the entire InnerClasses table. We record all classes that are referenced from
        // ABI methods and fields, and will use that information later to filter the InnerClasses table.
        ClassReferenceTracker referenceTracker = new ClassReferenceTracker(stub);
        ClassVisitor firstLevelFiltering = new AbiFilteringClassVisitor(referenceTracker);

        // If we want ABIs that are compatible with those generated from source, we add a visitor
        // at the very start of the chain which transforms the event stream coming out of `ClassNode`
        // to look like what ClassVisitorDriverFromElement would have produced.
        if (sourceAbiCompatible) {
            firstLevelFiltering = new SourceAbiCompatibleVisitor(firstLevelFiltering);
        }
        input.visitClass(path, firstLevelFiltering);

        // The synthetic package-info class is how package annotations are recorded; that one is
        // actually used by the compiler
        if (!isAnonymousOrLocalOrSyntheticClass(stub) || stub.name.endsWith("/package-info")) {
            return new StubJarClassEntry(path, stub, referenceTracker.getReferencedClassNames());
        }

        return null;
    }

    private StubJarClassEntry(Path path, ClassNode stub, Set<String> referencedClassNames) {
        this.path = path;
        this.stub = stub;
        this.referencedClassNames = referencedClassNames;
    }

    @Override
    public void write(StubJarWriter writer) throws IOException {
        writer.writeEntry(path, this::openInputStream);
    }

    private InputStream openInputStream() {
        ClassWriter writer = new ClassWriter(0);
        ClassVisitor visitor = writer;
        visitor = new InnerClassSortingClassVisitor(stub.name, visitor);
        visitor = new AbiFilteringClassVisitor(visitor, referencedClassNames);
        stub.accept(visitor);

        return new ByteArrayInputStream(writer.toByteArray());
    }

    private static boolean isAnonymousOrLocalOrSyntheticClass(ClassNode node) {
        if ((node.access & Opcodes.ACC_SYNTHETIC) == Opcodes.ACC_SYNTHETIC) {
            return true;
        }

        InnerClassNode innerClass = getInnerClassMetadata(node);
        while (innerClass != null) {
            if (innerClass.outerName == null) {
                return true;
            }
            innerClass = getInnerClassMetadata(node, innerClass.outerName);
        }

        return false;
    }

    @Nullable
    private static InnerClassNode getInnerClassMetadata(ClassNode node) {
        String name = node.name;
        return getInnerClassMetadata(node, name);
    }

    @Nullable
    private static InnerClassNode getInnerClassMetadata(ClassNode node, String className) {
        for (InnerClassNode innerClass : node.innerClasses) {
            if (innerClass.name.equals(className)) {
                return innerClass;
            }
        }

        return null;
    }

    private static class InnerClassSortingClassVisitor extends ClassVisitor {
        private final String className;
        private final List<InnerClassNode> innerClasses = new ArrayList<>();

        private InnerClassSortingClassVisitor(String className, ClassVisitor cv) {
            super(Opcodes.ASM5, cv);
            this.className = className;
        }

        @Override
        public void visitInnerClass(String name, String outerName, String innerName, int access) {
            innerClasses.add(new InnerClassNode(name, outerName, innerName, access));
        }

        @Override
        public void visitEnd() {
            innerClasses.sort((o1, o2) -> {
                // Enclosing classes and member classes should come first, with their order preserved
                boolean o1IsEnclosingOrMember = isEnclosingOrMember(o1);
                boolean o2IsEnclosingOrMember = isEnclosingOrMember(o2);
                if (o1IsEnclosingOrMember && o2IsEnclosingOrMember) {
                    // Preserve order among these
                    return 0;
                } else if (o1IsEnclosingOrMember) {
                    return -1;
                } else if (o2IsEnclosingOrMember) {
                    return 1;
                }

                // References to other classes get sorted.
                return o1.name.compareTo(o2.name);
            });

            for (InnerClassNode innerClass : innerClasses) {
                innerClass.accept(cv);
            }

            super.visitEnd();
        }

        private boolean isEnclosingOrMember(InnerClassNode innerClass) {
            if (className.equals(innerClass.name)) {
                // Self!
                return true;
            }

            if (className.equals(innerClass.outerName)) {
                // Member class
                return true;
            }

            // Enclosing class
            return className.startsWith(innerClass.name + "$");
        }
    }
}