Coverage Report - org.truth0.codegen.IteratingWrapperClassBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
IteratingWrapperClassBuilder
94%
35/37
80%
24/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  4
   public IteratingWrapperClassBuilder(SubjectFactory<?,?> subjectFactory) {
 98  4
     this.subjectFactory = subjectFactory;
 99  4
     this.className = subjectFactory.getSubjectClass().getCanonicalName() + ITERATING_WRAPPER;
 100  4
   }
 101  
 
 102  
   public String build() {
 103  4
     Class<?> subjectClass = subjectFactory.getSubjectClass();
 104  4
     List<Method> methods = Arrays.asList(subjectClass.getMethods());
 105  4
     Class<?> targetType = ReflectionUtil.typeParameter(subjectClass, TARGET_TYPE_PARAMETER);
 106  
 
 107  4
     StringBuilder methodWrappers = new StringBuilder();
 108  4
     for (Method m : methods)  {
 109  82
       appendMethodWrapper(methodWrappers, subjectClass, targetType, m);
 110  
     }
 111  4
     String code = String.format(
 112  
         CLASS_TEMPLATE,
 113  
         subjectClass.getPackage().getName(),
 114  
         subjectClass.getSimpleName(),
 115  
         targetType.getCanonicalName(),
 116  
         methodWrappers.toString());
 117  
 
 118  4
     return code;
 119  
   }
 120  
 
 121  
   private void appendMethodWrapper(
 122  
       StringBuilder code,
 123  
       Class<?> subjectType,
 124  
       Class<?> targetType,
 125  
       Method method) {
 126  82
     int modifiers = method.getModifiers();
 127  82
     boolean shouldWrap =
 128  
         !method.getDeclaringClass().equals(Subject.class) &&
 129  
         !method.getDeclaringClass().equals(Object.class) &&
 130  
         !(isFinal(modifiers) || isPrivate(modifiers) || isStatic(modifiers));
 131  
 
 132  82
     if (shouldWrap) {
 133  14
       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  82
   }
 147  
 
 148  
   private static StringBuilder methodParameterList(int length) {
 149  14
     StringBuilder builder = new StringBuilder();
 150  30
     for (int i = 0; i < length; i++) {
 151  16
       if (i > 0) builder.append(", ");
 152  16
       builder.append("arg").append(0);
 153  
     }
 154  14
     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  14
     StringBuilder builder = new StringBuilder();
 160  30
     for (int i = 0, iLen = parameters.length; i < iLen; i++) {
 161  16
       if (i > 0) builder.append(", ");
 162  17
       for (int j = 0, jLen = annotations[i].length; j < jLen; j++) {
 163  1
         if (j > 0) builder.append(" ");
 164  1
         builder.append("@").append(annotations[i][j].annotationType().getCanonicalName());
 165  1
         builder.append(" ");
 166  
       }
 167  16
       builder.append(parameters[i].getCanonicalName());
 168  16
       builder.append(" arg").append(i);
 169  
     }
 170  14
     return builder;
 171  
   }
 172  
 
 173  
   private static String stringVisibility(int modifiers) {
 174  14
     if (Modifier.isProtected(modifiers)) {
 175  0
       return "protected";
 176  14
     } else if (Modifier.isPublic(modifiers)) {
 177  14
       return "public";
 178  
     } else {
 179  0
       return "";
 180  
     }
 181  
   }
 182  
 
 183  
 }