net.yrom.tools.RSymbols.java Source code

Java tutorial

Introduction

Here is the source code for net.yrom.tools.RSymbols.java

Source

/*
 * Copyright (c) 2017 Yrom Wang
 *
 * 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 net.yrom.tools;

import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.TransformInput;
import com.google.common.collect.Maps;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
import static org.objectweb.asm.ClassReader.SKIP_FRAMES;

/**
 * @author yrom
 */
class RSymbols {
    /**
     * default package!
     */
    static final String R_STYLEABLES_CLASS_NAME = "R$styleable";

    private Map<String, Integer> symbols = Collections.emptyMap();
    private Map<String, int[]> styleables = Maps.newHashMap();

    public Integer get(String key) {
        return symbols.get(key);
    }

    public boolean containsKey(String key) {
        return symbols.containsKey(key);
    }

    public boolean isEmpty() {
        return symbols.isEmpty() && styleables.isEmpty();
    }

    public Map<String, int[]> getStyleables() {
        return Collections.unmodifiableMap(styleables);
    }

    public RSymbols from(Collection<TransformInput> inputs) {
        final PathMatcher rClassMatcher = FileSystems.getDefault().getPathMatcher("glob:R$*.class");
        final List<Path> paths = inputs.stream().map(TransformInput::getDirectoryInputs).flatMap(Collection::stream)
                .map(this::toStream).reduce(Stream.empty(), Stream::concat).collect(Collectors.toList());
        Stream<Path> stream;
        if (paths.size() >= Runtime.getRuntime().availableProcessors() * 3) {
            // use parallel here!
            stream = paths.parallelStream();
            symbols = Maps.newConcurrentMap();
        } else {
            stream = paths.stream();
            symbols = Maps.newHashMap();
        }
        stream.filter(path -> rClassMatcher.matches(path.getFileName())).forEach(this::drainSymbols);
        return this;
    }

    private void drainSymbols(Path file) {
        final String filename = file.getFileName().toString();
        String typeName = filename.substring(0, filename.length() - ".class".length());
        byte[] bytes;
        try {
            bytes = Files.readAllBytes(file);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5) {
            @Override
            public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
                // read constant value
                if (value instanceof Integer) {
                    String key = typeName + '.' + name;
                    Integer old = symbols.get(key);
                    if (old != null && !old.equals(value)) {
                        throw new IllegalStateException("Value of " + key + " mismatched! " + "Excepted 0x"
                                + Integer.toHexString(old) + " but was 0x" + Integer.toHexString((Integer) value));
                    } else {
                        symbols.put(key, (Integer) value);
                    }
                }
                return null;
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                    String[] exceptions) {
                if (access == Opcodes.ACC_STATIC && "<clinit>".equals(name)) {

                    return new MethodVisitor(Opcodes.ASM5) {
                        int[] current = null;
                        LinkedList<Integer> intStack = new LinkedList<>();

                        @Override
                        public void visitIntInsn(int opcode, int operand) {
                            if (opcode == Opcodes.NEWARRAY && operand == Opcodes.T_INT) {
                                current = new int[intStack.pop()];
                            } else if (opcode == Opcodes.BIPUSH) {
                                intStack.push(operand);
                            }
                        }

                        @Override
                        public void visitLdcInsn(Object cst) {
                            if (cst instanceof Integer) {
                                intStack.push((Integer) cst);
                            }
                        }

                        @Override
                        public void visitInsn(int opcode) {
                            if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) {
                                intStack.push(opcode - Opcodes.ICONST_0);
                            } else if (opcode == Opcodes.IASTORE) {
                                int value = intStack.pop();
                                int index = intStack.pop();
                                current[index] = value;
                            }
                        }

                        @Override
                        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
                            if (opcode == Opcodes.PUTSTATIC) {
                                int[] old = styleables.get(name);
                                if (old != null && old.length != current.length && !Arrays.equals(old, current)) {
                                    throw new IllegalStateException("Value of styleable." + name + " mismatched! "
                                            + "Excepted " + Arrays.toString(old) + " but was "
                                            + Arrays.toString(current));
                                } else {
                                    styleables.put(name, current);
                                }
                                current = null;
                                intStack.clear();
                            }
                        }
                    };
                }
                return null;
            }
        };

        new ClassReader(bytes).accept(visitor, SKIP_DEBUG | SKIP_FRAMES);
    }

    private Stream<Path> toStream(DirectoryInput dir) {
        try {
            return Files.walk(dir.getFile().toPath()).filter(Files::isRegularFile);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}