proguard.optimize.gson.GsonDomainClassFinder Maven / Gradle / Ivy
The newest version!
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2019 Guardsquare NV
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package proguard.optimize.gson;
import proguard.classfile.*;
import proguard.classfile.attribute.annotation.Annotation;
import proguard.classfile.attribute.annotation.visitor.*;
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import java.util.Arrays;
/**
* This class visitor determines whether a given domain class can be optimized
* by the GSON optimizations and traverses both the class and field hierarchy
* to look for further domain classes.
*
* @author Lars Vandenbergh
*/
public class GsonDomainClassFinder
extends SimplifiedVisitor
implements ClassVisitor
{
private static final boolean DEBUG = false;
private final ClassPool typeAdapterClassPool;
private final ClassPool gsonDomainClassPool;
private final WarningPrinter notePrinter;
private final LocalOrAnonymousClassChecker localOrAnonymousClassChecker =
new LocalOrAnonymousClassChecker();
private final TypeParameterClassChecker typeParameterClassChecker =
new TypeParameterClassChecker();
private final DuplicateJsonFieldNameChecker duplicateFieldNameChecker =
new DuplicateJsonFieldNameChecker();
/**
* Creates a new GsonDomainClassFinder.
*
* @param typeAdapterClassPool the class pool containing the classes for
* which a custom Gson type adapter is
* registered.
* @param gsonDomainClassPool the class pool to which the found domain
* classes are added.
* @param notePrinter used to print notes about domain classes that
* can not be handled by the Gson optimization.
*/
public GsonDomainClassFinder(ClassPool typeAdapterClassPool,
ClassPool gsonDomainClassPool,
WarningPrinter notePrinter)
{
this.typeAdapterClassPool = typeAdapterClassPool;
this.gsonDomainClassPool = gsonDomainClassPool;
this.notePrinter = notePrinter;
}
// Implementations for ClassVisitor.
@Override
public void visitProgramClass(ProgramClass programClass)
{
if (gsonDomainClassPool.getClass(programClass.getName()) == null)
{
// Local or anonymous classes are excluded by GSON.
programClass.accept(localOrAnonymousClassChecker);
if (localOrAnonymousClassChecker.isLocalOrAnonymous())
{
// No need to note here because this is not handled
// by GSON either.
return;
}
if(librarySuperClassCount(programClass) != 0)
{
note(programClass.getName(),
"Note: " + ClassUtil.externalClassName(programClass.getName() +
" can not be optimized for GSON because" +
" it is or inherits from a library class."));
return;
}
if(gsonSuperClassCount(programClass) != 0)
{
note(programClass.getName(),
"Note: " + ClassUtil.externalClassName(programClass.getName() +
" can not be optimized for GSON because" +
" it is or inherits from a GSON API class."));
return;
}
// Classes with fields that have generic type parameters are
// not supported by our optimization as it is rather complex
// to derive all possible type arguments and generate methods
// for each case.
typeParameterClassChecker.hasFieldWithTypeParameter = false;
programClass.hierarchyAccept(true,
true,
false,
false,
typeParameterClassChecker);
if (typeParameterClassChecker.hasFieldWithTypeParameter)
{
note(programClass.getName(),
"Note: " + ClassUtil.externalClassName(programClass.getName() +
" can not be optimized for GSON because" +
" it uses generic type variables."));
return;
}
// Class with duplicate field names are not supported by
// GSON either.
duplicateFieldNameChecker.hasDuplicateJsonFieldNames = false;
programClass.hierarchyAccept(true,
true,
false,
false,
duplicateFieldNameChecker);
if (duplicateFieldNameChecker.hasDuplicateJsonFieldNames)
{
note(programClass.getName(),
"Note: " + ClassUtil.externalClassName(programClass.getName() +
" can not be optimized for GSON because" +
" it contains duplicate field names in its JSON representation."));
return;
}
// Classes for which type adapters were registered are not optimized.
ClassCounter typeAdapterClassCounter = new ClassCounter();
programClass.hierarchyAccept(true,
true,
false,
false,
new ClassPresenceFilter(typeAdapterClassPool,
typeAdapterClassCounter, null));
if (typeAdapterClassCounter.getCount() > 0)
{
note(programClass.getName(),
"Note: " + ClassUtil.externalClassName(programClass.getName() +
" can not be optimized for GSON because" +
" a custom type adapter is registered for it."));
return;
}
// Classes that contain any JsonAdapter annotations are not optimized.
AnnotationFinder annotationFinder = new AnnotationFinder();
programClass.hierarchyAccept(true,
true,
false,
false,
new MultiClassVisitor(
new AllAttributeVisitor(true,
new AllAnnotationVisitor(
new AnnotationTypeFilter(GsonClassConstants.ANNOTATION_TYPE_JSON_ADAPTER,
annotationFinder)))));
if (annotationFinder.found)
{
note(programClass.getName(),
"Note: " + ClassUtil.externalClassName(programClass.getName() +
" can not be optimized for GSON because" +
" it contains a JsonAdapter annotation."));
return;
}
if ((programClass.getAccessFlags() & ClassConstants.ACC_INTERFACE) == 0)
{
if (DEBUG)
{
System.out.println("GsonDomainClassFinder: adding domain class " +
programClass.getName());
}
// Add type occurring in toJson() invocation to domain class pool.
gsonDomainClassPool.addClass(programClass);
// Recursively visit the fields of the domain class and consider
// their classes as domain classes too.
programClass.fieldsAccept(
new MemberAccessFilter(0, ClassConstants.ACC_SYNTHETIC,
new MultiMemberVisitor(
new MemberDescriptorReferencedClassVisitor(this),
new AllAttributeVisitor(
new SignatureAttributeReferencedClassVisitor(this)))));
}
// Consider super and sub classes as domain classes too.
programClass.hierarchyAccept(false,
true,
false,
true,
this);
}
}
@Override
public void visitLibraryClass(LibraryClass libraryClass)
{
// Library classes can not be optimized.
}
// Utility methods.
private int librarySuperClassCount(ProgramClass programClass)
{
ClassCounter nonObjectLibrarySuperClassCounter = new ClassCounter();
programClass.hierarchyAccept(true,
true,
false,
false,
new LibraryClassFilter(
new ClassNameFilter(Arrays.asList("!java/lang/Object", "!java/lang/Enum"),
nonObjectLibrarySuperClassCounter)));
return nonObjectLibrarySuperClassCounter.getCount();
}
private int gsonSuperClassCount(ProgramClass programClass)
{
ClassCounter gsonSuperClassCounter = new ClassCounter();
programClass.hierarchyAccept(true,
true,
false,
false,
new ProgramClassFilter(
new ClassNameFilter("com/google/gson/**",
gsonSuperClassCounter)));
return gsonSuperClassCounter.getCount();
}
private void note(String className, String note)
{
if (notePrinter != null)
{
notePrinter.print(className, note);
notePrinter.print(className, " You should consider keeping this class and its fields.");
}
}
private class AnnotationFinder
extends SimplifiedVisitor
implements AnnotationVisitor
{
private boolean found;
@Override
public void visitAnnotation(Clazz clazz, Annotation annotation)
{
found = true;
}
}
}