Merge "Add ability to promote classes to public visibility"
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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=[]"));
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user