/
Classpath.java
165 lines (152 loc) · 5.51 KB
/
Classpath.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - info@scireum.de
*/
package sirius.kernel;
import sirius.kernel.commons.Strings;
import sirius.kernel.health.Log;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
/**
* Retrieves a filtered list of resources in the classpath.
* <p>
* This is used by the {@link sirius.kernel.di.Injector} to discover and register all classes in the
* component model. Additionally {@link sirius.kernel.nls.Babelfish} uses this to load all relevant .properties files.
* <p>
* The method used, is to provide a name of a resource which is placed in every component root (jar file etc.) which
* then can be discovered using <tt>Class.getResources</tt>.
* <p>
* Once a file pattern is given, all files in the classpath are scanned, starting from the detected roots.
*/
public class Classpath {
/**
* Logger used to log problems when scanning the classpath
*/
protected static final Log LOG = Log.get("classpath");
private List<URL> componentRoots;
private final ClassLoader loader;
private final String componentName;
private final List<String> customizations;
/**
* Creates a new Classpath, scanning for component roots with the given name
*
* @param loader the class loader used to load the components
* @param componentName the file name to identify component roots
* @param customizations the list of active customizations to filter visible resources
*/
public Classpath(ClassLoader loader, String componentName, List<String> customizations) {
this.loader = loader;
this.componentName = componentName;
this.customizations = customizations;
}
/**
* Returns the class loader used to load the classpath
*
* @return the class loader used by the framework
*/
public ClassLoader getLoader() {
return loader;
}
/**
* Returns all detected component roots
*
* @return a list of URLs pointing to the component roots
*/
public List<URL> getComponentRoots() {
if (componentRoots == null) {
try {
componentRoots = Collections.list(loader.getResources(componentName));
componentRoots.sort(Comparator.comparing(URL::toString));
} catch (IOException exception) {
LOG.SEVERE(exception);
}
}
return componentRoots;
}
/**
* Scans the classpath for files which relative path match the given patter
*
* @param pattern the pattern for the relative path used to filter files
* @return a stream of matching elements
*/
public Stream<Matcher> find(final Pattern pattern) {
return getComponentRoots().stream().flatMap(this::scan).filter(path -> {
if (customizations != null && path.startsWith("customizations")) {
String config = Sirius.getCustomizationName(path);
return customizations.contains(config);
}
return true;
}).map(pattern::matcher).filter(Matcher::matches);
}
/*
* Scans all files below the given root URL. This can handle file:// and jar:// URLs
*/
private Stream<String> scan(URL url) {
List<String> result = new ArrayList<>();
if ("file".equals(url.getProtocol())) {
try {
File file = new File(url.toURI().getPath());
if (!file.isDirectory()) {
file = file.getParentFile();
}
addFiles(file, result, file);
} catch (URISyntaxException exception) {
LOG.SEVERE(exception);
}
} else if ("jar".equals(url.getProtocol())) {
try {
JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> enumeration = jar.entries();
while (enumeration.hasMoreElements()) {
JarEntry entry = enumeration.nextElement();
result.add(entry.getName());
}
} catch (IOException exception) {
LOG.SEVERE(exception);
}
}
return result.stream();
}
/*
* DFS searcher for file / directory based classpath elements
*/
private void addFiles(File file, List<String> collector, File reference) {
if (!file.exists() || !file.isDirectory()) {
return;
}
for (File child : file.listFiles()) {
if (child.isDirectory()) {
addFiles(child, collector, reference);
} else {
String path = buildRelativePath(reference, child);
collector.add(path);
}
}
}
private String buildRelativePath(File reference, File child) {
List<String> path = new ArrayList<>();
File iter = child;
while (iter != null && !Objects.equals(iter, reference)) {
path.addFirst(iter.getName());
iter = iter.getParentFile();
}
return Strings.join(path, "/");
}
}