Merge "Add ability to promote classes to public visibility"

This commit is contained in:
Diego Perez
2017-02-02 14:03:06 +00:00
committed by Android (Google) Code Review
7 changed files with 283 additions and 2 deletions

View File

@@ -36,6 +36,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
/**
* Class that generates a new JAR from a list of classes, some of which are to be kept as-is
@@ -78,6 +79,8 @@ public class AsmGenerator {
private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
/** A map { FQCN => set { field names } } which should be promoted to public visibility */
private final Map<String, Set<String>> mPromotedFields;
/** A list of classes to be promoted to public visibility */
private final Set<String> mPromotedClasses;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -179,6 +182,9 @@ public class AsmGenerator {
addToMap(createInfo.getPromotedFields(), mPromotedFields);
mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
mPromotedClasses =
Arrays.stream(createInfo.getPromotedClasses()).collect(Collectors.toSet());
}
/**
@@ -400,7 +406,11 @@ public class AsmGenerator {
if (promoteFields != null && !promoteFields.isEmpty()) {
cv = new PromoteFieldClassAdapter(cv, promoteFields);
}
if (!mPromotedClasses.isEmpty()) {
cv = new PromoteClassClassAdapter(cv, mPromotedClasses);
}
cr.accept(cv, 0);
return cw.toByteArray();
}

View File

@@ -112,6 +112,11 @@ public final class CreateInfo implements ICreateInfo {
return PROMOTED_FIELDS;
}
@Override
public String[] getPromotedClasses() {
return PROMOTED_CLASSES;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return INJECTED_METHODS;
@@ -343,6 +348,13 @@ public final class CreateInfo implements ICreateInfo {
"android.graphics.FontFamily#mBuilderPtr"
};
/**
* List of classes to be promoted to public visibility. Prefer using PROMOTED_FIELDS to this
* if possible.
*/
private final static String[] PROMOTED_CLASSES = new String[] {
};
/**
* List of classes for which the methods returning them should be deleted.
* The array contains a list of null terminated section starting with the name of the class

View File

@@ -77,6 +77,11 @@ public interface ICreateInfo {
*/
String[] getPromotedFields();
/**
* Returns a list of classes to be promoted to public visibility.
*/
String[] getPromotedClasses();
/**
* Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
* called to inject methods into a class.

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.android.tools.layoutlib.create;
import org.objectweb.asm.ClassVisitor;
import java.util.Set;
import java.util.stream.Collectors;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
/**
* Promotes given classes to public visibility.
*/
public class PromoteClassClassAdapter extends ClassVisitor {
private final Set<String> mClassNames;
private static final int CLEAR_PRIVATE_MASK = ~(ACC_PRIVATE | ACC_PROTECTED);
public PromoteClassClassAdapter(ClassVisitor cv, Set<String> classNames) {
super(Main.ASM_VERSION, cv);
mClassNames =
classNames.stream().map(name -> name.replace(".", "/")).collect(Collectors.toSet());
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
if (mClassNames.contains(name)) {
if ((access & ACC_PUBLIC) == 0) {
access = (access & CLEAR_PRIVATE_MASK) | ACC_PUBLIC;
}
}
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (mClassNames.contains(name)) {
if ((access & ACC_PUBLIC) == 0) {
access = (access & CLEAR_PRIVATE_MASK) | ACC_PUBLIC;
}
}
super.visitInnerClass(name, outerName, innerName, access);
}
}

View File

@@ -31,7 +31,7 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
public class PromoteFieldClassAdapter extends ClassVisitor {
private final Set<String> mFieldNames;
private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED);
private static final int CLEAR_PRIVATE_MASK = ~(ACC_PRIVATE | ACC_PROTECTED);
public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) {
super(Main.ASM_VERSION, cv);
@@ -43,7 +43,7 @@ public class PromoteFieldClassAdapter extends ClassVisitor {
Object value) {
if (mFieldNames.contains(name)) {
if ((access & ACC_PUBLIC) == 0) {
access = (access & ACC_NOT_PUBLIC) | ACC_PUBLIC;
access = (access & CLEAR_PRIVATE_MASK) | ACC_PUBLIC;
}
}
return super.visitField(access, name, desc, signature, value);

View File

@@ -136,6 +136,11 @@ public class AsmGeneratorTest {
return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedClasses() {
return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return Collections.emptyMap();
@@ -210,6 +215,11 @@ public class AsmGeneratorTest {
return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedClasses() {
return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return Collections.emptyMap();
@@ -292,6 +302,11 @@ public class AsmGeneratorTest {
return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedClasses() {
return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return Collections.emptyMap();
@@ -369,6 +384,11 @@ public class AsmGeneratorTest {
return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedClasses() {
return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return Collections.singletonMap("mock_android.util.EmptyArray",

View File

@@ -0,0 +1,170 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.android.tools.layoutlib.create;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.StringJoiner;
import static org.junit.Assert.assertTrue;
/**
* {@link ClassVisitor} that logs all the calls to the different visit methods so they can be later
* inspected.
*/
class LoggingClassVisitor extends ClassVisitor {
List<String> mLog = new LinkedList<String>();
public LoggingClassVisitor() {
super(Main.ASM_VERSION);
}
public LoggingClassVisitor(ClassVisitor cv) {
super(Main.ASM_VERSION, cv);
}
private static String formatAccess(int access) {
StringJoiner modifiers = new StringJoiner(",");
if ((access & Opcodes.ACC_PUBLIC) != 0) {
modifiers.add("public");
}
if ((access & Opcodes.ACC_PRIVATE) != 0) {
modifiers.add("private");
}
if ((access & Opcodes.ACC_PROTECTED) != 0) {
modifiers.add("protected");
}
if ((access & Opcodes.ACC_STATIC) != 0) {
modifiers.add("static");
}
if ((access & Opcodes.ACC_FINAL) != 0) {
modifiers.add("static");
}
return "[" + modifiers.toString() + "]";
}
private void log(String method, String format, Object...args) {
mLog.add(
String.format("[%s] - %s", method, String.format(format, (Object[]) args))
);
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
log(
"visitOuterClass",
"owner=%s, name=%s, desc=%s",
owner, name, desc
);
super.visitOuterClass(owner, name, desc);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
log(
"visitInnerClass",
"name=%s, outerName=%s, innerName=%s, access=%s",
name, outerName, innerName, formatAccess(access)
);
super.visitInnerClass(name, outerName, innerName, access);
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
log(
"visit",
"version=%d, access=%s, name=%s, signature=%s, superName=%s, interfaces=%s",
version, formatAccess(access), name, signature, superName, Arrays.toString(interfaces)
);
super.visit(version, access, name, signature, superName, interfaces);
}
}
class PackageProtectedClass {}
public class PromoteClassClassAdapterTest {
private static class PrivateClass {}
private static class ClassWithPrivateInnerClass {
private class InnerPrivateClass {}
}
@Test
public void testInnerClassPromotion() throws IOException {
ClassReader reader = new ClassReader(PrivateClass.class.getName());
LoggingClassVisitor log = new LoggingClassVisitor();
PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, new HashSet<String>() {
{
add("com.android.tools.layoutlib.create.PromoteClassClassAdapterTest$PrivateClass");
add("com.android.tools.layoutlib.create" +
".PromoteClassClassAdapterTest$ClassWithPrivateInnerClass$InnerPrivateClass");
}
});
reader.accept(adapter, 0);
assertTrue(log.mLog.contains(
"[visitInnerClass] - " +
"name=com/android/tools/layoutlib/create" +
"/PromoteClassClassAdapterTest$PrivateClass, " +
"outerName=com/android/tools/layoutlib/create" +
"/PromoteClassClassAdapterTest, innerName=PrivateClass, access=[public,static]"));
// Test inner of inner class
log.mLog.clear();
reader = new ClassReader(ClassWithPrivateInnerClass.class.getName());
reader.accept(adapter, 0);
assertTrue(log.mLog.contains("[visitInnerClass] - " +
"name=com/android/tools/layoutlib/create" +
"/PromoteClassClassAdapterTest$ClassWithPrivateInnerClass$InnerPrivateClass, " +
"outerName=com/android/tools/layoutlib/create" +
"/PromoteClassClassAdapterTest$ClassWithPrivateInnerClass, " +
"innerName=InnerPrivateClass, access=[public]"));
}
@Test
public void testProtectedClassPromotion() throws IOException {
ClassReader reader = new ClassReader(PackageProtectedClass.class.getName());
LoggingClassVisitor log = new LoggingClassVisitor();
PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, new HashSet<String>() {
{
add("com.android.tools.layoutlib.create.PackageProtectedClass");
}
});
reader.accept(adapter, 0);
assertTrue(log.mLog.contains("[visit] - version=52, access=[public], " +
"name=com/android/tools/layoutlib/create/PackageProtectedClass, signature=null, " +
"superName=java/lang/Object, interfaces=[]"));
}
}