1
2
3
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;
131 }
132
133
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 }