/testability-explorer/src/main/java/com/google/test/metric/MetricComputer.java
Java | 136 lines | 76 code | 15 blank | 45 comment | 11 complexity | 4b08889e35dbe031e5978dab63902f76 MD5 | raw file
1/* 2 * Copyright 2007 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16package com.google.test.metric; 17 18import com.google.inject.Inject; 19import com.google.inject.name.Named; 20import com.google.test.metric.ConfigModule.Error; 21import static com.google.test.metric.Reason.IMPLICIT_CONSTRUCTOR; 22import static com.google.test.metric.Reason.IMPLICIT_SETTER; 23import static com.google.test.metric.Reason.IMPLICIT_STATIC_INIT; 24import com.google.test.metric.TestabilityVisitor.CostRecordingFrame; 25import com.google.test.metric.TestabilityVisitor.Frame; 26 27import java.io.PrintStream; 28import java.util.ArrayList; 29import java.util.List; 30 31public class MetricComputer { 32 33 private final ClassRepository classRepository; 34 private final PrintStream err; 35 private final WhiteList whitelist; 36 private final int recordingDepth; 37 38 @Inject 39 public MetricComputer(ClassRepository classRepository, @Error PrintStream err, 40 WhiteList whitelist, @Named("printDepth") int recordingDepth) { 41 this.classRepository = classRepository; 42 this.err = err; 43 this.whitelist = whitelist; 44 this.recordingDepth = recordingDepth; 45 } 46 47 public ClassCost compute(String name) { 48 return compute(classRepository.getClass(name)); 49 } 50 51 /** 52 * Computing the ClassCost for a ClassInfo involves tallying up all the MethodCosts contained 53 * in the class. Then an overall cost is calculated, based on the {@code CostModel} the metric 54 * computer is using. 55 * 56 * @param clazz to compute the metric for. 57 * @return classCost 58 */ 59 public ClassCost compute(ClassInfo clazz) { 60 List<MethodCost> methods = new ArrayList<MethodCost>(); 61 for (MethodInfo method : clazz.getMethods()) { 62 methods.add(compute(method)); 63 } 64 return new ClassCost(clazz.getName(), methods); 65 } 66 67 /** 68 * Computing the MethodCost for a MethodInfo involves tallying up: 69 * <ul><li>The cost in any static initialization blocks of the class which holds the method.</li> 70 * <li>The cost of constructing the object.</li> 71 * <li>Recognizing injectability through setter methods. This in most cases improves the score 72 * unless you have lots of code in your setter.</li> 73 * <li>The field costs</li> 74 * <li>Lastly the costs are added up for all of the lines in this method. This includes the 75 * transitive non-mockable/interceptable closure of all the costs of the methods that are called.</li> 76 * <li></li></ul> 77 * 78 * @param method to compute the cost for. 79 * @return MethodCost for this method, including the accumulated costs the methods it calls. This 80 * MethodCost is guaranteed to have already been linked (sealed for adding additional costs). 81 */ 82 public MethodCost compute(MethodInfo method) { 83 TestabilityVisitor visitor = new TestabilityVisitor(classRepository, new VariableState(), err, whitelist); 84 TestabilityVisitor.CostRecordingFrame frame = visitor.createFrame(method, recordingDepth); 85 addStaticInitializationCost(method, frame); 86 if (!method.isStatic() && !method.isConstructor()) { 87 addConstructorCost(method, frame); 88 addSetterInjection(method, frame); 89 } 90 addFieldCost(method, frame); 91 return frame.applyMethodOperations(); 92 } 93 94 95 96 /** Goes through all methods and adds an implicit cost for those beginning with "set" (assuming 97 * to test the {@code baseMethod}'s class, you need to be able to call the setters for initialization. */ 98 private void addSetterInjection(MethodInfo baseMethod, CostRecordingFrame frame) { 99 for (MethodInfo setter : baseMethod.getSiblingSetters()) { 100 frame.applyImplicitCost(setter, IMPLICIT_SETTER); 101 } 102 } 103 104 /** Adds an implicit cost to all non-static methods for calling the constructor. (Because to test 105 * any instance method, you must be able to instantiate the class.) Also marks parameters 106 * injectable for the constructor with the most non-primitive parameters. */ 107 private void addConstructorCost(MethodInfo method, CostRecordingFrame frame) { 108 MethodInfo constructor = method.getClassInfo().getConstructorWithMostNonPrimitiveParameters(); 109 if (constructor != null) { 110 frame.applyImplicitCost(constructor, IMPLICIT_CONSTRUCTOR); 111 } 112 } 113 114 /** Doesn't really add the field costs (there are none), but marks non-private fields as injectable. */ 115 private void addFieldCost(MethodInfo method, 116 Frame frame) { 117 for (FieldInfo field : method.getClassInfo().getFields()) { 118 if (!field.isPrivate()) { 119 frame.getGlobalVariables().setInjectable(field); 120 } 121 } 122 } 123 124 /** Includes the cost of all static initialization blocks, as well as static field assignments. */ 125 private void addStaticInitializationCost(MethodInfo baseMethod, CostRecordingFrame frame) { 126 if (baseMethod.isStaticConstructor()) { 127 return; 128 } 129 for (MethodInfo method : baseMethod.getClassInfo().getMethods()) { 130 if (method.isStaticConstructor()) { 131 frame.applyImplicitCost(method, IMPLICIT_STATIC_INIT); 132 } 133 } 134 } 135 136}