View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.coverage.modification;
6   
7   import static java.util.regex.Pattern.compile;
8   
9   import static mockit.internal.util.GeneratedClasses.isExternallyGeneratedSubclass;
10  
11  import edu.umd.cs.findbugs.annotations.NonNull;
12  import edu.umd.cs.findbugs.annotations.Nullable;
13  
14  import java.net.URL;
15  import java.security.CodeSource;
16  import java.security.ProtectionDomain;
17  import java.util.regex.Matcher;
18  import java.util.regex.Pattern;
19  
20  import mockit.coverage.Configuration;
21  
22  final class ClassSelection {
23      private static final String THIS_CLASS_NAME = ClassSelection.class.getName();
24      private static final ClassLoader THIS_CLASS_LOADER = ClassSelection.class.getClassLoader();
25      private static final Pattern CSV = compile(",");
26      private static final Pattern DOT = compile("\\.");
27      private static final Pattern STAR = compile("\\*");
28      private static final Pattern TEST_CLASS_NAME = compile(".+Test(\\$.+)?");
29  
30      boolean loadedOnly;
31      @Nullable
32      private Matcher classesToInclude;
33      @Nullable
34      private Matcher classesToExclude;
35      @NonNull
36      private final Matcher testCode;
37      private boolean configurationRead;
38  
39      ClassSelection() {
40          testCode = TEST_CLASS_NAME.matcher("");
41      }
42  
43      @Nullable
44      private static Matcher newMatcherForClassSelection(@NonNull String specification) {
45          if (specification.isEmpty()) {
46              return null;
47          }
48  
49          String[] specs = CSV.split(specification);
50          StringBuilder finalRegexBuilder = new StringBuilder();
51          String sep = "";
52  
53          for (String spec : specs) {
54              String regex = null;
55  
56              if (spec.indexOf('\\') >= 0) {
57                  regex = spec;
58              } else if (!spec.isEmpty()) {
59                  regex = DOT.matcher(spec).replaceAll("\\.");
60                  regex = STAR.matcher(regex).replaceAll(".*");
61                  regex = regex.replace('?', '.');
62              }
63  
64              if (regex != null) {
65                  finalRegexBuilder.append(sep).append(regex);
66                  sep = "|";
67              }
68          }
69  
70          String finalRegex = finalRegexBuilder.toString();
71          return finalRegex.isEmpty() ? null : compile(finalRegex).matcher("");
72      }
73  
74      boolean isSelected(@NonNull String className, @NonNull ProtectionDomain protectionDomain) {
75          CodeSource codeSource = protectionDomain.getCodeSource();
76  
77          if (codeSource == null || isIneligibleForSelection(className)
78                  || !canAccessJMockitFromClassToBeMeasured(protectionDomain)) {
79              return false;
80          }
81  
82          URL location = findLocationInCodeSource(className, protectionDomain);
83  
84          if (location == null) {
85              return false;
86          }
87  
88          if (!configurationRead) {
89              readConfiguration();
90          }
91  
92          if (isClassExcludedFromCoverage(className)) {
93              return false;
94          }
95  
96          if (classesToInclude != null) {
97              return classesToInclude.reset(className).matches();
98          }
99  
100         return !isClassFromExternalLibrary(location);
101     }
102 
103     private static boolean isIneligibleForSelection(@NonNull String className) {
104         return className.charAt(0) == '[' || className.startsWith("mockit.") || className.startsWith("org.hamcrest.")
105                 || className.startsWith("org.junit.") || className.startsWith("junit.")
106                 || className.startsWith("org.testng.") || className.startsWith("org.apache.maven.surefire.")
107                 || isExternallyGeneratedSubclass(className);
108     }
109 
110     private static boolean canAccessJMockitFromClassToBeMeasured(@NonNull ProtectionDomain protectionDomain) {
111         ClassLoader loaderOfClassToBeMeasured = protectionDomain.getClassLoader();
112 
113         if (loaderOfClassToBeMeasured != null) {
114             try {
115                 Class<?> thisClass = loaderOfClassToBeMeasured.loadClass(THIS_CLASS_NAME);
116                 return thisClass == ClassSelection.class;
117             } catch (ClassNotFoundException ignore) {
118             }
119         }
120 
121         return false;
122     }
123 
124     @Nullable
125     private static URL findLocationInCodeSource(@NonNull String className, @NonNull ProtectionDomain protectionDomain) {
126         URL location = protectionDomain.getCodeSource().getLocation();
127 
128         if (location == null) {
129             if (protectionDomain.getClassLoader() == THIS_CLASS_LOADER) {
130                 return null; // it's likely a dynamically generated class
131             }
132 
133             // It's from a custom class loader, so it may exist in the classpath.
134             String classFileName = className.replace('.', '/') + ".class";
135             location = THIS_CLASS_LOADER.getResource(classFileName);
136         }
137 
138         return location;
139     }
140 
141     private boolean isClassExcludedFromCoverage(@NonNull String className) {
142         return classesToExclude != null && classesToExclude.reset(className).matches()
143                 || testCode.reset(className).matches();
144     }
145 
146     private static boolean isClassFromExternalLibrary(@NonNull URL location) {
147         if ("jar".equals(location.getProtocol())) {
148             return true;
149         }
150 
151         String path = location.getPath();
152         return path.endsWith(".jar") || path.endsWith("/.cp/") || path.endsWith("/test-classes/");
153     }
154 
155     private void readConfiguration() {
156         String classes = Configuration.getProperty("classes", "");
157         loadedOnly = "loaded".equals(classes);
158         classesToInclude = loadedOnly ? null : newMatcherForClassSelection(classes);
159 
160         String excludes = Configuration.getProperty("excludes", "");
161         classesToExclude = newMatcherForClassSelection(excludes);
162 
163         configurationRead = true;
164     }
165 }