1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.tinyjee.maven.dim.spi;
18
19 import org.apache.maven.doxia.logging.Log;
20 import org.codehaus.plexus.util.StringUtils;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.lang.ref.SoftReference;
25 import java.net.*;
26 import java.util.*;
27 import java.util.regex.Pattern;
28
29 import static java.lang.String.valueOf;
30 import static java.util.Arrays.asList;
31
32
33
34
35
36
37
38 public final class ResourceResolver {
39
40 private static String currentClassLoaderKey;
41 private static SoftReference<URLClassLoader> currentClassLoader = new SoftReference<URLClassLoader>(null);
42
43 static final Pattern SPLITTER = Pattern.compile("\\s*[,;]+\\s*", Pattern.MULTILINE);
44
45
46
47
48
49
50
51
52 static final String[] DEFAULT_SITE_SOURCE_SEARCH_PATH = SPLITTER.split(System.getProperty(
53 "org.tinyjee.maven.dim.siteSourceSearchPath", "" +
54 "src/site, " +
55 "src/site/resources, " +
56 "site, " +
57 "site/resources"));
58
59
60
61
62
63
64
65
66 static final String[] DEFAULT_SOURCE_SEARCH_PATH = SPLITTER.split(System.getProperty(
67 "org.tinyjee.maven.dim.sourceSearchPath", "" +
68 "src/main/java, " +
69 "src/main/resources, " +
70 "src/test/java, " +
71 "src/test/resources, " +
72 "src/main, " +
73 "src, " +
74 "target"));
75
76 static final String CLASSES_PATH = System.getProperty(
77 "org.tinyjee.maven.dim.include.classesPath",
78 "target/classes/");
79
80 static final String TEST_CLASSES_PATH = System.getProperty(
81 "org.tinyjee.maven.dim.include.testClassesPath",
82 "target/test-classes/");
83
84
85
86
87
88
89
90
91
92
93
94
95 public static void setModulePath(String groupId, String artifactId, File basePath) {
96
97
98 String absolutePath = basePath.getAbsolutePath();
99 System.setProperty("dim." + artifactId + ".basedir", absolutePath);
100 System.setProperty("dim." + groupId + '_' + artifactId + ".basedir", absolutePath);
101 }
102
103
104
105
106
107
108
109 public static String getModulePath(String moduleName) {
110 return System.getProperty("dim." + moduleName.replace(':', '_') + ".basedir");
111 }
112
113
114
115
116
117
118 public static Map<String, String> getModulePaths() {
119 Map<String, String> modulePaths = new TreeMap<String, String>();
120 for (Object entry : System.getProperties().entrySet()) {
121 String key = valueOf(((Map.Entry) entry).getKey()), value = valueOf(((Map.Entry) entry).getValue());
122 if (key.startsWith("dim.") && key.endsWith(".basedir"))
123 modulePaths.put('[' + key.substring(4, key.length() - 8).replace('_', ':') + ']', value);
124 }
125 return modulePaths;
126 }
127
128 static File[] canonicalizePath(File basePath, String path) {
129 File[] result = new File[1];
130 if (isAbsolute(path)) {
131 result[0] = new File(path);
132 } else {
133 String base;
134 try {
135 base = basePath.getCanonicalPath();
136 } catch (IOException e) {
137 base = basePath.getAbsolutePath();
138 }
139 result = resolveDefaultProjectPaths("siteDirectory", "src/site", base, path, result);
140 if (result[0] == null) result = resolveDefaultProjectPaths("sourceDirectory", "src/main/java", base, path, result);
141 if (result[0] == null) result = resolveDefaultProjectPaths("resourceDirectories", "src/main/resources", base, path, result);
142 if (result[0] == null) result = resolveDefaultProjectPaths("outputDirectory", "target/classes", base, path, result);
143 if (result[0] == null) result = resolveDefaultProjectPaths("testSourceDirectory", "src/test/java", base, path, result);
144 if (result[0] == null) result = resolveDefaultProjectPaths("testResourceDirectories", "src/test/resources", base, path, result);
145 if (result[0] == null) result = resolveDefaultProjectPaths("testOutputDirectory", "target/test-classes", base, path, result);
146 if (result[0] == null) result = resolveDefaultProjectPaths("targetDirectory", "target", base, path, result);
147 if (result[0] == null) result[0] = new File(basePath, path);
148 }
149
150 for (int i = 0; i < result.length; i++) {
151 try {
152 result[i] = result[i].getCanonicalFile();
153 } catch (IOException e) {
154 final Log log = Globals.getLog();
155 log.warn("Failed to retrieve the canonical path of " + result[i] + '.');
156 if (log.isDebugEnabled()) log.debug(e.getMessage(), e);
157 }
158 }
159 return result;
160 }
161
162 static File[] resolveDefaultProjectPaths(String projectPathKey, String projectPathPrefix,
163 String basePath, String path, File[] result) {
164 path = path.replace('\\', '/');
165 if (path.equals(projectPathPrefix) || path.startsWith(projectPathPrefix + '/')) {
166 final String projectPath = System.getProperty("org.tinyjee.maven.dim.project." + projectPathKey);
167 if (!StringUtils.isEmpty(projectPath)) {
168 final Log log = Globals.getLog();
169 final List<String> paths = new ArrayList<String>(asList(StringUtils.split(projectPath, File.pathSeparator)));
170 for (Iterator<String> iterator = paths.iterator(); iterator.hasNext(); ) {
171 final String pp = iterator.next();
172 if (!isPathBelowBase(basePath, pp)) {
173 if (log.isDebugEnabled()) {
174 log.debug("Ignoring project path '" + projectPathKey + ':' + pp + "' is not below the given " +
175 "search base path of '" + basePath + '\'');
176 }
177 iterator.remove();
178 }
179 }
180
181 if (!paths.isEmpty()) {
182 result = result.length == paths.size() ? result : new File[paths.size()];
183 for (int i = 0, len = paths.size(); i < len; i++)
184 result[i] = new File(paths.get(i) + File.separator + path.substring(projectPathPrefix.length()));
185 }
186 }
187 }
188
189 return result;
190 }
191
192 private static boolean isPathBelowBase(String basePath, String path) {
193 if (!isAbsolute(basePath)) return false;
194 if (!isAbsolute(path)) return false;
195 path = path.replace('\\', '/').trim();
196 basePath = basePath.replace('\\', '/').trim();
197 boolean caseSensitive = File.separatorChar == '/';
198 return caseSensitive ? path.startsWith(basePath) : path.toLowerCase().startsWith(basePath.toLowerCase());
199 }
200
201
202
203
204
205
206
207
208 public static List<File> buildDefaultSearchPaths(File basePath, String... proposedPaths) {
209 final List<File> searchPaths = new ArrayList<File>();
210 final File[] basePaths = {basePath, new File(".")};
211 final List<String[]> partialPaths = new ArrayList<String[]>();
212
213 if (proposedPaths != null && proposedPaths.length > 0) {
214 for (int i = 0; i < proposedPaths.length; i++)
215 proposedPaths[i] = toFilePath(proposedPaths[i]);
216 partialPaths.add(proposedPaths);
217 }
218
219 partialPaths.add(DEFAULT_SITE_SOURCE_SEARCH_PATH);
220 partialPaths.add(DEFAULT_SOURCE_SEARCH_PATH);
221 partialPaths.add(new String[]{""});
222
223 for (File path : basePaths) {
224 if (path == null)
225 continue;
226
227 for (String[] paths : partialPaths) {
228 for (String pp : paths) {
229 if (pp == null) continue;
230
231 for (File searchPath : canonicalizePath(path, pp)) {
232 if (searchPath.exists() && !searchPaths.contains(searchPath))
233 searchPaths.add(searchPath.getAbsoluteFile());
234 }
235 }
236 }
237 }
238
239 return searchPaths;
240 }
241
242
243
244
245
246
247
248 public static boolean isAbsolute(String filePath) {
249 if (filePath == null)
250 return false;
251 if (filePath.startsWith("/") || filePath.startsWith("\\"))
252 return true;
253
254 return File.pathSeparatorChar != ':' && filePath.length() > 1 && filePath.charAt(1) == ':';
255 }
256
257
258
259
260
261
262
263 public static String toFilePath(String source) {
264 if (source != null) {
265
266 source = source.replace('\\', '/');
267
268 if (!isAbsolute(source)) {
269
270 try {
271 URI sourceURI = new URI(source);
272 if (sourceURI.getScheme() != null) {
273 if ("file".equalsIgnoreCase(sourceURI.getScheme()))
274 source = sourceURI.getPath();
275 else
276 source = null;
277 }
278 } catch (URISyntaxException ignored) {
279 Globals.getLog().debug(source + " is not a file path, processing it as URL.");
280 }
281 }
282 }
283
284 return source;
285 }
286
287
288
289
290
291
292
293
294
295 public static void findMatchingPaths(List<File> searchPaths, String source, List<URL> results) {
296 source = toFilePath(source);
297 if (source == null) return;
298
299 for (File searchPath : searchPaths) {
300 File file = new File(searchPath, source);
301 if (file.exists())
302 try {
303 URL url = file.toURI().toURL();
304 if (!results.contains(url)) results.add(url);
305 } catch (MalformedURLException e) {
306 throw new RuntimeException(e);
307 }
308 }
309 }
310
311
312
313
314
315
316
317
318 public static void findMatchingURLs(File basePath, String source, List<URL> results) {
319 if (isAbsolute(source)) return;
320
321 try {
322 if (source.startsWith("[") && source.contains("]:")) {
323 String moduleName = source.substring(1, source.indexOf("]:"));
324 String path = getModulePath(moduleName);
325 if (path != null) {
326 final List<File> searchPaths = buildDefaultSearchPaths(new File(path));
327 findMatchingPaths(searchPaths, source.substring(moduleName.length() + 3), results);
328 assertHasResult(source, results, "below the paths", searchPaths);
329 } else {
330 assertHasResult(source, results, "below the module base paths", getModulePaths().entrySet());
331 }
332 } else {
333 URI sourceURI = new URI(source);
334 String scheme = sourceURI.getScheme();
335 if (scheme != null && !"file".equalsIgnoreCase(scheme)) {
336 if ("classpath".equalsIgnoreCase(scheme)) {
337 URLClassLoader ucl = resolveClassLoader(basePath);
338 String path = sourceURI.getPath();
339 if (path.startsWith("/")) path = path.substring(1);
340 for (Enumeration<URL> e = ucl.getResources(path); e.hasMoreElements(); ) {
341 URL url = e.nextElement();
342 if (!results.contains(url)) results.add(url);
343 }
344
345 assertHasResult(source, results,
346 "inside Maven's site building classpath nor below the paths", asList(ucl.getURLs()));
347 } else {
348 results.add(sourceURI.toURL());
349 }
350 }
351 }
352 } catch (RuntimeException e) {
353 throw e;
354 } catch (IOException e) {
355 Globals.getLog().debug(source + " points to an invalid or broken classpath.", e);
356 } catch (Exception ignored) {
357 Globals.getLog().debug(source + " is not an URI, not resolving it as URL.");
358 }
359 }
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374 public static URL findSource(File basePath, String source) {
375 final Log log = Globals.getLog();
376 final List<URL> results = findAllSources(basePath, source);
377
378 if (results.size() > 1) {
379 for (int i = results.size() - 1; i > 0; i--) {
380 URL url = results.get(i);
381 if ("file".equalsIgnoreCase(url.getProtocol())) {
382 url = results.set(0, url);
383 results.set(i, url);
384 log.info("Found multiple matching sources " + results + " for " + source + ", preferring the local file over others.");
385 }
386 }
387 log.warn("Found matching sources @ " + results + " for " + source + ", using the first match.");
388 } else if (log.isDebugEnabled())
389 log.debug("Found matching source @ " + results + " for " + source + '.');
390
391 return results.get(0);
392 }
393
394
395
396
397
398
399
400
401 public static List<URL> findAllSources(File basePath, String source) {
402 final List<URL> results = new ArrayList<URL>();
403
404 findMatchingURLs(basePath, source, results);
405
406 if (results.isEmpty()) {
407 List<File> searchPaths = buildDefaultSearchPaths(basePath);
408 findMatchingPaths(searchPaths, source, results);
409 assertHasResult(source, results, "below the paths", searchPaths);
410 }
411
412 return results;
413 }
414
415 private static void assertHasResult(String source, List<URL> results, String where, Collection<?> searchPaths) {
416 if (results.isEmpty()) {
417 final String separator = System.getProperty("line.separator");
418 final StringBuilder builder = new StringBuilder().append("Didn't find ");
419
420 builder.append('\'').append(source).append("' ").append(where).append(": ");
421 for (Object path : searchPaths) builder.append(separator).append("-- ").append(path);
422
423 throw new IllegalArgumentException(builder.toString());
424 }
425 }
426
427
428
429
430
431
432
433
434
435 public static Class<?> resolveClass(File basePath, String className) {
436 try {
437 return Class.forName(className);
438 } catch (ClassNotFoundException ignored) {
439 final URLClassLoader loader = resolveClassLoader(basePath);
440 try {
441 return Class.forName(className, true, loader);
442 } catch (ClassNotFoundException e1) {
443 throw new IllegalArgumentException("Didn't find the source class '" + className +
444 "' inside Maven's site building classpath nor below the paths: " +
445 asList(loader.getURLs()), e1);
446 }
447 }
448 }
449
450
451
452
453
454
455
456
457 public static synchronized URLClassLoader resolveClassLoader(File basePath) {
458 if (basePath == null) return (URLClassLoader) ResourceResolver.class.getClassLoader();
459
460
461
462 final String clKey = basePath.getAbsolutePath();
463 URLClassLoader ulc = currentClassLoader.get();
464 if (ulc == null || currentClassLoaderKey == null || !currentClassLoaderKey.equals(clKey)) {
465 try {
466 Set<String> classpath = new LinkedHashSet<String>();
467 classpath.add(System.getProperty("org.tinyjee.maven.dim.project.outputDirectory",
468 new File(basePath, CLASSES_PATH).getAbsolutePath()));
469 classpath.add(System.getProperty("org.tinyjee.maven.dim.project.testOutputDirectory",
470 new File(basePath, TEST_CLASSES_PATH).getAbsolutePath()));
471
472 String projectClassPath = System.getProperty("org.tinyjee.maven.dim.include.project.classpath");
473 if (projectClassPath != null)
474 Collections.addAll(classpath, StringUtils.split(projectClassPath, File.pathSeparator));
475
476 String projectTestClassPath = System.getProperty("org.tinyjee.maven.dim.include.project.test.classpath");
477 if (projectTestClassPath != null)
478 Collections.addAll(classpath, StringUtils.split(projectTestClassPath, File.pathSeparator));
479
480 Globals.getLog().debug("Creating a new classloader to load classes via 'source-class', using the classpath:" + classpath);
481
482 int i = 0;
483 URL[] urls = new URL[classpath.size()];
484 for (String file : classpath) urls[i++] = new File(file).toURI().toURL();
485 ulc = new URLClassLoader(urls, ResourceResolver.class.getClassLoader());
486 currentClassLoader = new SoftReference<URLClassLoader>(ulc);
487 currentClassLoaderKey = clKey;
488 } catch (Exception e) {
489 throw new RuntimeException("Failed to build search path to lookup .class files.", e);
490 }
491 }
492
493 return ulc;
494 }
495
496
497
498
499
500
501
502
503 public static File findSiteSourceDirectory(File baseDir, File proposedSiteDir) {
504 final Log log = Globals.getLog();
505 final List<File> paths = buildDefaultSearchPaths(baseDir, proposedSiteDir == null ? null : proposedSiteDir.getAbsolutePath());
506
507 for (File file : paths) {
508 if ("site".equals(file.getName())) {
509 log.debug("Using '" + file + "/resources', to append CSS style sheets.");
510 return file;
511 }
512 }
513
514 log.debug("Didn't find site directory below " + paths + ", will inline CSS styles.");
515 return null;
516 }
517
518 private ResourceResolver() {
519 }
520 }