package com.francetelecom.rd.stubs.engine;
/*
* #%L
* Matos
* $Id:$
* $HeadURL:$
* %%
* Copyright (C) 2008 - 2015 Orange SA
* %%
* 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.
* #L%
*/
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* This class pretty prints the empty stub of a loaded class. It should perform
* nothing and real stubs must be implemented by overloading this class.
* @author Pierre Cregut
*
*/
/**
* @author Pierre Cregut
*
*/
public class ClassDumper {
final static private int PUBLIC = 1;
@SuppressWarnings("unused")
final static private int PACKAGE = 2;
final static private int PRIVATE = 3;
final static Class <?> OBJECT_CLASS = java.lang.Object.class;
final static Class <?> ENUM_CLASS = java.lang.Enum.class;
final private String outDir;
private Pattern shortenerPattern;
private static Pattern anonymousClassPattern = Pattern.compile("([./]|^)([0-9])");
int count = 0;
int indentDepth = 0;
final private boolean privateAccess;
final private ReflexUtil rf;
final Type [] typeAW = new Type[0];
final Method [] methodAW = new Method[0];
private final AlternativeAnnotations optAnnotations;
private VersionDatabase database = null;
private HashSet<Class<?>> neededClasses = new HashSet<Class<?>>();
/**
* Constructor.
* @param outDir the name of the directory where the new source tree is created
* @param prefix the relocation prefix added for the classloader.
* @param privateAccess: if true private and package class, methods and fields are dumped
*/
public ClassDumper(String outDir, ReflexUtil rf, boolean privateAccess, AlternativeAnnotations optAlternative) {
this.outDir = outDir;
this.privateAccess=privateAccess;
this.optAnnotations=optAlternative;
this.rf = rf;
}
/**
* Constructor in the most usual case: does not show private elements.
* @param outDir the name of the directory where the new source tree is created
* @param prefix the relocation prefix added for the classloader.
*/
public ClassDumper(String outDir, ReflexUtil rf, AlternativeAnnotations optAlternative) {
this(outDir,rf,false, optAlternative);
}
/**
* Register a version database to dump the set of versions.
* @param database
*/
public void setVersionDatabase(VersionDatabase database) {
this.database = database;
}
/**
* Change all the intermediate names that begin with digits.
* @param s
* @return
*/
private String renameAnonymousClass (String s) {
return anonymousClassPattern.matcher(s).replaceAll("$1Anonymous$2");
}
/**
* Remove the relocation prefix from the name.
* @param name
* @return
*/
protected String shorten(String name) {
name = rf.restoreString(name);
name = shortenerPattern.matcher(name).replaceAll("$2").replace('$', '.');
name = renameAnonymousClass(name);
return name;
}
/**
* Print out the right number of spaces to get a correct indentation.
* @param out
*/
protected void indent(PrintStream out) {
for (int i=0; i < indentDepth; i++) out.print(" ");
}
/**
* Increase the indentation level
*/
protected void beginIndent() { indentDepth ++; }
/**
* Decrease the indentation level.
*/
protected void endIndent() { indentDepth --; }
/**
* Keep visible types from a list.
* @param types an array
* @return a filtered array.
*/
private Type [] filterListTypes (Type [] types) {
List<Type> result = new ArrayList<Type>();
for (Type type: types) {
if(!isInvisible(type)) result.add(type);
}
return result.toArray(new Type [0]);
}
/**
* Output a type list with a comma as separator (regular case)
* @param out
* @param list
* @param authParams
*/
protected void dumpListType(PrintStream out, Type [] list, boolean authParams) {
boolean first = true;
for(Type param : list) {
if (first) first = false;
else out.print(", ");
type(out, param, authParams, null);
}
}
/**
* Output a type list with & as separator (for wildcard bound)
* @param out
* @param list
* @param authParams
*/
private void dumpListTypeAmp(PrintStream out, Type [] list, boolean authParams) {
boolean first = true;
for(Type param : list) {
if (first) first = false;
else out.print(" & ");
type(out, param,authParams, null);
}
}
/**
* Print outs a type in regular string format.
* @param type
* @return
*/
protected void type(PrintStream out,Type type, boolean authParams, Map<TypeVariable<?>, Type> typEnv) {
if (type == null) {
out.print("*** bug null ***");
return;
}
if (type instanceof GenericArrayType) {
type (out, ((GenericArrayType) type).getGenericComponentType(), authParams, typEnv);
out.print(" []");
} else if (type instanceof ParameterizedType) {
if (isInvisible(type)) {
type(out,((ParameterizedType) type).getRawType(), authParams, typEnv);
} else
typeParameterized(out,(ParameterizedType) type, typEnv, authParams);
} else if (type instanceof TypeVariable<?>) {
if (typEnv != null && typEnv.containsKey((TypeVariable <?>) type)) {
Type substituted = typEnv.get((TypeVariable<?>) type);
type(out,substituted,authParams, null);
} else {
TypeVariable <?> tvar = (TypeVariable <?>) type;
out.print(tvar.getName());
}
} else if (type instanceof WildcardType) {
typeWildcard(out,(WildcardType) type, authParams, typEnv);
} else {
Class <?> classType = (Class <?>) type;
if (classType.isArray()) {
type(out,classType.getComponentType(),authParams, typEnv);
out.print(" []");
} else if (!neededClasses.contains(classType) && isInvisible(classType)) {
type(out,classType.getSuperclass(), authParams, typEnv);
} else out.print(shorten(classType.getName()));
}
}
/**
* Output a wildcard type
* @param out output stream
* @param wt the type to print
* @param authParams do we keep type parameters
* @param typEnv a typing environment.
*/
private void typeWildcard(PrintStream out, WildcardType wt,boolean authParams,
Map<TypeVariable<?>, Type> typEnv) {
out.print("?");
Type [] bounds = wt.getUpperBounds();
if (bounds.length > 0 && !bounds[0].equals(OBJECT_CLASS) ) {
out.print(" extends ");
dumpListType(out,bounds, authParams);
}
bounds = wt.getLowerBounds();
if (bounds.length > 0 && !bounds[0].equals(OBJECT_CLASS) ) {
out.print(" super ");
dumpListType(out,bounds, authParams);
}
}
/**
* Dumps a parameterized type.
* @param out
* @param pt the type to print.
* @param typEnv typing environment (for interpreting type variables)
* @param authParams Do we keep type parameters.
*/
private void typeParameterized(PrintStream out, ParameterizedType pt,
Map<TypeVariable<?>, Type> typEnv, boolean authParams) {
if (pt.getOwnerType() != null) {
type(out,pt.getOwnerType(), authParams, typEnv);
out.print(".");
Class <?> rawType = (Class <?>) pt.getRawType();
out.print(renameAnonymousClass(rawType.getSimpleName()));
} else {
type(out, pt.getRawType(), authParams, typEnv);
}
Type [] args = pt.getActualTypeArguments();
if (authParams && args.length > 0) {
out.print("<");
dumpListType(out,args, authParams);
out.print(">");
}
}
/**
* Print outs a type in regular string format. The type environment is null and
* corresponds to the case where the type is solved.
* @param type
* @return
*/
protected void type(PrintStream out, Type type) {
type(out, type,true, null);
}
/**
* Prints out a list of type parameters (quantified type variables).
* @param out
* @param tvars
*/
protected void typeParameters(PrintStream out, TypeVariable<?> [] tvars) {
if (tvars != null && tvars.length > 0) {
out.print("<");
boolean first = true;
for(TypeVariable <?> tvar : tvars) {
if (first) first = false;
else out.print(", ");
out.print(tvar.getName());
Type [] bounds = tvar.getBounds();
if (bounds.length > 0 && bounds[0] != java.lang.Object.class) {
out.print(" extends ");
dumpListTypeAmp(out,bounds, false);
}
}
out.print(">");
}
}
protected final boolean debug = false;
private File outFileRef = null;
private HashSet<String> seenMethods;
/**
* Create the output stream and the necessary folders.
* @param classname
* @return
* @throws IOException
*/
protected PrintStream buildOutStream(String classname) throws IOException {
PrintStream out;
if (debug) {
out = System.out;
outFileRef = null;
}
else {
outFileRef = new File(outDir, classname.replace('.', '/') + ".java");
File parentDir = outFileRef.getParentFile();
if (parentDir != null && !parentDir.isDirectory() && !parentDir.mkdirs()) {
System.err.println("Cannot create output folder for" + classname);
return null;
}
out = new PrintStream(new FileOutputStream(outFileRef));
}
return out;
}
/**
* Dump the class to a file
* @param out
* @param c
*/
public void dumpClass(Class <?> c) throws IOException {
boolean keep = false;
String classname = c.getName();
if (classname.contains("$")) return;
classname = rf.restoreString(classname);
PrintStream out = buildOutStream(classname);
File outFile = outFileRef;
if (out == null) return;
try {
setPackage(out, classname);
generateImports(out,c);
keep = dumpClass(out,c);
} catch (Throwable e) {
e.printStackTrace();
out.println("}");
throw new IOException(e.getMessage());
}
if (!debug) {
out.close();
if (!keep && outFile != null) {
if (!outFile.delete()) System.err.println("Cannot delete out file" + outFile); }
}
}
protected void generateImports(PrintStream out, Class<?> c) {
HashSet <String> importedAnnotations = new HashSet<String>();
collectImportedAnnotations(importedAnnotations, c);
for(String annot : importedAnnotations) {
out.println("import " + rf.restoreString(annot) + ";");
}
out.println();
}
private void collectImportedAnnotations(HashSet<String> importedAnnotations, Class<?> clazz) {
Annotation [] clazzAnnots = clazz.getAnnotations();
addAnnotations(clazzAnnots, importedAnnotations);
for(Field field: clazz.getDeclaredFields())
addAnnotations(field.getAnnotations(), importedAnnotations);
for (Class <?> subclass : clazz.getDeclaredClasses()) {
collectImportedAnnotations(importedAnnotations, subclass);
}
for (Constructor <?> co : clazz.getDeclaredConstructors())
addAnnotations(co.getAnnotations(), importedAnnotations);
for (Method me : clazz.getDeclaredMethods())
addAnnotations(me.getAnnotations(), importedAnnotations);
}
private void addAnnotations(Annotation[] annotations, HashSet<String> importedAnnotations) {
for (Annotation annot : annotations) {
if (rf.isStubAnnotation(annot)) {
importedAnnotations.add(annot.annotationType().getName());
}
}
}
/**
* Output the package name and remember it.
* @param out
* @param c
*/
protected void setPackage(PrintStream out, String className) {
int i = className.lastIndexOf('.');
String pkgName = (i == -1) ? "" : className.substring(0,i);
out.println("package "+ pkgName + ";\n");
String regexp = "^("+pkgName+")[.]([^.]*)$";
shortenerPattern = Pattern.compile(regexp);
if (database != null) out.println("import " + rf.annotationPackage() + ".Missing;");
}
/**
* Dump a class on an existing stream. Can be used for inner class.
* @param out
* @param c
* @return if false we discard this class (used if we opened a file specifically for it).
*/
public boolean dumpClass(PrintStream out, Class <?> c) {
if (!neededClasses.contains(c) && (c.isAnonymousClass() || isInvisible(c))) return false;
Map<TypeVariable<?>, Type> typeEnv = dumpClassHeader(out,c);
out.println("{");
beginIndent();
dumpClassBody(out,c,typeEnv);
endIndent();
indent(out);
out.println("}");
return true;
}
/**
* Dumps the declaration of a class (first lines up to the brace)
* @param out the output stream
* @param clazz the class
* @return an environment telling how to interpret type parameter encountered inside
*/
protected Map<TypeVariable<?>, Type> dumpClassHeader(PrintStream out, Class <?> clazz) {
int mod = clazz.getModifiers();
Map<TypeVariable<?>, Type> typeEnv = null;
Annotation [] annotations =
(optAnnotations == null ? clazz.getDeclaredAnnotations() : optAnnotations.getDeclaredAnnotations(clazz));
dumpAnnotationList(out,annotations);
if (database != null) {
dumpMissingAnnotation(out, database.missingVersionsOf(clazz));
}
indent(out);
boolean isItf = Modifier.isInterface(mod);
boolean isEnum = clazz.isEnum();
boolean isAnnot = clazz.isAnnotation();
if (isEnum) {
mod &= (~Modifier.FINAL & ~Modifier.ABSTRACT);
}
if (isItf) {
mod &= ~Modifier.ABSTRACT;
}
out.print(modifier(mod));
if(isAnnot) out.print("@");
out.print(isItf ? "interface " : (isEnum ? "enum " : "class "));
String classname = shorten(clazz.getName());
out.print(classname.substring(classname.lastIndexOf('.') + 1));
typeParameters(out, clazz.getTypeParameters());
out.println();
indent(out);
// Hides the super that are not public
Class <?> baseSuper = clazz;
Class <?> sup = baseSuper.getSuperclass();
if (sup != null && isInvisible(sup))
System.err.println(rf.restoreString("TO KEEP FOR " + clazz + " : " + sup));
Set <Type> itfsRaw = new HashSet <Type>();
itfsRaw.addAll(Arrays.asList(clazz.getGenericInterfaces()));
/*
while(baseSuper.getSuperclass() != null && isInvisible(baseSuper.getSuperclass())) {
baseSuper = baseSuper.getSuperclass();
itfsRaw.addAll(Arrays.asList(baseSuper.getGenericInterfaces()));
} */
Type superClassType = baseSuper.getGenericSuperclass();
if (superClassType != null && superClassType != OBJECT_CLASS && !isEnum) {
out.print(" extends ");
type(out, superClassType);
typeEnv = buildEnvType(superClassType);
indent(out);
}
Type [] itfs = filterListTypes(itfsRaw.toArray(typeAW));
if (itfs != null && itfs.length > 0 && !isAnnot) {
out.print(isItf ? " extends " : " implements ");
dumpListType(out,itfs, true);
out.println();
indent(out);
}
return typeEnv;
}
/**
* The contents of the class (constructors, other class, fields and methods)
* @param out
* @param c
* @param typeEnv
*/
protected void dumpClassBody(PrintStream out, Class<?> c,
Map<TypeVariable<?>, Type> typeEnv) {
boolean isEnum = c.isEnum();
boolean isItf = Modifier.isInterface(c.getModifiers());
String classname = shorten(c.getName());
Field [] fields = c.getDeclaredFields();
if (isEnum) dumpEnums(out,fields,c);
Class <?> [] classes = c.getDeclaredClasses();
dumpClasses(out,classes);
dumpFields(out,fields);
Constructor <?> [] constructors = c.getDeclaredConstructors();
dumpConstructors(out, constructors, classname, typeEnv,
c.getEnclosingClass());
resetSeen();
Method [] methods = c.getDeclaredMethods();
dumpMethods(out,methods, c, isEnum, isItf, typeEnv);
dumpMethods(out, filterMethodsHidden(c, c.getMethods()), c, isEnum, isItf, typeEnv);
}
protected Map<TypeVariable<?>, Type> buildEnvType(Type superClassType) {
if (superClassType instanceof ParameterizedType) {
Map<TypeVariable<?>, Type> typeEnv = new HashMap<TypeVariable<?>, Type>();
buildEnvType(typeEnv, (ParameterizedType) superClassType);
return typeEnv;
} else return null;
}
/**
* Output all the classes contained in a given class
* @param out output stream
* @param classes the array of subclasses
*/
private void dumpClasses(PrintStream out, Class<?>[] classes) {
if (classes.length > 0) {
indent(out);
out.println("// Classes\n");
for(Class<?> clazz: classes) dumpClass(out, clazz);
}
}
/**
* Output all the methods of a class. Get rids of special enum methods.
* Output a body only if existing.
* @param out output stream
* @param methods the array of methods
* @param c current class
* @param isEnum is it an enum class ?
* @param isItf is it an interface
* @param typeEnv type environment.
*/
private void dumpMethods(PrintStream out, Method[] methods, Class <?> c, boolean isEnum, boolean isItf, Map<TypeVariable<?>, Type> typeEnv) {
if (methods.length > 0) {
indent(out);
out.println("// Methods\n");
Method meth1 = null, meth2 = null;
if (isEnum) {
try {
meth1 = c.getMethod("values");
meth2 = c.getMethod("valueOf", java.lang.String.class);
} catch (NoSuchMethodException e) {
System.err.println("Enum: cannot isolate specific methods (not accessible).");
}
}
for(Method meth: methods) {
if (isEnum && (meth.equals(meth1) || meth.equals(meth2))) continue;
if (useInvisibleType(meth.getParameterTypes(), meth.getReturnType())) continue;
if (meth.isSynthetic() && meth.isBridge()) continue; // WARNING MAY BE meth.isBridge() || meth.isSynthetic
if (seen(meth)) continue;
dumpMethod(out,meth,isItf, false, typeEnv);
}
}
}
private boolean seen(Method meth) {
String signature = meth.getName() + Arrays.toString(meth.getParameterTypes());
if (seenMethods.contains(signature)) return true;
seenMethods.add(signature);
return false;
}
private void resetSeen() {
seenMethods = new HashSet<String>();
}
private boolean useInvisibleType(Class<?>[] parameterTypes, Class<?> returnType) {
for(Class<?> typ : parameterTypes) { if (isInvisible(typ)) return true; }
return returnType != null && isInvisible(returnType);
}
/**
* Test if the element is visible (public or protected).
* @param mod
* @return
*/
private boolean isAccessible(int mod) {
return (privateAccess || Modifier.isPublic(mod) || Modifier.isProtected(mod));
}
/**
* Check if this type contains components that should not be visible.
* @param typ
* @return
*/
private boolean isInvisible(Class<?> typ) {
if (typ.isArray()) return isInvisible(typ.getComponentType());
int mod = typ.getModifiers();
Class <?> enc;
return !isAccessible(mod) || ((enc = typ.getEnclosingClass()) != null && isInvisible(enc));
// return Modifier.isPrivate(mod);
}
/**
* Check if this type contains components that should not be visible.
* @param typ
* @return
*/
private boolean isInvisible(Type typ) {
if (typ instanceof GenericArrayType)
return isInvisible(((GenericArrayType) typ).getGenericComponentType());
if (typ instanceof ParameterizedType)
return isInvisible (((ParameterizedType) typ).getRawType()) || isInvisible(((ParameterizedType) typ).getOwnerType());
if (typ instanceof Class<?>)
return isInvisible((Class <?>) typ);
return false;
}
/**
* List of method that have an invisible type.
* @param c
* @param methods
* @return
*/
private Method [] filterMethodsHidden(Class<?> c, Method [] methods) {
List <Method> result = new ArrayList<Method>();
for (Method method: methods) {
Class <?> declaring = method.getDeclaringClass();
if (declaring != c && !neededClasses.contains(declaring) && isInvisible(declaring)) {
result.add(method);
}
}
return result.toArray(methodAW);
}
/**
* Output all the constructors of the class
* @param out output stream
* @param constructors list of constructors;
* @param classname classname
* @param typeEnv type environment of the current class
* @param enclosing the class containing the current class if any (nested class compilation)
*/
private void dumpConstructors(PrintStream out, Constructor<?>[] constructors, String classname, Map<TypeVariable<?>, Type> typeEnv, Class<?> enclosing) {
if (constructors.length > 0) {
indent(out);
out.println("// Constructors\n");
for(Constructor<?> co: constructors) {
if (useInvisibleType(co.getParameterTypes(), null)) continue;
dumpConstructor(out,classname, co, typeEnv, enclosing);
}
}
}
/**
* Output all the fields of a class
* @param out output stream
* @param fields array of fields
*/
private void dumpFields(PrintStream out, Field[] fields) {
if (fields.length > 0) {
indent(out);
out.println("// Fields\n");
for(Field field: fields) {
if (isInvisible(field.getType())) continue;
dumpField(out,field, true);
}
}
}
/**
* Output all the enum values of a class.
* @param out output stream
* @param fields array of all fields (select the right one)
* @param enumTyp the current class (an enum).
*/
private void dumpEnums(PrintStream out, Field[] fields, Class <?> enumTyp) {
Type [] argTyps = null;
try {
enumTyp.getConstructor();
} catch (Exception e1) {
Constructor<?> [] constructors = enumTyp.getDeclaredConstructors();
if (constructors.length > 0) argTyps = constructors[0].getGenericParameterTypes();
}
boolean first = true;
indent(out);
out.println("// Enum Constants\n");
indent(out);
for(Field field: fields) {
if(!field.isEnumConstant()) continue;
if (first) first = false;
else out.print(", ");
try {
out.print(field.get(null));
} catch (Exception e) {
out.print(field.getName());
}
if (argTyps != null) {
out.print("(");
boolean first2 = true;
int i = 0;
for(Type typ2: argTyps) {
if(isAddedParam(typ2, i++, null)) continue;
if (first2) first2 = false;
else out.print(", ");
dumpDefaultValue(out, typ2, null);
}
out.println(")");
}
}
out.println(";");
}
/**
* From a parameterized types, extracts the bindings between type parameters
* and actual type value and store them in the environment type.
* @param typeEnv the environment type completed
* @param type the type analyzed.
*/
protected void buildEnvType(Map<TypeVariable<?>, Type> typeEnv,
ParameterizedType type) {
Type [] argTyps = type.getActualTypeArguments();
TypeVariable<?> [] tvars = ((Class <?>) type.getRawType()).getTypeParameters();
int l = argTyps.length;
if (tvars.length != l) throw new RuntimeException("buildEnvTYpe");
for(int i = 0; i < l; i++) {
typeEnv.put(tvars[i], argTyps[i]);
}
}
/**
* Dumps an empty constructor on the stream. Takes care of super calls.
* @param out printstream
* @param classname name of the class / constructor
* @param co reflexive representation of the constructor
* @param typeEnv type environment of the containing class
* @param enclosing if non null the class containing the current class.
*/
private void dumpConstructor(PrintStream out, String classname, Constructor<?> co, Map<TypeVariable<?>, Type> typeEnv, Class<?> enclosing) {
if (co.isSynthetic()) return;
int mod = co.getModifiers();
Type [] params = co.getGenericParameterTypes();
Annotation [] annotations =
optAnnotations == null ? co.getDeclaredAnnotations() : optAnnotations.getDeclaredAnnotations(co);
dumpAnnotationList(out,annotations);
if (database != null) {
dumpMissingAnnotation(out, database.missingVersionsOf(co));
}
indent(out);
out.print(modifier(mod));
this.typeParameters(out, co.getTypeParameters());
out.print(classname.substring(classname.lastIndexOf('.') + 1));
out.print("(");
Annotation [][] paramAnnotations =
optAnnotations == null ? co.getParameterAnnotations() : optAnnotations.getParameterAnnotations(co);
int count = 0;
for(int i=0; i < params.length; i++) {
Type param = params[i];
if(!Modifier.isStatic(co.getDeclaringClass().getModifiers()) && isAddedParam(param, count, enclosing)) continue;
if(count > 0) out.print(", ");
for(Annotation annot : paramAnnotations[i]) {
dumpAnnotation(out, annot);
out.print(" ");
}
type(out, param);
out.print(" arg");
out.print(++count);
}
out.print(")");
Type [] excs = filterListTypes(co.getGenericExceptionTypes());
if (excs.length > 0) {
out.print(" throws ");
dumpListType(out, excs, true);
}
out.println("{");
beginIndent();
dumpConstructorBody(out,co, typeEnv);
endIndent();
indent(out);
out.println("}");
}
private void dumpMissingAnnotation(PrintStream out, List<String> missing) {
if (missing.size() > 0) {
indent(out);
out.print("@Missing({");
boolean first = true;
for(String version : missing) {
if (first) first = false;
else out.print(", ");
out.print('"');
out.print(version);
out.print('"');
}
out.println("})");
}
}
/**
* This dumps out the body of a constructor. It contains the call to super
* at least.
* @param out output stream
* @param constructor reflexive representation of the constructor
* @param typeEnv type environment of the current class
*/
protected void dumpConstructorBody(PrintStream out,Constructor<?> constructor, Map<TypeVariable<?>, Type> typeEnv) {
Class<?> superclass = constructor.getDeclaringClass();
do {
superclass = superclass.getSuperclass();
} while (!neededClasses.contains(superclass) && isInvisible(superclass));
Class<?> [] exceptions = constructor.getExceptionTypes();
if (superclass == OBJECT_CLASS) return;
if (superclass == ENUM_CLASS) return;
Constructor<?> [] constructors = superclass.getDeclaredConstructors();
Constructor <?> co = selectCompatibleWithExceptions(constructors, exceptions);
if (co == null) {
System.err.println("No constructor in super " + superclass + " compatible with " + Arrays.toString(exceptions));
return;
}
// Take one at random (the first).
Class <?> embed = superclass.getEnclosingClass();
Type [] argTyps = co.getGenericParameterTypes();
indent(out);
out.print("super(");
boolean first = true;
int i = 0;
for(Type typ: argTyps) {
if(!Modifier.isStatic(superclass.getModifiers()) && isAddedParam(typ, i++, embed)) continue;
if (first) first = false;
else out.print(", ");
dumpDefaultValue(out, typ, typeEnv);
}
out.println(");");
}
/**
* This method decides if a parameter of a constructor is in fact added for the
* compilation of nested classes. This is kind of hacky but the compilation
* of nested classes whose effect we must undo is a hack.
* @param typ the type of the parameter
* @param i its position
* @param enclosing the enclosing class
* @return
*/
private boolean isAddedParam(Type typ, int i, Class<?> enclosing) {
if (enclosing == null) return false;
if (typ instanceof ParameterizedType)
typ = ((ParameterizedType) typ).getRawType();
if (typ instanceof Class<?>) {
Class <?> c = (Class <?>) typ;
return (enclosing.equals(c) && i == 0) || c.isAnonymousClass();
} else return false;
}
/**
* Finds a contructor with a given visibility scope such that it is compatible with
* a set of exceptions it is allowed to raise (the one raised in the constructor that
* contains the call to super).
* @param constructors the set of super constructors
* @param authorizedExceptions the exceptions that can be raised by the call to super
* @param mode mode telling what should be considered in the constructor list.
* It is necessary for nested class that can access private of their enclosing
* class.
* @return
*/
private Constructor<?> selectCompatibleWithExceptions(Constructor<?> [] constructors, Class <?>[] authorizedExceptions) {
for(int mode = PUBLIC; mode <= PRIVATE; mode ++) {
level1:
for(Constructor <?> co : constructors) {
int mod = co.getModifiers();
if (co.isSynthetic() || (mode != PRIVATE && Modifier.isPrivate(mod)) || (mode == PUBLIC && (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)))) continue;
// if (!isAccessible(co.getModifiers())) continue;
Class<?> [] exceptions = co.getExceptionTypes();
level2:
for(Class <?> exception : exceptions) {
for (Class <?> catcher : authorizedExceptions) {
if (catcher.isAssignableFrom(exception)) {
continue level2;
}
}
continue level1;
}
return co;
}
}
return null;
}
/**
* Dumps a method definition. Works for abstract or actual methods.
* @param out output stream
* @param meth method to dump
* @param isItf tell if it is in an interface.
*/
protected void dumpMethod(PrintStream out, Method meth, boolean isItf, boolean isNotAbstract, Map<TypeVariable<?>, Type> typeEnv) {
int mod = meth.getModifiers();
if (!isAccessible(mod)) return;
Annotation [] annotations =
optAnnotations == null ? meth.getDeclaredAnnotations() : optAnnotations.getDeclaredAnnotations(meth);
Annotation [] [] paramAnnotations =
optAnnotations == null ? meth.getParameterAnnotations() : optAnnotations.getParameterAnnotations(meth);
dumpAnnotationList(out,annotations);
if (database != null) {
dumpMissingAnnotation(out, database.missingVersionsOf(meth));
}
indent(out);
out.print(modifier((isNotAbstract || isItf) ? (mod & ~Modifier.ABSTRACT) : mod));
typeParameters(out, meth.getTypeParameters());
type(out, meth.getGenericReturnType(), true, typeEnv);
out.print(" ");
out.print(meth.getName());
out.print("(");
Type [] params = meth.getGenericParameterTypes();
int count = 0;
for(Type param : params) {
if(count > 0) out.print(", ");
for(Annotation annot : paramAnnotations[count]) {
dumpAnnotation(out, annot);
out.print(" ");
}
type(out, param,true, typeEnv);
out.print(" arg");
out.print(++count);
}
out.print(")");
Type [] excs = meth.getGenericExceptionTypes();
if (excs.length > 0) {
out.print(" throws ");
dumpListType(out, excs, true);
}
if ((Modifier.isAbstract(mod) && !isNotAbstract) || Modifier.isNative(mod)) {
out.println(";");
} else {
out.println("{");
beginIndent();
dumpMethodBody(out, meth, typeEnv);
endIndent();
indent(out);
out.println("}");
}
}
/**
* Dump an annotation. works at any level.
* @param out output stream
* @param annot annotation to dump
*/
protected void dumpAnnotation(PrintStream out, Annotation annot) {
out.print("@");
Class<? extends Annotation> c = annot.annotationType();
String annotName =
(rf.isStubAnnotation(annot)) ? c.getSimpleName() :shorten(c.getName());
out.print(annotName);
boolean first = true;
Method [] methods = c.getDeclaredMethods();
if (methods.length == 0) return;
if (methods.length == 1 && methods[0].getName().equals("value")) {
String value;
try {
value = simpleValue(methods[0].invoke(annot),true);
} catch (Throwable e) {
value = "null";
}
if (annotName.equals("ClassDone") && value.equals("0")) return;
out.print("(" + value + ")");
} else {
out.print("(");
for(Method method : c.getDeclaredMethods()) {
String fieldName = method.getName();
String value;
try {
value = simpleValue(method.invoke(annot),true);
} catch (Throwable e) {
value = "null";
}
if ((annotName.equals("ArgsRule") || annotName.equals("FieldRule")) && fieldName.equals("report") && value.equals("\"-\"")) return;
if (first) first = false;
else out.print(", ");
out.print(fieldName);
out.print(" = ");
out.print(value);
}
out.print(")");
}
}
protected void dumpAnnotationList(PrintStream out, Annotation[] annotations) {
for(Annotation annot : annotations) {
indent(out);
dumpAnnotation(out, annot);
out.println();
}
}
/**
* Dumps a field on the out stream be it static or instance. If it is an
* enum constant or a synthetic field, does nothing.
* @param out output stream
* @param field field to dump
* @param hasValue should it define a default value.
*/
protected void dumpField(PrintStream out, Field field, boolean hasValue) {
int mod = field.getModifiers();
if (!isAccessible(mod)) return;
if (field.isEnumConstant() || field.isSynthetic()) return;
Annotation [] annotations =
optAnnotations == null ? field.getDeclaredAnnotations() : optAnnotations.getDeclaredAnnotations(field);
dumpAnnotationList(out, annotations);
if (database != null) {
dumpMissingAnnotation(out, database.missingVersionsOf(field));
}
indent(out);
out.print(modifier(mod));
type(out, field.getGenericType());
out.print(" ");
out.print(field.getName());
if (hasValue) dumpFieldValue(out,field);
out.println(";");
out.println();
}
/**
* Generates a value for a field if necessary.
* @param out
* @param field
*/
protected void dumpFieldValue(PrintStream out, Field field) {
int mod = field.getModifiers();
if (Modifier.isFinal(mod)) {
out.print(" = ");
if (Modifier.isStatic(mod)) dumpValue(out,field);
else dumpDefaultValue(out, field.getGenericType(), null);
}
}
/**
* Dumps the result of a method. Relies on the default value.
* @param out output stream
* @param returnType type of the result for which a return is done.
*/
protected void dumpMethodBody(PrintStream out, Method meth, Map<TypeVariable<?>, Type> typeEnv) {
Type returnType = meth.getGenericReturnType();
String typeName = returnType.toString();
if(typeName.equals("void")) return;
indent(out);
out.print("return ");
dumpDefaultValue(out,returnType, typeEnv);
out.println(";");
}
/**
* Dumps a default value for a given type. As it can be done with types from the super class, we need
* to resolve the parameters given to this type, so the type environment.
* @param out output stream
* @param type type of the default value
* @param typeEnv type environment of the current class to interpret the type
* variables.
*/
protected void dumpDefaultValue(PrintStream out, Type type, Map<TypeVariable<?>, Type> typeEnv) {
if(type.equals(boolean.class)) out.print("false");
else if (type.equals(byte.class)) out.print("(byte) 0");
else if (type.equals(char.class)) out.print("'\\u0000'");
else if (type.equals(short.class)) out.print("(short) 0");
else if (type.equals(int.class)) out.print("0");
else if (type.equals(long.class)) out.print("0l");
else if (type.equals(float.class)) out.print("0.0f");
else if (type.equals(double.class)) out.print("0.0d");
else {
if (type instanceof TypeVariable<?> && typeEnv == null)
out.print("null");
else {
out.print("(");
type(out, type, false, typeEnv);
out.print(") null");
}
}
}
/**
* Builds the string representing a simple value after escaping its contents if
* necessary. Limited for annotations.
* We need to restore the strings correctly ! They may have been butchered by the classloader.
* @param obj a value in one of the limited annotation types.
* @return a string corresponding.
*/
private String simpleValue(Object obj, boolean isTop) {
if (obj instanceof String) return "\"" + StringUtil.escape(rf.restoreString((String) obj)) + "\"";
if (obj instanceof Character) return "'" + StringUtil.escape(((Character) obj).toString()) + "'";
if (obj instanceof Annotation) {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
PrintStream out2 = new PrintStream(bs);
dumpAnnotation(out2, (Annotation) obj);
out2.close();
return bs.toString();
}
if (obj.getClass().isArray()) {
if (obj instanceof int []) {
int [] vals = (int []) obj;
if (isTop && vals.length == 1) {
return String.valueOf(vals[0]);
}
StringBuilder b = new StringBuilder();
boolean first = true;
b.append("{");
for(int i=0; i < vals.length; i++) {
if (first) first = false;
else b.append(", ");
b.append(vals[i]);
}
b.append("}");
return b.toString();
} else {
if (isTop && ((Object []) obj).length == 1)
return simpleValue(((Object []) obj)[0], false);
Object [] array = (Object []) obj;
StringBuilder b = new StringBuilder();
boolean first = true;
b.append("{");
for(int i=0; i < array.length; i++) {
if (first) first = false;
else b.append(", ");
b.append(simpleValue(array[i], false));
}
b.append("}");
return b.toString();
}
}
if(obj instanceof Enum<?>) {
Class <?> c = obj.getClass();
return shorten(c.getName()) + "." + obj;
}
if(obj instanceof Class<?>) {
return shorten(((Class <?>) obj).getName()) + ".class";
}
return obj.toString();
}
/**
* If available, dumps the static value of a final field.
* @param out output stream
* @param field the field for which we seek a static value.
*/
protected void dumpValue(PrintStream out, Field field) {
try {
Class<?> typ = field.getType();
if (typ.equals(int.class)) {
out.print(field.getInt(null));
} else if (typ.equals(char.class)) {
char v = field.getChar(null);
out.print("'" + StringUtil.escape(String.valueOf(v)) + "'");
} else if (typ.equals(byte.class)) {
out.print(field.getByte(null));
} else if (typ.equals(boolean.class)) {
out.print(field.getBoolean(null));
} else if (typ.equals(short.class)) {
out.print(field.getShort(null));
} else if (typ.equals(float.class)) {
out.print(StringUtil.escapeFloat(field.getFloat(null)));
} else if (typ.equals(double.class)) {
out.print(StringUtil.escapeDouble(field.getDouble(null)));
} else if (typ.equals(long.class)) {
out.print(field.getLong(null) + "l");
} else if (typ.equals(java.lang.String.class)) {
String v = (String) field.get(null);
if (v != null) out.print("\"" + StringUtil.escape(rf.restoreString(v)) + "\"");
else out.print("null");
} else dumpValueAnyClass(out, typ);
} catch (Throwable e) {
dumpDefaultValue(out, field.getType(), null);
}
}
/**
* The value of a field of a class not declared. This is null in the first phase but we
* must provide a better value in the second case.
* @param out
* @param typ
*/
protected void dumpValueAnyClass(PrintStream out, Type typ) {
out.print( "null");
}
/**
* Computes the modifier prefix in Java syntax.
* @param i modifier bitfield in Java format
* @return a string in Java syntax.
*/
protected static String modifier(int i) {
StringBuilder b = new StringBuilder();
if(Modifier.isPrivate(i)) b.append("private ");
if(Modifier.isProtected(i)) b.append("protected ");
if(Modifier.isPublic(i)) b.append("public ");
if(Modifier.isAbstract(i)) b.append("abstract ");
if(Modifier.isStatic(i)) b.append("static ");
if(Modifier.isFinal(i)) b.append("final ");
if(Modifier.isSynchronized(i)) b.append("synchronized ");
if(Modifier.isNative(i)) b.append("native ");
return b.toString();
}
/**
* This method registers known class to handle. The purpose is to make sure that super
* class of visible classes even if they are themselves invisible will be added to
* the output.
* @param c the class to register.
*/
public void register(Class <?> c) {
try {
if (c.isAnonymousClass() || isInvisible(c)) return;
} catch (Throwable e) { return; }
Class <?> superClass = c.getSuperclass();
while(superClass != null && isInvisible(superClass)) {
neededClasses.add(superClass);
superClass = superClass.getSuperclass();
}
}
/**
* Debug message giving the number of nested classes.
*/
public void dumpRegistered() {
System.err.println("==============");
System.err.println("Needed Classes");
System.err.println("==============");
for(Class<?> c : neededClasses) { System.err.println(rf.restoreString(c.getCanonicalName())); }
System.err.println("==============");
}
}