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