Skip to content

Package: DefaultClasspathScanner

DefaultClasspathScanner

nameinstructionbranchcomplexitylinemethod
DefaultClasspathScanner()
M: 0 C: 12
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
addListener(Consumer)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
createJarFile(URL)
M: 0 C: 14
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
getUrlAsUri(URL)
M: 16 C: 3
16%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 2 C: 1
33%
M: 0 C: 1
100%
isJar(String)
M: 0 C: 12
100%
M: 1 C: 3
75%
M: 1 C: 2
67%
M: 0 C: 1
100%
M: 0 C: 1
100%
lambda$processClass$0(Class, Consumer)
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
processClass(String)
M: 7 C: 13
65%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 2 C: 4
67%
M: 0 C: 1
100%
processClasses(String)
M: 7 C: 32
82%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 2 C: 8
80%
M: 0 C: 1
100%
processDirectory(File, String)
M: 8 C: 96
92%
M: 3 C: 13
81%
M: 3 C: 6
67%
M: 2 C: 16
89%
M: 0 C: 1
100%
processElements(Enumeration, String)
M: 1 C: 41
98%
M: 1 C: 5
83%
M: 1 C: 3
75%
M: 1 C: 10
91%
M: 0 C: 1
100%
processJarFile(JarFile)
M: 14 C: 61
81%
M: 1 C: 5
83%
M: 1 C: 3
75%
M: 2 C: 14
88%
M: 0 C: 1
100%
sanitizePath(URL)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
static {...}
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%

Coverage

1: /**
2: * Copyright (C) 2022 Czech Technical University in Prague
3: * <p>
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: * <p>
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.loaders;
16:
17: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
18: import org.slf4j.Logger;
19: import org.slf4j.LoggerFactory;
20:
21: import java.io.File;
22: import java.io.IOException;
23: import java.io.UnsupportedEncodingException;
24: import java.net.URI;
25: import java.net.URISyntaxException;
26: import java.net.URL;
27: import java.net.URLDecoder;
28: import java.nio.charset.StandardCharsets;
29: import java.util.*;
30: import java.util.function.Consumer;
31: import java.util.jar.JarEntry;
32: import java.util.jar.JarFile;
33:
34: /**
35: * Processes classes available to the current classloader.
36: */
37: public class DefaultClasspathScanner implements ClasspathScanner {
38:
39: private static final Logger LOG = LoggerFactory.getLogger(DefaultClasspathScanner.class);
40:
41: protected static final char JAVA_CLASSPATH_SEPARATOR = '/';
42: protected static final char WINDOWS_FILE_SEPARATOR = '\\';
43: protected static final char JAVA_PACKAGE_SEPARATOR = '.';
44: protected static final String JAR_FILE_SUFFIX = ".jar";
45: protected static final String CLASS_FILE_SUFFIX = ".class";
46:
47: protected final List<Consumer<Class<?>>> listeners = new ArrayList<>();
48:
49: protected final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
50:
51: protected String pathPattern;
52: protected Set<URL> visited;
53:
54: @Override
55: public void addListener(Consumer<Class<?>> listener) {
56: listeners.add(listener);
57: }
58:
59: /**
60: * Inspired by https://github.com/ddopson/java-class-enumerator
61: */
62: @Override
63: public void processClasses(String scanPackage) {
64: this.pathPattern = scanPackage.replace(JAVA_PACKAGE_SEPARATOR, JAVA_CLASSPATH_SEPARATOR);
65: this.visited = new HashSet<>();
66: try {
67: Enumeration<URL> urls = classLoader.getResources(pathPattern);
68: processElements(urls, scanPackage);
69: // Scan jar files on classpath
70: Enumeration<URL> resources = classLoader.getResources(".");
71: processElements(resources, scanPackage);
72: } catch (IOException e) {
73: throw new OWLPersistenceException("Unable to scan packages for entity classes.", e);
74: }
75: }
76:
77: protected void processElements(Enumeration<URL> urls, String scanPath) throws IOException {
78:• while (urls.hasMoreElements()) {
79: final URL url = urls.nextElement();
80:• if (visited.contains(url)) {
81: continue;
82: }
83: visited.add(url);
84: LOG.trace("Processing classpath element {}", url);
85:• if (isJar(url.toString())) {
86: processJarFile(createJarFile(url));
87: } else {
88: processDirectory(new File(getUrlAsUri(url).getPath()), scanPath);
89: }
90: }
91: }
92:
93: /**
94: * Handles possible non-ascii character encoding in the specified URL.
95: *
96: * @param url Resource URL (presumably leading to a local file)
97: * @return Decoded argument
98: * @throws UnsupportedEncodingException Should not happen, using standard UTF-8 encoding
99: */
100: protected static String sanitizePath(URL url) throws UnsupportedEncodingException {
101: return URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8.toString());
102: }
103:
104: protected static boolean isJar(String filePath) {
105:• return filePath.startsWith("jar:") || filePath.endsWith(JAR_FILE_SUFFIX);
106: }
107:
108: protected static JarFile createJarFile(URL elementUrl) throws IOException {
109: final String jarPath = sanitizePath(elementUrl).replaceFirst("[.]jar[!].*", JAR_FILE_SUFFIX)
110: .replaceFirst("file:", "");
111: return new JarFile(jarPath);
112: }
113:
114: protected static URI getUrlAsUri(URL url) {
115: try {
116: // Transformation to URI handles encoding, e.g. of whitespaces in the path
117: return url.toURI();
118: } catch (URISyntaxException ex) {
119: throw new OWLPersistenceException(
120: "Unable to scan resource " + url + ". It is not a valid URI.", ex);
121: }
122: }
123:
124: /**
125: * Processes the specified {@link JarFile}, looking for classes in the configured package.
126: *
127: * @param jarFile JAR file to scan
128: */
129: protected void processJarFile(final JarFile jarFile) {
130: LOG.trace("Scanning jar file {} for entity classes.", jarFile.getName());
131: try (final JarFile localFile = jarFile) {
132: final Enumeration<JarEntry> entries = localFile.entries();
133:• while (entries.hasMoreElements()) {
134: final JarEntry entry = entries.nextElement();
135: final String entryName = entry.getName();
136:• if (entryName.endsWith(CLASS_FILE_SUFFIX) && entryName.contains(pathPattern)) {
137: String className = entryName.substring(entryName.indexOf(pathPattern));
138: className = className.replace(JAVA_CLASSPATH_SEPARATOR, JAVA_PACKAGE_SEPARATOR).replace(WINDOWS_FILE_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
139: className = className.substring(0, className.length() - CLASS_FILE_SUFFIX.length());
140: processClass(className);
141: }
142: }
143: } catch (IOException e) {
144: throw new OWLPersistenceException("Unexpected IOException reading JAR File " + jarFile, e);
145: }
146: }
147:
148: /**
149: * Retrieves a {@link Class} with the specified name and passes it to the registered listeners.
150: *
151: * @param className Fully-qualified class name
152: */
153: protected void processClass(String className) {
154: try {
155: final Class<?> cls = Class.forName(className, true, classLoader);
156: listeners.forEach(listener -> listener.accept(cls));
157: } catch (ClassNotFoundException e) {
158: throw new OWLPersistenceException("Unexpected ClassNotFoundException when scanning for entities.", e);
159: }
160: }
161:
162: /**
163: * Processes the specified directory, looking for classes in the specified package (and its descendants).
164: *
165: * @param dir Directory
166: * @param packageName Package name
167: */
168: protected void processDirectory(File dir, String packageName) throws IOException {
169:• if (!dir.getPath().replace(WINDOWS_FILE_SEPARATOR, JAVA_CLASSPATH_SEPARATOR).contains(pathPattern)) {
170: return;
171: }
172: LOG.trace("Scanning directory {} for entity classes.", dir);
173: // Get the list of the files contained in the package
174: final String[] files = dir.list();
175:• if (files == null) {
176: return;
177: }
178:• for (String fileName : files) {
179: String className = null;
180: // we are only interested in .class files
181:• if (fileName.endsWith(CLASS_FILE_SUFFIX)) {
182: // removes the .class extension
183: className = packageName + '.' + fileName.substring(0, fileName.length() - 6);
184: }
185:• if (className != null) {
186: processClass(className);
187: }
188: final File subDir = new File(dir, fileName);
189:• if (subDir.isDirectory()) {
190:• processDirectory(subDir, packageName + (!packageName.isEmpty() ? JAVA_PACKAGE_SEPARATOR : "") + fileName);
191:• } else if (isJar(subDir.getAbsolutePath())) {
192: processJarFile(createJarFile(subDir.toURI().toURL()));
193: }
194: }
195: }
196: }