/testability-explorer/src/main/java/com/google/test/metric/MetricComputer.java

http://testability-explorer.googlecode.com/ · 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. */
  16. package com.google.test.metric;
  17. import com.google.inject.Inject;
  18. import com.google.inject.name.Named;
  19. import com.google.test.metric.ConfigModule.Error;
  20. import static com.google.test.metric.Reason.IMPLICIT_CONSTRUCTOR;
  21. import static com.google.test.metric.Reason.IMPLICIT_SETTER;
  22. import static com.google.test.metric.Reason.IMPLICIT_STATIC_INIT;
  23. import com.google.test.metric.TestabilityVisitor.CostRecordingFrame;
  24. import com.google.test.metric.TestabilityVisitor.Frame;
  25. import java.io.PrintStream;
  26. import java.util.ArrayList;
  27. import java.util.List;
  28. public class MetricComputer {
  29. private final ClassRepository classRepository;
  30. private final PrintStream err;
  31. private final WhiteList whitelist;
  32. private final int recordingDepth;
  33. @Inject
  34. public MetricComputer(ClassRepository classRepository, @Error PrintStream err,
  35. WhiteList whitelist, @Named("printDepth") int recordingDepth) {
  36. this.classRepository = classRepository;
  37. this.err = err;
  38. this.whitelist = whitelist;
  39. this.recordingDepth = recordingDepth;
  40. }
  41. public ClassCost compute(String name) {
  42. return compute(classRepository.getClass(name));
  43. }
  44. /**
  45. * Computing the ClassCost for a ClassInfo involves tallying up all the MethodCosts contained
  46. * in the class. Then an overall cost is calculated, based on the {@code CostModel} the metric
  47. * computer is using.
  48. *
  49. * @param clazz to compute the metric for.
  50. * @return classCost
  51. */
  52. public ClassCost compute(ClassInfo clazz) {
  53. List<MethodCost> methods = new ArrayList<MethodCost>();
  54. for (MethodInfo method : clazz.getMethods()) {
  55. methods.add(compute(method));
  56. }
  57. return new ClassCost(clazz.getName(), methods);
  58. }
  59. /**
  60. * Computing the MethodCost for a MethodInfo involves tallying up:
  61. * <ul><li>The cost in any static initialization blocks of the class which holds the method.</li>
  62. * <li>The cost of constructing the object.</li>
  63. * <li>Recognizing injectability through setter methods. This in most cases improves the score
  64. * unless you have lots of code in your setter.</li>
  65. * <li>The field costs</li>
  66. * <li>Lastly the costs are added up for all of the lines in this method. This includes the
  67. * transitive non-mockable/interceptable closure of all the costs of the methods that are called.</li>
  68. * <li></li></ul>
  69. *
  70. * @param method to compute the cost for.
  71. * @return MethodCost for this method, including the accumulated costs the methods it calls. This
  72. * MethodCost is guaranteed to have already been linked (sealed for adding additional costs).
  73. */
  74. public MethodCost compute(MethodInfo method) {
  75. TestabilityVisitor visitor = new TestabilityVisitor(classRepository, new VariableState(), err, whitelist);
  76. TestabilityVisitor.CostRecordingFrame frame = visitor.createFrame(method, recordingDepth);
  77. addStaticInitializationCost(method, frame);
  78. if (!method.isStatic() && !method.isConstructor()) {
  79. addConstructorCost(method, frame);
  80. addSetterInjection(method, frame);
  81. }
  82. addFieldCost(method, frame);
  83. return frame.applyMethodOperations();
  84. }
  85. /** Goes through all methods and adds an implicit cost for those beginning with "set" (assuming
  86. * to test the {@code baseMethod}'s class, you need to be able to call the setters for initialization. */
  87. private void addSetterInjection(MethodInfo baseMethod, CostRecordingFrame frame) {
  88. for (MethodInfo setter : baseMethod.getSiblingSetters()) {
  89. frame.applyImplicitCost(setter, IMPLICIT_SETTER);
  90. }
  91. }
  92. /** Adds an implicit cost to all non-static methods for calling the constructor. (Because to test
  93. * any instance method, you must be able to instantiate the class.) Also marks parameters
  94. * injectable for the constructor with the most non-primitive parameters. */
  95. private void addConstructorCost(MethodInfo method, CostRecordingFrame frame) {
  96. MethodInfo constructor = method.getClassInfo().getConstructorWithMostNonPrimitiveParameters();
  97. if (constructor != null) {
  98. frame.applyImplicitCost(constructor, IMPLICIT_CONSTRUCTOR);
  99. }
  100. }
  101. /** Doesn't really add the field costs (there are none), but marks non-private fields as injectable. */
  102. private void addFieldCost(MethodInfo method,
  103. Frame frame) {
  104. for (FieldInfo field : method.getClassInfo().getFields()) {
  105. if (!field.isPrivate()) {
  106. frame.getGlobalVariables().setInjectable(field);
  107. }
  108. }
  109. }
  110. /** Includes the cost of all static initialization blocks, as well as static field assignments. */
  111. private void addStaticInitializationCost(MethodInfo baseMethod, CostRecordingFrame frame) {
  112. if (baseMethod.isStaticConstructor()) {
  113. return;
  114. }
  115. for (MethodInfo method : baseMethod.getClassInfo().getMethods()) {
  116. if (method.isStaticConstructor()) {
  117. frame.applyImplicitCost(method, IMPLICIT_STATIC_INIT);
  118. }
  119. }
  120. }
  121. }