com.google.devtools.build.android.resources.RClassWriter.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.android.resources.RClassWriter.java

Source

// Copyright 2016 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.android.resources;

import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Table;
import com.google.common.io.Files;

import com.android.SdkConstants;
import com.android.builder.internal.SymbolLoader;
import com.android.builder.internal.SymbolLoader.SymbolEntry;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.InstructionAdapter;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Writes out bytecode for an R.class directly, rather than go through an R.java and compile. This
 * avoids re-parsing huge R.java files and other time spent in the java compiler (e.g., plugins like
 * ErrorProne). A difference is that this doesn't generate line number tables and other debugging
 * information. Also, the order of the constant pool tends to be different.
 */
public class RClassWriter {

    private static final int JAVA_VERSION = Opcodes.V1_7;
    private static final String SUPER_CLASS = "java/lang/Object";
    private final File outFolder;
    private final String packageName;
    private final List<SymbolLoader> symbolTables = new ArrayList<>();
    private final SymbolLoader symbolValues;
    private final boolean finalFields;

    public RClassWriter(File outFolder, String packageName, SymbolLoader values, boolean finalFields) {
        this.outFolder = outFolder;
        this.packageName = packageName;
        this.symbolValues = values;
        this.finalFields = finalFields;
    }

    public void addSymbolsToWrite(SymbolLoader symbols) {
        symbolTables.add(symbols);
    }

    private Table<String, String, SymbolEntry> getAllSymbols() throws IOException {
        Table<String, String, SymbolEntry> symbols = HashBasedTable.create();
        for (SymbolLoader symbolLoader : symbolTables) {
            symbols.putAll(getSymbols(symbolLoader));
        }
        return symbols;
    }

    private Method symbolsMethod;

    private Table<String, String, SymbolEntry> getSymbols(SymbolLoader symbolLoader) throws IOException {
        // TODO(bazel-team): upstream a patch to change the visibility instead of hacking it.
        try {
            if (symbolsMethod == null) {
                Method getSymbols = SymbolLoader.class.getDeclaredMethod("getSymbols");
                getSymbols.setAccessible(true);
                symbolsMethod = getSymbols;
            }
            @SuppressWarnings("unchecked")
            Table<String, String, SymbolEntry> result = (Table<String, String, SymbolEntry>) symbolsMethod
                    .invoke(symbolLoader);
            return result;
        } catch (ReflectiveOperationException e) {
            throw new IOException(e);
        }
    }

    /**
     * Builds the bytecode and writes out the R.class file, and R$inner.class files.
     */
    public void write() throws IOException {
        Splitter splitter = Splitter.on('.');
        Iterable<String> folders = splitter.split(packageName);
        File packageDir = outFolder;
        for (String folder : folders) {
            packageDir = new File(packageDir, folder);
        }
        File rClassFile = new File(packageDir, SdkConstants.FN_COMPILED_RESOURCE_CLASS);
        Files.createParentDirs(rClassFile);
        String packageWithSlashes = packageName.replaceAll("\\.", "/");
        String rClassName = packageWithSlashes + "/R";
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        classWriter.visit(JAVA_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, rClassName,
                null, SUPER_CLASS, null);
        classWriter.visitSource(SdkConstants.FN_RESOURCE_CLASS, null);
        writeConstructor(classWriter);

        Table<String, String, SymbolEntry> symbols = getAllSymbols();
        Table<String, String, SymbolEntry> values = getSymbols(symbolValues);

        Set<String> rowSet = symbols.rowKeySet();
        List<String> rowList = new ArrayList<>(rowSet);
        Collections.sort(rowList);

        // Build the R.class w/ the inner classes, then later build the individual R$inner.class.
        for (String row : rowList) {
            String innerClassName = rClassName + "$" + row;
            classWriter.visitInnerClass(innerClassName, rClassName, row,
                    Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC);
        }
        classWriter.visitEnd();
        Files.write(classWriter.toByteArray(), rClassFile);

        // Now generate the R$inner.class files.
        for (String row : rowList) {
            writeInnerClass(symbols, values, packageDir, rClassName, row);
        }
    }

    /**
     * Represents an int or int[] field and its initializer (where initialization is done via code in
     * the static clinit function).
     */
    private interface DeferredInitializer {

        /**
         * Write the code for the initializer via insts.
         *
         * @return the number of stack slots needed for the code.
         */
        int writeCLInit(String className, InstructionAdapter insts);
    }

    private static final class IntArrayDeferredInitializer implements DeferredInitializer {

        private final String fieldName;
        private final ImmutableList<Integer> values;

        IntArrayDeferredInitializer(String fieldName, ImmutableList<Integer> values) {
            this.fieldName = fieldName;
            this.values = values;
        }

        public static DeferredInitializer of(String name, String value) {
            Preconditions.checkArgument(value.startsWith("{ "), "Expected list starting with { ");
            Preconditions.checkArgument(value.endsWith(" }"), "Expected list ending with } ");
            // Check for an empty list, which is "{ }".
            if (value.length() < 4) {
                return new IntArrayDeferredInitializer(name, ImmutableList.<Integer>of());
            }
            ImmutableList.Builder<Integer> intValues = ImmutableList.builder();
            String trimmedValue = value.substring(2, value.length() - 2);
            Iterable<String> valueStrings = Splitter.on(',').trimResults().omitEmptyStrings().split(trimmedValue);
            for (String valueString : valueStrings) {
                intValues.add(Integer.decode(valueString));
            }
            return new IntArrayDeferredInitializer(name, intValues.build());
        }

        @Override
        public int writeCLInit(String className, InstructionAdapter insts) {
            insts.iconst(values.size());
            insts.newarray(Type.INT_TYPE);
            int curIndex = 0;
            for (Integer value : values) {
                insts.dup();
                insts.iconst(curIndex);
                insts.iconst(value);
                insts.astore(Type.INT_TYPE);
                ++curIndex;
            }
            insts.putstatic(className, fieldName, "[I");
            // Needs up to 4 stack slots for: the array ref for the putstatic, the dup of the array ref
            // for the store, the index, and the value to store.
            return 4;
        }
    }

    private static final class IntDeferredInitializer implements DeferredInitializer {

        private final String fieldName;
        private final Integer value;

        IntDeferredInitializer(String fieldName, Integer value) {
            this.fieldName = fieldName;
            this.value = value;
        }

        public static DeferredInitializer of(String name, String value) {
            return new IntDeferredInitializer(name, Integer.decode(value));
        }

        @Override
        public int writeCLInit(String className, InstructionAdapter insts) {
            insts.iconst(value);
            insts.putstatic(className, fieldName, "I");
            // Just needs one stack slot for the iconst.
            return 1;
        }
    }

    private void writeInnerClass(Table<String, String, SymbolEntry> symbols,
            Table<String, String, SymbolEntry> values, File packageDir, String fullyQualifiedOuterClass,
            String innerClass) throws IOException {
        ClassWriter innerClassWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        String fullyQualifiedInnerClass = fullyQualifiedOuterClass + "$" + innerClass;
        innerClassWriter.visit(JAVA_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
                fullyQualifiedInnerClass, null, SUPER_CLASS, null);
        innerClassWriter.visitSource("R.java", null);
        writeConstructor(innerClassWriter);
        innerClassWriter.visitInnerClass(fullyQualifiedInnerClass, fullyQualifiedOuterClass, innerClass,
                Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC);

        Map<String, SymbolEntry> rowMap = symbols.row(innerClass);
        Set<String> symbolSet = rowMap.keySet();
        List<String> symbolList = new ArrayList<>(symbolSet);
        Collections.sort(symbolList);
        List<DeferredInitializer> deferredInitializers = new ArrayList<>();
        int fieldAccessLevel = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
        if (finalFields) {
            fieldAccessLevel |= Opcodes.ACC_FINAL;
        }
        for (String symbolName : symbolList) {
            // get the matching SymbolEntry from the values Table.
            SymbolEntry value = values.get(innerClass, symbolName);
            if (value != null) {
                String desc;
                Object initializer = null;
                if (value.getType().equals("int")) {
                    desc = "I";
                    if (finalFields) {
                        initializer = Integer.decode(value.getValue());
                    } else {
                        deferredInitializers.add(IntDeferredInitializer.of(value.getName(), value.getValue()));
                    }
                } else {
                    Preconditions.checkArgument(value.getType().equals("int[]"));
                    desc = "[I";
                    deferredInitializers.add(IntArrayDeferredInitializer.of(value.getName(), value.getValue()));
                }
                innerClassWriter.visitField(fieldAccessLevel, value.getName(), desc, null, initializer).visitEnd();
            }
        }

        if (!deferredInitializers.isEmpty()) {
            // build the <clinit> method.
            writeStaticClassInit(innerClassWriter, fullyQualifiedInnerClass, deferredInitializers);
        }

        innerClassWriter.visitEnd();
        File innerFile = new File(packageDir, "R$" + innerClass + ".class");
        Files.write(innerClassWriter.toByteArray(), innerFile);
    }

    private static void writeConstructor(ClassWriter classWriter) {
        MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        constructor.visitCode();
        constructor.visitVarInsn(Opcodes.ALOAD, 0);
        constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, SUPER_CLASS, "<init>", "()V", false);
        constructor.visitInsn(Opcodes.RETURN);
        constructor.visitMaxs(1, 1);
        constructor.visitEnd();
    }

    private static void writeStaticClassInit(ClassWriter classWriter, String className,
            List<DeferredInitializer> deferredInitializers) {
        MethodVisitor visitor = classWriter.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
        visitor.visitCode();
        int stackSlotsNeeded = 0;
        InstructionAdapter insts = new InstructionAdapter(visitor);
        for (DeferredInitializer fieldInit : deferredInitializers) {
            stackSlotsNeeded = Math.max(stackSlotsNeeded, fieldInit.writeCLInit(className, insts));
        }
        insts.areturn(Type.VOID_TYPE);
        visitor.visitMaxs(stackSlotsNeeded, 0);
        visitor.visitEnd();
    }

}