View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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; // it's likely a dynamically generated class
132             }
133 
134             // It's from a custom class loader, so it may exist in the classpath.
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 }