blob: f33e7224695d3761cbe26bd4ab4b8fe6522cec99 [file] [log] [blame]
Stuart McCullochf29ef3f2008-12-04 07:58:07 +00001/* Copyright 2006 aQute SARL
2 * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
3package aQute.lib.osgi;
4
5import java.io.*;
6import java.net.*;
7import java.util.*;
8import java.util.jar.*;
9
10import aQute.bnd.make.*;
11import aQute.bnd.service.*;
12import aQute.libg.header.*;
13import aQute.libg.reporter.*;
14
15public class Processor implements Reporter, Constants, Closeable {
16 // TODO handle include files out of date
17 public static String DEFAULT_PLUGINS = ""; // "aQute.lib.spring.SpringComponent";
18 // TODO make splitter skip eagerly whitespace so trim is not necessary
19 public static String LIST_SPLITTER = "\\\\?\\s*,\\s*";
20 private List<String> errors = new ArrayList<String>();
21 private List<String> warnings = new ArrayList<String>();
22 boolean pedantic;
23 boolean trace;
24 boolean exceptions;
25 boolean fileMustExist = true;
26
27 List<Object> plugins;
28 private File base = new File("").getAbsoluteFile();
29 private List<Closeable> toBeClosed = newList();
30
31 final Properties properties;
32 private Macro replacer;
33 private long lastModified;
34 private File propertiesFile;
35 private boolean fixup = true;
36 long modified;
37 Processor parent;
38 Set<File> included;
39 CL pluginLoader;
40
41 public Processor() {
42 properties = new Properties();
43 }
44
45 public Processor(Properties parent) {
46 properties = new Properties(parent);
47 }
48
49 public Processor(Processor parent) {
50 this(parent.properties);
51 this.parent = parent;
52 }
53
54 public void setParent(Processor processor) {
55 this.parent = processor;
56 }
57
58 public Processor getParent() {
59 return parent;
60 }
61
62 public void getInfo(Processor processor, String prefix) {
63 if (isFailOk())
64 addAll(warnings, processor.getErrors(), prefix);
65 else
66 addAll(errors, processor.getErrors(), prefix);
67 addAll(warnings, processor.getWarnings(), prefix);
68
69 processor.errors.clear();
70 processor.warnings.clear();
71 }
72
73 public void getInfo(Processor processor) {
74 getInfo(processor, "");
75 }
76
77 private <T> void addAll(List<String> to, List<? extends T> from,
78 String prefix) {
79 for (T x : from) {
80 to.add(prefix + x);
81 }
82 }
83
84 public void warning(String string, Object ...args) {
85 String s = String.format(string,args);
86 if ( ! warnings.contains(s))
87 warnings.add(s);
88 }
89
90 public void error(String string, Object ...args) {
91 if (isFailOk())
92 warning(string, args);
93 else {
94 String s = String.format(string,args);
95 if ( ! errors.contains(s))
96 errors.add(s);
97 }
98 }
99
100 public void error(String string, Throwable t, Object ... args) {
101 if (isFailOk())
102 warning(string + ": " + t, args);
103 else{
104 String s = String.format(string,args);
105 if ( ! errors.contains(s))
106 errors.add(s);
107 }
108 if (exceptions)
109 t.printStackTrace();
110 }
111
112 public List<String> getWarnings() {
113 return warnings;
114 }
115
116 public List<String> getErrors() {
117 return errors;
118 }
119
120 public Map<String, Map<String, String>> parseHeader(String value) {
121 return parseHeader(value, this);
122 }
123
124 /**
125 * Standard OSGi header parser.
126 *
127 * @param value
128 * @return
129 */
130 @SuppressWarnings("unchecked")
131 static public Map<String, Map<String, String>> parseHeader(String value,
132 Processor logger) {
133 return OSGiHeader.parseHeader(value, logger);
134 }
135
136 Map<String, Map<String, String>> getClauses(String header) {
137 return parseHeader(getProperty(header));
138 }
139
140 public void addClose(Closeable jar) {
141 toBeClosed.add(jar);
142 }
143
144 /**
145 * Remove all entries from a map that start with a specific prefix
146 *
147 * @param <T>
148 * @param source
149 * @param prefix
150 * @return
151 */
152 static <T> Map<String, T> removeKeys(Map<String, T> source, String prefix) {
153 Map<String, T> temp = new TreeMap<String, T>(source);
154 for (Iterator<String> p = temp.keySet().iterator(); p.hasNext();) {
155 String pack = (String) p.next();
156 if (pack.startsWith(prefix))
157 p.remove();
158 }
159 return temp;
160 }
161
162 public void progress(String s, Object ... args) {
163 // System.out.println(s);
164 }
165
166 public boolean isPedantic() {
167 return pedantic;
168 }
169
170 public void setPedantic(boolean pedantic) { // System.out.println("Set
171 // pedantic: " + pedantic + " "
172 // + this );
173 this.pedantic = pedantic;
174 }
175
176 public static File getFile(File base, String file) {
177 File f = new File(file);
178 if (f.isAbsolute())
179 return f;
180 int n;
181
182 f = base.getAbsoluteFile();
183 while ((n = file.indexOf('/')) > 0) {
184 String first = file.substring(0, n);
185 file = file.substring(n + 1);
186 if (first.equals(".."))
187 f = f.getParentFile();
188 else
189 f = new File(f, first);
190 }
191 return new File(f, file).getAbsoluteFile();
192 }
193
194 public File getFile(String file) {
195 return getFile(base, file);
196 }
197
198 /**
199 * Return a list of plugins that implement the given class.
200 *
201 * @param clazz
202 * Each returned plugin implements this class/interface
203 * @return A list of plugins
204 */
205 public <T> List<T> getPlugins(Class<T> clazz) {
206 List<T> l = new ArrayList<T>();
207 List<Object> all = getPlugins();
208 for (Object plugin : all) {
209 if (clazz.isInstance(plugin))
210 l.add(clazz.cast(plugin));
211 }
212 return l;
213 }
214
215 /**
216 * Return a list of plugins. Plugins are defined with the -plugin command.
217 * They are class names, optionally associated with attributes. Plugins can
218 * implement the Plugin interface to see these attributes.
219 *
220 * Any object can be a plugin.
221 *
222 * @return
223 */
224 public List<Object> getPlugins() {
225 if (this.plugins != null)
226 return this.plugins;
227
228 String spe = getProperty(Analyzer.PLUGIN, DEFAULT_PLUGINS);
229 Map<String, Map<String, String>> plugins = parseHeader(spe);
230 List<Object> list = new ArrayList<Object>();
231
232 // Add the default plugins. Only if non is specified
233 // will they be removed.
234 list.add(new MakeBnd());
235 list.add(new MakeCopy());
236
237 for (Map.Entry<String, Map<String, String>> entry : plugins.entrySet()) {
238 String key = (String) entry.getKey();
239 if (key.equals("none"))
240 return this.plugins = newList();
241
242 try {
243 CL loader = getLoader();
244 String path = entry.getValue().get("path:");
245 if ( path != null ) {
246 File f = getFile(path).getAbsoluteFile();
247 loader.add(f.toURL());
248 }
249
250 trace("Using plugin %s", key);
251
252 // Plugins could use the same class with different
253 // parameters so we could have duplicate names Remove
254 // the ! added by the parser to make each name unique.
255 key = removeDuplicateMarker(key);
256
257 Class<?> c = (Class<?>) loader.loadClass(
258 key);
259 Object plugin = c.newInstance();
260 if (plugin instanceof Plugin) {
261 ((Plugin) plugin).setProperties(entry.getValue());
262 ((Plugin) plugin).setReporter(this);
263 }
264 list.add(plugin);
265 } catch (Exception e) {
266 error("Problem loading the plugin: " + key + " exception: " + e);
267 }
268 }
269 return this.plugins = list;
270 }
271
272 public boolean isFailOk() {
273 String v = getProperty(Analyzer.FAIL_OK, null);
274 return v != null && v.equalsIgnoreCase("true");
275 }
276
277 public File getBase() {
278 return base;
279 }
280
281 public void setBase(File base) {
282 this.base = base;
283 }
284
285 public void clear() {
286 errors.clear();
287 warnings.clear();
288 }
289
290 public void trace(String msg, Object... parms) {
291 if (trace) {
292 System.out.printf("# " + msg + "\n", parms);
293 }
294 }
295
296 public <T> List<T> newList() {
297 return new ArrayList<T>();
298 }
299
300 public <T> Set<T> newSet() {
301 return new TreeSet<T>();
302 }
303
304 public static <K, V> Map<K, V> newMap() {
305 return new LinkedHashMap<K, V>();
306 }
307
308 public static <K, V> Map<K, V> newHashMap() {
309 return new HashMap<K, V>();
310 }
311
312 public <T> List<T> newList(Collection<T> t) {
313 return new ArrayList<T>(t);
314 }
315
316 public <T> Set<T> newSet(Collection<T> t) {
317 return new TreeSet<T>(t);
318 }
319
320 public <K, V> Map<K, V> newMap(Map<K, V> t) {
321 return new LinkedHashMap<K, V>(t);
322 }
323
324 public void close() {
325 for (Closeable c : toBeClosed) {
326 try {
327 c.close();
328 } catch (IOException e) {
329 // Who cares?
330 }
331 }
332 toBeClosed = null;
333 }
334
335 public String _basedir(String args[]) {
336 if (base == null)
337 throw new IllegalArgumentException("No base dir set");
338
339 return base.getAbsolutePath();
340 }
341
342 /**
343 * Property handling ...
344 *
345 * @return
346 */
347
348 public Properties getProperties() {
349 if (fixup) {
350 fixup = false;
351 begin();
352 }
353
354 return properties;
355 }
356
357 public String getProperty(String key) {
358 return getProperty(key, null);
359 }
360
361 public void mergeProperties(File file, boolean override) {
362 if (file.isFile()) {
363 try {
364 Properties properties = loadProperties(file);
365 mergeProperties(properties, override);
366 } catch (Exception e) {
367 error("Error loading properties file: " + file);
368 }
369 } else {
370 if (!file.exists())
371 error("Properties file does not exist: " + file);
372 else
373 error("Properties file must a file, not a directory: " + file);
374 }
375 }
376
377 public void mergeProperties(Properties properties, boolean override) {
378 for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
379 String key = (String) e.nextElement();
380 String value = properties.getProperty(key);
381 if (override || !getProperties().containsKey(key))
382 setProperty(key, value);
383 }
384 }
385
386 public void setProperties(Properties properties) {
387 doIncludes(getBase(), properties, new HashSet<String>());
388 this.properties.putAll(properties);
389 }
390
391 public void addProperties(File file) throws Exception {
392 addIncluded(file);
393 Properties p = loadProperties(file);
394 setProperties(p);
395 }
396
397 public synchronized void addIncluded(File file) {
398 if (included == null)
399 included = new HashSet<File>();
400 included.add(file);
401 }
402
403 /**
404 * Inspect the properties and if you find -includes parse the line included
405 * manifest files or properties files. The files are relative from the given
406 * base, this is normally the base for the analyzer.
407 *
408 * @param ubase
409 * @param p
410 * @param done
411 * @throws IOException
412 */
413 private void doIncludes(File ubase, Properties p, Set<String> done) {
414 String includes = p.getProperty(INCLUDE);
415 if (includes != null) {
416 includes = getReplacer().process(includes);
417 p.remove(INCLUDE);
418 Collection<String> clauses = parseHeader(includes).keySet();
419
420 for (String value : clauses) {
421 boolean fileMustExist = true;
422 boolean overwrite = true;
423 while (true) {
424 if (value.startsWith("-")) {
425 fileMustExist = false;
426 value = value.substring(1).trim();
427 } else if (value.startsWith("~")) {
428 // Overwrite properties!
429 overwrite = false;
430 value = value.substring(1).trim();
431 } else
432 break;
433 }
434 try {
435 File file = getFile(ubase, value).getAbsoluteFile();
436 if (file.isFile()) {
437 if (included != null && included.contains(file)) {
438 error("Cyclic include of " + file);
439 } else {
440 addIncluded(file);
441 updateModified(file.lastModified(), "Include "
442 + value);
443 InputStream in = new FileInputStream(file);
444 Properties sub;
445 if (file.getName().toLowerCase().endsWith(".mf")) {
446 sub = getManifestAsProperties(in);
447 } else
448 sub = loadProperties(in, file.getAbsolutePath());
449 in.close();
450
451 doIncludes(file.getParentFile(), sub, done);
452 // make sure we do not override properties
453 if (!overwrite)
454 sub.keySet().removeAll(p.keySet());
455 p.putAll(sub);
456 }
457 } else {
458 if (fileMustExist)
459 error("Included file "
460 + file
461 + (file.exists() ? " does not exist"
462 : " is directory"));
463 }
464 } catch (IOException e) {
465 if (fileMustExist)
466 error("Error in processing included file: " + value, e);
467 }
468 }
469 }
470 }
471
472 public void unsetProperty(String string) {
473 getProperties().remove(string);
474
475 }
476
477 public boolean refresh() {
478 if (propertiesFile == null)
479 return false;
480
481 boolean changed = false;
482 if (included != null) {
483 for (File file : included) {
484
485 if (file.lastModified() > modified) {
486 changed = true;
487 break;
488 }
489 }
490 }
491
492 // System.out.println("Modified " + modified + " file: "
493 // + propertiesFile.lastModified() + " diff "
494 // + (modified - propertiesFile.lastModified()));
495
496 changed |= modified < propertiesFile.lastModified();
497 if (changed) {
498 included = null;
499 properties.clear();
500 setProperties(propertiesFile, base);
501 propertiesChanged();
502 return true;
503 }
504 return false;
505 }
506
507 public void propertiesChanged() {
508 plugins = null;
509 }
510
511 /**
512 * Set the properties by file. Setting the properties this way will also set
513 * the base for this analyzer. After reading the properties, this will call
514 * setProperties(Properties) which will handle the includes.
515 *
516 * @param propertiesFile
517 * @throws FileNotFoundException
518 * @throws IOException
519 */
520 public void setProperties(File propertiesFile) throws IOException {
521 propertiesFile = propertiesFile.getAbsoluteFile();
522 setProperties(propertiesFile, propertiesFile.getParentFile());
523 }
524
525 public void setProperties(File propertiesFile, File base) {
526 this.propertiesFile = propertiesFile.getAbsoluteFile();
527 setBase(base);
528 try {
529 if (propertiesFile.isFile()) {
530 // System.out.println("Loading properties " + propertiesFile);
531 modified = propertiesFile.lastModified();
532 included = null;
533 Properties p = loadProperties(propertiesFile);
534 setProperties(p);
535 } else {
536 if (fileMustExist) {
537 error("No such properties file: " + propertiesFile);
538 }
539 }
540 } catch (IOException e) {
541 error("Could not load properties " + propertiesFile);
542 }
543 }
544
545 protected void begin() {
546 if (isTrue(getProperty(PEDANTIC)))
547 setPedantic(true);
548 }
549
550 public static boolean isTrue(String value) {
551 return "true".equalsIgnoreCase(value);
552 }
553
554 /**
555 * Get a property with a proper default
556 *
557 * @param headerName
558 * @param deflt
559 * @return
560 */
561 public String getProperty(String key, String deflt) {
562 String value = getProperties().getProperty(key);
563 if (value != null)
564 return getReplacer().process(value);
565 else if (deflt != null)
566 return getReplacer().process(deflt);
567 else
568 return null;
569 }
570
571 /**
572 * Helper to load a properties file from disk.
573 *
574 * @param file
575 * @return
576 * @throws IOException
577 */
578 public Properties loadProperties(File file) throws IOException {
579 updateModified(file.lastModified(), "Properties file: " + file);
580 InputStream in = new FileInputStream(file);
581 Properties p = loadProperties(in, file.getAbsolutePath());
582 in.close();
583 return p;
584 }
585
586 Properties loadProperties(InputStream in, String name) throws IOException {
587 int n = name.lastIndexOf('/');
588 if (n > 0)
589 name = name.substring(0, n);
590 if (name.length() == 0)
591 name = ".";
592
593 try {
594 Properties p = new Properties();
595 p.load(in);
596 return replaceAll(p, "\\$\\{\\.\\}", name);
597 } catch (Exception e) {
598 error("Error during loading properties file: " + name + ", error:"
599 + e);
600 return new Properties();
601 }
602 }
603
604 /**
605 * Replace a string in all the values of the map. This can be used to
606 * preassign variables that change. I.e. the base directory ${.} for a
607 * loaded properties
608 */
609
610 public static Properties replaceAll(Properties p, String pattern,
611 String replacement) {
612 Properties result = new Properties();
613 for (Iterator<Map.Entry<Object, Object>> i = p.entrySet().iterator(); i
614 .hasNext();) {
615 Map.Entry<Object, Object> entry = i.next();
616 String key = (String) entry.getKey();
617 String value = (String) entry.getValue();
618 value = value.replaceAll(pattern, replacement);
619 result.put(key, value);
620 }
621 return result;
622 }
623
624 /**
625 * Merge the attributes of two maps, where the first map can contain
626 * wildcarded names. The idea is that the first map contains patterns (for
627 * example *) with a set of attributes. These patterns are matched against
628 * the found packages in actual. If they match, the result is set with the
629 * merged set of attributes. It is expected that the instructions are
630 * ordered so that the instructor can define which pattern matches first.
631 * Attributes in the instructions override any attributes from the actual.<br/>
632 *
633 * A pattern is a modified regexp so it looks like globbing. The * becomes a .*
634 * just like the ? becomes a .?. '.' are replaced with \\. Additionally, if
635 * the pattern starts with an exclamation mark, it will remove that matches
636 * for that pattern (- the !) from the working set. So the following
637 * patterns should work:
638 * <ul>
639 * <li>com.foo.bar</li>
640 * <li>com.foo.*</li>
641 * <li>com.foo.???</li>
642 * <li>com.*.[^b][^a][^r]</li>
643 * <li>!com.foo.* (throws away any match for com.foo.*)</li>
644 * </ul>
645 * Enough rope to hang the average developer I would say.
646 *
647 *
648 * @param instructions
649 * the instructions with patterns. A
650 * @param actual
651 * the actual found packages
652 */
653
654 public static Map<String, Map<String, String>> merge(String type,
655 Map<String, Map<String, String>> instructions,
656 Map<String, Map<String, String>> actual, Set<String> superfluous) {
657 Map<String, Map<String, String>> ignored = newMap();
658 Map<String, Map<String, String>> toVisit = new HashMap<String, Map<String, String>>(
659 actual); // we do not want to ruin our
660 // original
661 Map<String, Map<String, String>> result = newMap();
662 for (Iterator<String> i = instructions.keySet().iterator(); i.hasNext();) {
663 String instruction = i.next();
664 String originalInstruction = instruction;
665
666 Map<String, String> instructedAttributes = instructions
667 .get(instruction);
668
669 // Check if we have a fixed (starts with '=') or a
670 // duplicate name. A fixed name is added to the output without
671 // checking against the contents. Duplicates are marked
672 // at the end. In that case we do not pick up any contained
673 // information but just add them to the output including the
674 // marker.
675 if (instruction.startsWith("=")) {
676 result.put(instruction.substring(1), instructedAttributes);
677 superfluous.remove(originalInstruction);
678 continue;
679 }
680 if (isDuplicate(instruction)) {
681 result.put(instruction, instructedAttributes);
682 superfluous.remove(originalInstruction);
683 continue;
684 }
685
686 Instruction instr = Instruction.getPattern(instruction);
687
688 for (Iterator<String> p = toVisit.keySet().iterator(); p.hasNext();) {
689 String packageName = p.next();
690
691 if (instr.matches(packageName)) {
692 superfluous.remove(originalInstruction);
693 if (!instr.isNegated()) {
694 Map<String, String> newAttributes = new HashMap<String, String>();
695 newAttributes.putAll(actual.get(packageName));
696 newAttributes.putAll(instructedAttributes);
697 result.put(packageName, newAttributes);
698 } else {
699 ignored.put(packageName, new HashMap<String, String>());
700 }
701 p.remove(); // Can never match again for another pattern
702 }
703 }
704
705 }
706 return result;
707 }
708
709 /**
710 * Print a standard Map based OSGi header.
711 *
712 * @param exports
713 * map { name => Map { attribute|directive => value } }
714 * @return the clauses
715 */
716 public static String printClauses(Map<String, Map<String, String>> exports,
717 String allowedDirectives) {
718 return printClauses(exports, allowedDirectives, false);
719 }
720
721 public static String printClauses(Map<String, Map<String, String>> exports,
722 String allowedDirectives, boolean checkMultipleVersions) {
723 StringBuffer sb = new StringBuffer();
724 String del = "";
725 for (Iterator<String> i = exports.keySet().iterator(); i.hasNext();) {
726 String name = i.next();
727 Map<String, String> clause = exports.get(name);
728
729 // We allow names to be duplicated in the input
730 // by ending them with '~'. This is necessary to use
731 // the package names as keys. However, we remove these
732 // suffixes in the output so that you can set multiple
733 // exports with different attributes.
734 String outname = removeDuplicateMarker(name);
735 sb.append(del);
736 sb.append(outname);
737 printClause(clause, allowedDirectives, sb);
738 del = ",";
739 }
740 return sb.toString();
741 }
742
743 public static void printClause(Map<String, String> map,
744 String allowedDirectives, StringBuffer sb) {
745
746 for (Iterator<String> j = map.keySet().iterator(); j.hasNext();) {
747 String key = j.next();
748
749 // Skip directives we do not recognize
750 if (!key.startsWith("x-")
751 && key.endsWith(":")
752 && (allowedDirectives == null || allowedDirectives
753 .indexOf(key) < 0))
754 continue;
755
756 String value = ((String) map.get(key)).trim();
757 sb.append(";");
758 sb.append(key);
759 sb.append("=");
760
761 boolean clean = (value.length() >= 2 && value.charAt(0) == '"' && value
762 .charAt(value.length() - 1) == '"')
763 || Verifier.TOKEN.matcher(value).matches();
764 if (!clean)
765 sb.append("\"");
766 sb.append(value);
767 if (!clean)
768 sb.append("\"");
769 }
770 }
771
772 public Macro getReplacer() {
773 if (replacer == null)
774 return replacer = new Macro(getProperties(), this,
775 getMacroDomains());
776 else
777 return replacer;
778 }
779
780 /**
781 * This should be overridden by subclasses to add extra macro command
782 * domains on the search list.
783 *
784 * @return
785 */
786 protected Object[] getMacroDomains() {
787 return new Object[] {};
788 }
789
790 /**
791 * Return the properties but expand all macros. This always returns a new
792 * Properties object that can be used in any way.
793 *
794 * @return
795 */
796 public Properties getFlattenedProperties() {
797 return getReplacer().getFlattenedProperties();
798
799 }
800
801 public void updateModified(long time, String reason) {
802 if (time > lastModified) {
803 lastModified = time;
804 }
805 }
806
807 public long lastModified() {
808 return lastModified;
809 }
810
811 /**
812 * Add or override a new property.
813 *
814 * @param key
815 * @param value
816 */
817 public void setProperty(String key, String value) {
818 checkheader: for (int i = 0; i < headers.length; i++) {
819 if (headers[i].equalsIgnoreCase(value)) {
820 value = headers[i];
821 break checkheader;
822 }
823 }
824 getProperties().put(key, value);
825 }
826
827 /**
828 * Read a manifest but return a properties object.
829 *
830 * @param in
831 * @return
832 * @throws IOException
833 */
834 public static Properties getManifestAsProperties(InputStream in)
835 throws IOException {
836 Properties p = new Properties();
837 Manifest manifest = new Manifest(in);
838 for (Iterator<Object> it = manifest.getMainAttributes().keySet()
839 .iterator(); it.hasNext();) {
840 Attributes.Name key = (Attributes.Name) it.next();
841 String value = manifest.getMainAttributes().getValue(key);
842 p.put(key.toString(), value);
843 }
844 return p;
845 }
846
847 public File getPropertiesFile() {
848 return propertiesFile;
849 }
850
851 public void setFileMustExist(boolean mustexist) {
852 fileMustExist = mustexist;
853 }
854
855 static public String read(InputStream in) throws Exception {
856 InputStreamReader ir = new InputStreamReader(in);
857 StringBuilder sb = new StringBuilder();
858
859 try {
860 char chars[] = new char[1000];
861 int size = ir.read(chars);
862 while (size > 0) {
863 sb.append(chars, 0, size);
864 size = ir.read(chars);
865 }
866 } finally {
867 ir.close();
868 }
869 return sb.toString();
870 }
871
872 /**
873 * Join a list.
874 *
875 * @param args
876 * @return
877 */
878 public static String join(Collection<?> list, String delimeter) {
879 if ( list == null )
880 return "";
881 StringBuilder sb = new StringBuilder();
882 String del = "";
883 for (Object item : list) {
884 sb.append(del);
885 sb.append(item);
886 del = delimeter;
887 }
888 return sb.toString();
889 }
890
891 public static String join(Collection<?> list) {
892 return join(list, ",");
893 }
894
895 public static void split(String s, Collection<String> set) {
896
897 String elements[] = s.trim().split(LIST_SPLITTER);
898 for (String element : elements) {
899 if (element.length() > 0)
900 set.add(element);
901 }
902 }
903
904 public static Collection<String> split(String s) {
905 return split(s, LIST_SPLITTER);
906 }
907
908 public static Collection<String> split(String s, String splitter) {
909 if (s == null || s.trim().length() == 0)
910 return Collections.emptyList();
911
912 return Arrays.asList(s.split(splitter));
913 }
914
915 public boolean isExceptions() {
916 return exceptions;
917 }
918
919 public void setExceptions(boolean exceptions) {
920 this.exceptions = exceptions;
921 }
922
923 /**
924 * Make the file short if it is inside our base directory, otherwise long.
925 *
926 * @param f
927 * @return
928 */
929 public String normalize(String f) {
930 if (f.startsWith(base.getAbsolutePath() + "/"))
931 return f.substring(base.getAbsolutePath().length() + 1);
932 else
933 return f;
934 }
935
936 public String normalize(File f) {
937 return normalize(f.getAbsolutePath());
938 }
939
940 public static String removeDuplicateMarker(String key) {
941 int i = key.length() - 1;
942 while (i >= 0 && key.charAt(i) == DUPLICATE_MARKER)
943 --i;
944
945 return key.substring(0, i + 1);
946 }
947
948 public static boolean isDuplicate(String name) {
949 return name.length() > 0
950 && name.charAt(name.length() - 1) == DUPLICATE_MARKER;
951 }
952
953 public void setTrace(boolean x ) {
954 trace = x;
955 }
956
957
958 static class CL extends URLClassLoader {
959
960 CL() {
961 super( new URL[0], Processor.class.getClassLoader() );
962 }
963
964 void add(URL url) {
965 URL urls[] = getURLs();
966 for ( URL u : urls ) {
967 if ( u.equals(url))
968 return;
969 }
970 super.addURL(url);
971 }
972
973 }
974
975 private CL getLoader() {
976 if ( pluginLoader == null )
977 pluginLoader = new CL();
978 return pluginLoader;
979 }
980
981 public boolean exists() {
982 return base != null && base.exists();
983 }
984
985 public boolean isOk() {
986 return isFailOk() || (getErrors().size()==0);
987 }
988 public boolean isPerfect() {
989 return getErrors().size()==0 && getWarnings().size()==0;
990 }
991}
992
993