package com.francetelecom.rd.stubs.engine; /* * #%L * Matos * $Id:$ * $HeadURL:$ * %% * Copyright (C) 2008 - 2014 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.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * @author Laurent Sebag * Dumps the interfaces and abstract classes that have no implementation counter part in a hierarchy or * through annotations. */ public class WitnessIssuesDumper { private static final int NUMBER_NEEDED_BY_DISPLAYED = 3; private ReflexUtil rf; private Hierarchy hierarchy; private HashSet<Class<?>> withWitnesses; private HashMap<Class<?>, Set<Method>> neededBy; /** * Constructor * @param rf remapping utilities * @param hierarchy hierarchy */ public WitnessIssuesDumper(ReflexUtil rf, Hierarchy hierarchy) { this.rf = rf; this.hierarchy = hierarchy; } /** * Static entry point. Prints to a files * @param outFile * @param rf remapping utilities * @param hierarchy hierarchy * @throws FileNotFoundException */ public static void dumpWitnessIssues(String outFile, ReflexUtil rf, Hierarchy hierarchy) throws FileNotFoundException { PrintStream out = new PrintStream(new FileOutputStream(outFile)); dumpWitnessIssues(out, rf, hierarchy); } /** * Static entry point. Prints to standard output * @param rf remapping utilities * @param hierarchy hierarchy */ public static void dumpWitnessIssues(ReflexUtil rf, Hierarchy hierarchy) { dumpWitnessIssues(System.out, rf, hierarchy); } /** * Static entry point. Prints to a stream. * @param out * @param rf remapping utilities * @param hierarchy hierarchy */ public static void dumpWitnessIssues(PrintStream out, ReflexUtil rf, Hierarchy hierarchy) { WitnessIssuesDumper dumper = new WitnessIssuesDumper(rf, hierarchy); Set<Class<?>> neededAndNecessary = dumper.getNeededandNecessary(); out.println("====================="); out.println("Witness Issues Report"); out.println("====================="); out.println(""); out.println("Types that are necessary and have no witness: "+neededAndNecessary.size()); out.println(""); out.println(""); for(Class<?> t : neededAndNecessary) { StringBuilder sb = new StringBuilder(); Set<Method> usedBy = dumper.getMethodsUsingType(t); Iterator<Method> i = usedBy.iterator(); int c = 0; sb.append("["); while(i.hasNext() && c++<NUMBER_NEEDED_BY_DISPLAYED) { sb.append(i.next()); sb.append(", "); } sb.delete(sb.length()-2, sb.length()); if(i.hasNext()) { sb.append(", ... and "); sb.append(usedBy.size()-NUMBER_NEEDED_BY_DISPLAYED); sb.append(" others"); } sb.append("]"); out.println(rf.restoreString(t.getName()) +" needed by "+ sb.toString()); out.println(""); } out.println(""); out.println("====================="); out.close(); } private Set<Method> getMethodsUsingType(Class<?> t) { return neededBy.get(t); } /** * @return the set of types with witnesses */ private Set<Class<?>> getNeededandNecessary() { withWitnesses = new HashSet<Class<?>>();; neededBy = new HashMap<Class<?>, Set<Method>>(); for(Class <?> type : hierarchy.getContents()) { Annotation [] annots = type.getAnnotations(); Annotation accumulator_annot = rf.findAnnotation(EngineConstant.ACCUMULATOR_ANNOT, annots); int mod = type.getModifiers(); boolean isConcrete = false; // // fill the withWitnesses Set // if(!Modifier.isAbstract(mod) && !Modifier.isInterface(mod)) { // A concrete class if(type.getDeclaringClass()==null) isConcrete = true; } else { // isAbstract || isInterface boolean hasDirectFakeImplem; Annotation real_annot = rf.findAnnotation(EngineConstant.REAL_ANNOT, annots); hasDirectFakeImplem = real_annot!=null; // boolean hasDirectFakeImplem = false; // List<Class<?>> implems = hierarchy.getImplementors(type); // if( implems!=null ) { // for(Class<?> implementor : implems) { // Annotation[] implemAnnots = implementor.getAnnotations(); // Annotation real_annot = rf.findAnnotation(EngineConstant.REAL_ANNOT, implemAnnots); // if(real_annot!=null) { // hasDirectFakeImplem = true; // } // } // } // abstract class or interface but has a direct fake implementer (Real annotation) // OR type associated to a field through an Accumulator annotation if(hasDirectFakeImplem || accumulator_annot!=null) { isConcrete = true; } } if(isConcrete) { recursAddTypesWithWitnesses(withWitnesses, type); } // // fill the necessary types map // for(Method m : type.getDeclaredMethods()) { Annotation [] methodAnnots = m.getAnnotations(); if(isConcrete) { Annotation field_get_annot = rf.findAnnotation(EngineConstant.FIELD_GET_ANNOT, methodAnnots); Annotation code_annot = rf.findAnnotation(EngineConstant.CODE_ANNOT, methodAnnots); Class<?> returnType = m.getReturnType(); while (returnType.isArray()) returnType = returnType.getComponentType(); boolean omit = returnType.isPrimitive() || returnType.isMemberClass(); if(!returnType.equals(Void.TYPE) && field_get_annot==null && code_annot==null) { // TODO there seems to be a bug here. See public oLaBsTuBs.android.widget.Adapter oLaBsTuBs.android.widget.GridView.getAdapter() // which has a FieldGet annotation and still passes this if conditions // if(m.getName().contains("getAdapter")) System.err.println("##################### "+type.getName()+"."+ m.getName()); // a result is given back by a concrete stub method, which is not a field getter and has no code associated if(!omit) addTypetoNeeded(returnType, m, neededBy); } } Annotation callback_annot = rf.findAnnotation(EngineConstant.CALLBACK_ANNOT, methodAnnots); if(callback_annot!=null) { // an argument given to a callback method Class<?>[] parameters = m.getParameterTypes(); for(Class<?> p : parameters) { while (p.isArray()) p = p.getComponentType(); boolean omit = p.isPrimitive() || p.isMemberClass(); if(!omit) addTypetoNeeded(p, m, neededBy); } } } } // complement of types with witnesses in the keyset of the map (neededBy.keySet() - withWitnesses) // Set<Class<?>> result = neededBy.keySet(); TreeSet<Class<?>> result = new TreeSet<Class<?>>(new Comparator<Class<?>>() { @Override public int compare(Class<?> o1, Class<?> o2) { return o1.getName().compareTo(o2.getName()); }; }); result.addAll(neededBy.keySet()); result.removeAll(withWitnesses); return result; } private static void addTypetoNeeded(Class<?> type, Method method, Map<Class<?>,Set<Method>> neededBy) { Set<Method> methods = neededBy.get(type); if(methods==null) { methods = new HashSet<Method>(); neededBy.put(type, methods); } methods.add(method); } private void recursAddTypesWithWitnesses(Set<Class<?>> withWitnesses, Class<?> type) { if(type==null || withWitnesses.contains(type)) return; withWitnesses.add(type); if( type.isInterface() ) { // browse implementors List<Class<?>> implementors = hierarchy.getImplementors(type); if(implementors!=null) { for(Class<?> implementor : implementors ) { recursAddTypesWithWitnesses(withWitnesses, implementor); } } } else { // browse super classes Class<?> parent = type.getSuperclass(); if(parent!=null && !withWitnesses.contains(parent)) { recursAddTypesWithWitnesses(withWitnesses, parent); } // browse implems for(Class<?> implem : type.getInterfaces() ) { recursAddTypesWithWitnesses(withWitnesses, implem); } } } }