Coverage Report - org.truth0.codegen.IteratingWrapperClassBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
IteratingWrapperClassBuilder
0%
0/38
0%
0/30
3.667
 
 1  
 package org.truth0.codegen;
 2  
 
 3  
 import static java.lang.reflect.Modifier.isFinal;
 4  
 import static java.lang.reflect.Modifier.isPrivate;
 5  
 import static java.lang.reflect.Modifier.isStatic;
 6  
 
 7  
 import java.lang.annotation.Annotation;
 8  
 import java.lang.reflect.Method;
 9  
 import java.lang.reflect.Modifier;
 10  
 import java.util.Arrays;
 11  
 import java.util.List;
 12  
 
 13  
 import org.truth0.subjects.Subject;
 14  
 import org.truth0.subjects.SubjectFactory;
 15  
 import org.truth0.util.ReflectionUtil;
 16  
 
 17  
 /**
 18  
  * A builder of classes to wrap a provided SubjectFactory's concrete Subject subclass.
 19  
  * The generated class will be a direct subclass of the concrete Subject subclass, but
 20  
  * each public, protected, or friendly method not declared by Object will be wrapped
 21  
  * such that invocations on it will be invoked on a new Subject instance populated
 22  
  * with an element in the provided collection.  This allows for a type-safe, IDE-discoverable
 23  
  * Subject in a for-each style.
 24  
  */
 25  
 public class IteratingWrapperClassBuilder {
 26  
 
 27  
   /**
 28  
    * <p>A string intended for use in String.format() representing the
 29  
    *    text of the code of the wrapper class.
 30  
    *
 31  
    * <p>Format parameters include:
 32  
    * <ol>
 33  
    *   <li>package name</li>
 34  
    *   <li>simple name of a concrete subtype of Subject</li>
 35  
    *   <li>the fully qualified name of the target type</li>
 36  
    *   <li>the text of the code of the wrapped methods</li>
 37  
    * </ol>
 38  
    * </p>
 39  
    */
 40  
   private static final String CLASS_TEMPLATE =
 41  
       "package %1$s;%n" +
 42  
       "%n" +
 43  
       "import org.truth0.FailureStrategy;%n" +
 44  
       "import org.truth0.subjects.SubjectFactory;%n" +
 45  
       "%n" +
 46  
       "public class %2$sIteratingWrapper extends %2$s {%n" +
 47  
       "%n" +
 48  
       "  private final SubjectFactory subjectFactory;%n" +
 49  
       "  private final Iterable<%3$s> data;%n" +
 50  
       "%n" +
 51  
       "  public %2$sIteratingWrapper(%n" +
 52  
       "      FailureStrategy failureStrategy,%n" +
 53  
       "      SubjectFactory<?, ?> subjectFactory,%n" +
 54  
       "      Iterable<%3$s> data%n" +
 55  
       "  ) {%n" +
 56  
       "    super(failureStrategy, (%3$s)null);%n" +
 57  
       "    this.subjectFactory = subjectFactory;%n" +
 58  
       "    this.data = data;%n" +
 59  
       "  }%n" +
 60  
       "%n" +
 61  
       "%4$s" +
 62  
       "}%n";
 63  
 
 64  
   /**
 65  
    * <p>A string intended for use in String.format() representing the
 66  
    *    text of the code of all wrapped methods.
 67  
    *
 68  
    * <p>Format parameters include:
 69  
    * <ol>
 70  
    *   <li>visibility</li>
 71  
    *   <li>fully qualified name of the return type</li>
 72  
    *   <li>method name</li>
 73  
    *   <li>method's parameter list</li>
 74  
    *   <li>the target type of the Subject</li>
 75  
    *   <li>concrete subtype of Subject to be wrapped</li>
 76  
    *   <li>parameter list</li>
 77  
    * </ol>
 78  
    * </p>
 79  
    */
 80  
   private static final String WRAPPER_METHOD_TEMPLATE =
 81  
       "  %1$s %2$s %3$s(%4$s) {%n" +
 82  
       "    for (%5$s item : data) {%n" +
 83  
       "      %6$s subject = (%6$s)subjectFactory.getSubject(failureStrategy, item);%n" +
 84  
       "      subject.%3$s(%7$s);%n" +
 85  
       "    }%n" +
 86  
       "  }%n";
 87  
 
 88  
 
 89  
   private static final int TARGET_TYPE_PARAMETER = 1;
 90  
 
 91  
   private static final String ITERATING_WRAPPER = "IteratingWrapper";
 92  
 
 93  
   private final SubjectFactory<?, ?> subjectFactory;
 94  
 
 95  
   public final String className;
 96  
 
 97  0
   public IteratingWrapperClassBuilder(SubjectFactory<?,?> subjectFactory) {
 98  0
     this.subjectFactory = subjectFactory;
 99  0
     this.className = subjectFactory.getSubjectClass().getCanonicalName() + ITERATING_WRAPPER;
 100  0
   }
 101  
 
 102  
   public String build() {
 103  0
     Class<?> subjectClass = subjectFactory.getSubjectClass();
 104  0
     List<Method> methods = Arrays.asList(subjectClass.getMethods());
 105  0
     Class<?> targetType = ReflectionUtil.typeParameter(subjectClass, TARGET_TYPE_PARAMETER);
 106  
 
 107  0
     StringBuilder methodWrappers = new StringBuilder();
 108  0
     for (Method m : methods)  {
 109  0
       appendMethodWrapper(methodWrappers, subjectClass, targetType, m);
 110  0
     }
 111  0
     String code = String.format(
 112  
         CLASS_TEMPLATE,
 113  
         subjectClass.getPackage().getName(),
 114  
         subjectClass.getSimpleName(),
 115  
         targetType.getCanonicalName(),
 116  
         methodWrappers.toString());
 117  
 
 118  0
     return code;
 119  
   }
 120  
 
 121  
   private void appendMethodWrapper(
 122  
       StringBuilder code,
 123  
       Class<?> subjectType,
 124  
       Class<?> targetType,
 125  
       Method method) {
 126  0
     int modifiers = method.getModifiers();
 127  0
     boolean shouldWrap =
 128  
         !method.getDeclaringClass().equals(Subject.class) &&
 129  
         !method.getDeclaringClass().equals(Object.class) &&
 130  
         !(isFinal(modifiers) || isPrivate(modifiers) || isStatic(modifiers));
 131  
 
 132  0
     if (shouldWrap) {
 133  0
       code.append(String.format(
 134  
           WRAPPER_METHOD_TEMPLATE,
 135  
           stringVisibility(modifiers),
 136  
           method.getReturnType().getCanonicalName(),
 137  
           method.getName(),
 138  
           methodSignature(
 139  
               method.getParameterTypes(),
 140  
               method.getParameterAnnotations()).toString(),
 141  
           targetType.getCanonicalName(),
 142  
           subjectType.getCanonicalName(),
 143  
           methodParameterList(method.getParameterTypes().length)
 144  
           ));
 145  
     }
 146  0
   }
 147  
 
 148  
   private static StringBuilder methodParameterList(int length) {
 149  0
     StringBuilder builder = new StringBuilder();
 150  0
     for (int i = 0; i < length; i++) {
 151  0
       if (i > 0) builder.append(", ");
 152  0
       builder.append("arg").append(0);
 153  
     }
 154  0
     return builder;
 155  
   }
 156  
 
 157  
   /** Builds a string for the parameters within a method signature. */
 158  
   private static StringBuilder methodSignature(Class<?>[] parameters, Annotation[][] annotations) {
 159  0
     StringBuilder builder = new StringBuilder();
 160  0
     for (int i = 0, iLen = parameters.length; i < iLen; i++) {
 161  0
       if (i > 0) builder.append(", ");
 162  0
       for (int j = 0, jLen = annotations[i].length; j < jLen; j++) {
 163  0
         if (j > 0) builder.append(" ");
 164  0
         builder.append("@").append(annotations[i][j].annotationType().getCanonicalName());
 165  0
         builder.append(" ");
 166  
       }
 167  0
       builder.append(parameters[i].getCanonicalName());
 168  0
       builder.append(" arg").append(i);
 169  
     }
 170  0
     return builder;
 171  
   }
 172  
 
 173  
   private static String stringVisibility(int modifiers) {
 174  0
     if (Modifier.isProtected(modifiers)) {
 175  0
       return "protected";
 176  0
     } else if (Modifier.isPublic(modifiers)) {
 177  0
       return "public";
 178  
     } else {
 179  0
       return "";
 180  
     }
 181  
   }
 182  
 
 183  
 }