blob: e30ddcd7a1be1e7b9f9152af2085cbdaab4aee60 [file] [log] [blame]
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochf3173222012-06-07 21:57:32 +00002
3import java.io.*;
4import java.net.*;
5import java.util.*;
6import java.util.Map.Entry;
7import java.util.concurrent.*;
8import java.util.jar.*;
9import java.util.regex.*;
10
Stuart McCulloch42151ee2012-07-16 13:43:38 +000011import aQute.bnd.header.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000012import aQute.bnd.service.*;
13import aQute.lib.collections.*;
14import aQute.lib.io.*;
15import aQute.libg.generics.*;
Stuart McCulloch1a890552012-06-29 19:23:09 +000016import aQute.service.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000017
18public class Processor extends Domain implements Reporter, Registry, Constants, Closeable {
19
20 static ThreadLocal<Processor> current = new ThreadLocal<Processor>();
21 static ExecutorService executor = Executors.newCachedThreadPool();
22 static Random random = new Random();
23
24 // TODO handle include files out of date
25 // TODO make splitter skip eagerly whitespace so trim is not necessary
26 public final static String LIST_SPLITTER = "\\s*,\\s*";
27 final List<String> errors = new ArrayList<String>();
28 final List<String> warnings = new ArrayList<String>();
29 final Set<Object> basicPlugins = new HashSet<Object>();
30 private final Set<Closeable> toBeClosed = new HashSet<Closeable>();
31 Set<Object> plugins;
32
33 boolean pedantic;
34 boolean trace;
35 boolean exceptions;
36 boolean fileMustExist = true;
37
38 private File base = new File("").getAbsoluteFile();
39
40 Properties properties;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000041 String profile;
Stuart McCullochf3173222012-06-07 21:57:32 +000042 private Macro replacer;
43 private long lastModified;
44 private File propertiesFile;
45 private boolean fixup = true;
46 long modified;
47 Processor parent;
48 List<File> included;
49
50 CL pluginLoader;
51 Collection<String> filter;
52 HashSet<String> missingCommand;
53
54 public Processor() {
55 properties = new Properties();
56 }
57
58 public Processor(Properties parent) {
59 properties = new Properties(parent);
60 }
61
Stuart McCulloch669423b2012-06-26 16:34:24 +000062 public Processor(Processor child) {
63 this(child.properties);
64 this.parent = child;
Stuart McCullochf3173222012-06-07 21:57:32 +000065 }
66
67 public void setParent(Processor processor) {
68 this.parent = processor;
69 Properties ext = new Properties(processor.properties);
70 ext.putAll(this.properties);
71 this.properties = ext;
72 }
73
74 public Processor getParent() {
75 return parent;
76 }
77
78 public Processor getTop() {
79 if (parent == null)
80 return this;
Stuart McCulloch669423b2012-06-26 16:34:24 +000081 return parent.getTop();
Stuart McCullochf3173222012-06-07 21:57:32 +000082 }
83
84 public void getInfo(Reporter processor, String prefix) {
85 if (isFailOk())
86 addAll(warnings, processor.getErrors(), prefix);
87 else
88 addAll(errors, processor.getErrors(), prefix);
89 addAll(warnings, processor.getWarnings(), prefix);
90
91 processor.getErrors().clear();
92 processor.getWarnings().clear();
93 }
94
95 public void getInfo(Reporter processor) {
96 getInfo(processor, "");
97 }
98
Stuart McCulloch4482c702012-06-15 13:27:53 +000099 private <T> void addAll(List<String> to, List< ? extends T> from, String prefix) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000100 for (T x : from) {
101 to.add(prefix + x);
102 }
103 }
104
105 /**
106 * A processor can mark itself current for a thread.
107 *
108 * @return
109 */
110 private Processor current() {
111 Processor p = current.get();
112 if (p == null)
113 return this;
Stuart McCulloch669423b2012-06-26 16:34:24 +0000114 return p;
Stuart McCullochf3173222012-06-07 21:57:32 +0000115 }
116
Stuart McCulloch1a890552012-06-29 19:23:09 +0000117 public SetLocation warning(String string, Object... args) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000118 Processor p = current();
119 String s = formatArrays(string, args);
120 if (!p.warnings.contains(s))
121 p.warnings.add(s);
122 p.signal();
Stuart McCulloch1a890552012-06-29 19:23:09 +0000123 return location(s);
Stuart McCullochf3173222012-06-07 21:57:32 +0000124 }
125
Stuart McCulloch1a890552012-06-29 19:23:09 +0000126 public SetLocation error(String string, Object... args) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000127 Processor p = current();
Stuart McCulloch1a890552012-06-29 19:23:09 +0000128 try {
129 if (p.isFailOk())
130 return p.warning(string, args);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000131 String s = formatArrays(string, args == null ? new Object[0] : args);
132 if (!p.errors.contains(s))
133 p.errors.add(s);
134 return location(s);
Stuart McCullochf3173222012-06-07 21:57:32 +0000135 }
Stuart McCulloch1a890552012-06-29 19:23:09 +0000136 finally {
137 p.signal();
138 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000139 }
140
Stuart McCulloch1a890552012-06-29 19:23:09 +0000141 public void progress(float progress, String format, Object... args) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000142 format = String.format("[%2d] %s", (int) progress, format);
Stuart McCulloch1a890552012-06-29 19:23:09 +0000143 trace(format, args);
144 }
145
146 public void progress(String format, Object... args) {
147 progress(-1f, format, args);
148 }
149
150 public SetLocation exception(Throwable t, String format, Object... args) {
151 return error(format, t, args);
152 }
153
154 public SetLocation error(String string, Throwable t, Object... args) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000155 Processor p = current();
Stuart McCulloch1a890552012-06-29 19:23:09 +0000156 try {
157 if (p.exceptions)
158 t.printStackTrace();
159 if (p.isFailOk()) {
160 return p.warning(string + ": " + t, args);
161 }
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000162 p.errors.add("Exception: " + t.getMessage());
163 String s = formatArrays(string, args == null ? new Object[0] : args);
164 if (!p.errors.contains(s))
165 p.errors.add(s);
166 return location(s);
Stuart McCullochf3173222012-06-07 21:57:32 +0000167 }
Stuart McCulloch1a890552012-06-29 19:23:09 +0000168 finally {
169 p.signal();
170 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000171 }
172
Stuart McCulloch4482c702012-06-15 13:27:53 +0000173 public void signal() {}
Stuart McCullochf3173222012-06-07 21:57:32 +0000174
175 public List<String> getWarnings() {
176 return warnings;
177 }
178
179 public List<String> getErrors() {
180 return errors;
181 }
182
183 /**
184 * Standard OSGi header parser.
185 *
186 * @param value
187 * @return
188 */
189 static public Parameters parseHeader(String value, Processor logger) {
190 return new Parameters(value, logger);
191 }
192
193 public Parameters parseHeader(String value) {
194 return new Parameters(value, this);
195 }
196
197 public void addClose(Closeable jar) {
198 assert jar != null;
199 toBeClosed.add(jar);
200 }
201
202 public void removeClose(Closeable jar) {
203 assert jar != null;
204 toBeClosed.remove(jar);
205 }
206
Stuart McCullochf3173222012-06-07 21:57:32 +0000207 public boolean isPedantic() {
208 return current().pedantic;
209 }
210
211 public void setPedantic(boolean pedantic) {
212 this.pedantic = pedantic;
213 }
214
215 public void use(Processor reporter) {
216 setPedantic(reporter.isPedantic());
217 setTrace(reporter.isTrace());
218 setBase(reporter.getBase());
219 setFailOk(reporter.isFailOk());
220 }
221
222 public static File getFile(File base, String file) {
223 return IO.getFile(base, file);
224 }
225
226 public File getFile(String file) {
227 return getFile(base, file);
228 }
229
230 /**
231 * Return a list of plugins that implement the given class.
232 *
233 * @param clazz
234 * Each returned plugin implements this class/interface
235 * @return A list of plugins
236 */
237 public <T> List<T> getPlugins(Class<T> clazz) {
238 List<T> l = new ArrayList<T>();
239 Set<Object> all = getPlugins();
240 for (Object plugin : all) {
241 if (clazz.isInstance(plugin))
242 l.add(clazz.cast(plugin));
243 }
244 return l;
245 }
246
247 /**
248 * Returns the first plugin it can find of the given type.
249 *
250 * @param <T>
251 * @param clazz
252 * @return
253 */
254 public <T> T getPlugin(Class<T> clazz) {
255 Set<Object> all = getPlugins();
256 for (Object plugin : all) {
257 if (clazz.isInstance(plugin))
258 return clazz.cast(plugin);
259 }
260 return null;
261 }
262
263 /**
264 * Return a list of plugins. Plugins are defined with the -plugin command.
265 * They are class names, optionally associated with attributes. Plugins can
Stuart McCulloch4482c702012-06-15 13:27:53 +0000266 * implement the Plugin interface to see these attributes. Any object can be
267 * a plugin.
Stuart McCullochf3173222012-06-07 21:57:32 +0000268 *
269 * @return
270 */
271 protected synchronized Set<Object> getPlugins() {
272 if (this.plugins != null)
273 return this.plugins;
274
275 missingCommand = new HashSet<String>();
276 Set<Object> list = new LinkedHashSet<Object>();
277
278 // The owner of the plugin is always in there.
279 list.add(this);
280 setTypeSpecificPlugins(list);
281
282 if (parent != null)
283 list.addAll(parent.getPlugins());
284
285 // We only use plugins now when they are defined on our level
286 // and not if it is in our parent. We inherit from our parent
287 // through the previous block.
288
289 if (properties.containsKey(PLUGIN)) {
290 String spe = getProperty(PLUGIN);
291 if (spe.equals(NONE))
292 return new LinkedHashSet<Object>();
293
294 String pluginPath = getProperty(PLUGINPATH);
295 loadPlugins(list, spe, pluginPath);
296 }
297
298 return this.plugins = list;
299 }
300
301 /**
302 * @param list
303 * @param spe
304 */
305 protected void loadPlugins(Set<Object> list, String spe, String pluginPath) {
306 Parameters plugins = new Parameters(spe);
307 CL loader = getLoader();
308
309 // First add the plugin-specific paths from their path: directives
Stuart McCulloch4482c702012-06-15 13:27:53 +0000310 for (Entry<String,Attrs> entry : plugins.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000311 String key = removeDuplicateMarker(entry.getKey());
312 String path = entry.getValue().get(PATH_DIRECTIVE);
313 if (path != null) {
314 String parts[] = path.split("\\s*,\\s*");
315 try {
316 for (String p : parts) {
317 File f = getFile(p).getAbsoluteFile();
318 loader.add(f.toURI().toURL());
319 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000320 }
321 catch (Exception e) {
322 error("Problem adding path %s to loader for plugin %s. Exception: (%s)", path, key, e);
Stuart McCullochf3173222012-06-07 21:57:32 +0000323 }
324 }
325 }
326
327 // Next add -pluginpath entries
328 if (pluginPath != null && pluginPath.length() > 0) {
329 StringTokenizer tokenizer = new StringTokenizer(pluginPath, ",");
330 while (tokenizer.hasMoreTokens()) {
331 String path = tokenizer.nextToken().trim();
332 try {
333 File f = getFile(path).getAbsoluteFile();
334 loader.add(f.toURI().toURL());
Stuart McCulloch4482c702012-06-15 13:27:53 +0000335 }
336 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000337 error("Problem adding path %s from global plugin path. Exception: %s", path, e);
338 }
339 }
340 }
341
342 // Load the plugins
Stuart McCulloch4482c702012-06-15 13:27:53 +0000343 for (Entry<String,Attrs> entry : plugins.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000344 String key = entry.getKey();
345
346 try {
347 trace("Using plugin %s", key);
348
349 // Plugins could use the same class with different
350 // parameters so we could have duplicate names Remove
351 // the ! added by the parser to make each name unique.
352 key = removeDuplicateMarker(key);
353
354 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000355 Class< ? > c = loader.loadClass(key);
Stuart McCullochf3173222012-06-07 21:57:32 +0000356 Object plugin = c.newInstance();
357 customize(plugin, entry.getValue());
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000358 if (plugin instanceof Closeable) {
359 addClose((Closeable) plugin);
Stuart McCulloch55fbda52012-08-02 13:26:25 +0000360 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000361 list.add(plugin);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000362 }
363 catch (Throwable t) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000364 // We can defer the error if the plugin specifies
365 // a command name. In that case, we'll verify that
366 // a bnd file does not contain any references to a
367 // plugin
368 // command. The reason this feature was added was
369 // to compile plugin classes with the same build.
370 String commands = entry.getValue().get(COMMAND_DIRECTIVE);
371 if (commands == null)
372 error("Problem loading the plugin: %s exception: (%s)", key, t);
373 else {
374 Collection<String> cs = split(commands);
375 missingCommand.addAll(cs);
376 }
377 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000378 }
379 catch (Throwable e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000380 error("Problem loading the plugin: %s exception: (%s)", key, e);
381 }
382 }
383 }
384
385 protected void setTypeSpecificPlugins(Set<Object> list) {
386 list.add(executor);
387 list.add(random);
388 list.addAll(basicPlugins);
389 }
390
391 /**
392 * @param plugin
393 * @param entry
394 */
395 protected <T> T customize(T plugin, Attrs map) {
396 if (plugin instanceof Plugin) {
397 if (map != null)
398 ((Plugin) plugin).setProperties(map);
399
400 ((Plugin) plugin).setReporter(this);
401 }
402 if (plugin instanceof RegistryPlugin) {
403 ((RegistryPlugin) plugin).setRegistry(this);
404 }
405 return plugin;
406 }
407
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000408 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000409 public boolean isFailOk() {
410 String v = getProperty(Analyzer.FAIL_OK, null);
411 return v != null && v.equalsIgnoreCase("true");
412 }
413
414 public File getBase() {
415 return base;
416 }
417
418 public void setBase(File base) {
419 this.base = base;
420 }
421
422 public void clear() {
423 errors.clear();
424 warnings.clear();
425 }
426
427 public void trace(String msg, Object... parms) {
428 Processor p = current();
429 if (p.trace) {
430 System.err.printf("# " + msg + "%n", parms);
431 }
432 }
433
434 public <T> List<T> newList() {
435 return new ArrayList<T>();
436 }
437
438 public <T> Set<T> newSet() {
439 return new TreeSet<T>();
440 }
441
Stuart McCulloch4482c702012-06-15 13:27:53 +0000442 public static <K, V> Map<K,V> newMap() {
443 return new LinkedHashMap<K,V>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000444 }
445
Stuart McCulloch4482c702012-06-15 13:27:53 +0000446 public static <K, V> Map<K,V> newHashMap() {
447 return new LinkedHashMap<K,V>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000448 }
449
450 public <T> List<T> newList(Collection<T> t) {
451 return new ArrayList<T>(t);
452 }
453
454 public <T> Set<T> newSet(Collection<T> t) {
455 return new TreeSet<T>(t);
456 }
457
Stuart McCulloch4482c702012-06-15 13:27:53 +0000458 public <K, V> Map<K,V> newMap(Map<K,V> t) {
459 return new LinkedHashMap<K,V>(t);
Stuart McCullochf3173222012-06-07 21:57:32 +0000460 }
461
462 public void close() {
463 for (Closeable c : toBeClosed) {
464 try {
465 c.close();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000466 }
467 catch (IOException e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000468 // Who cares?
469 }
470 }
471 toBeClosed.clear();
472 }
473
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000474 public String _basedir(@SuppressWarnings("unused")
475 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000476 if (base == null)
477 throw new IllegalArgumentException("No base dir set");
478
479 return base.getAbsolutePath();
480 }
481
482 /**
483 * Property handling ...
484 *
485 * @return
486 */
487
488 public Properties getProperties() {
489 if (fixup) {
490 fixup = false;
491 begin();
492 }
493
494 return properties;
495 }
496
497 public String getProperty(String key) {
498 return getProperty(key, null);
499 }
500
501 public void mergeProperties(File file, boolean override) {
502 if (file.isFile()) {
503 try {
504 Properties properties = loadProperties(file);
505 mergeProperties(properties, override);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000506 }
507 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000508 error("Error loading properties file: " + file);
509 }
510 } else {
511 if (!file.exists())
512 error("Properties file does not exist: " + file);
513 else
514 error("Properties file must a file, not a directory: " + file);
515 }
516 }
517
518 public void mergeProperties(Properties properties, boolean override) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000519 for (Enumeration< ? > e = properties.propertyNames(); e.hasMoreElements();) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000520 String key = (String) e.nextElement();
521 String value = properties.getProperty(key);
522 if (override || !getProperties().containsKey(key))
523 setProperty(key, value);
524 }
525 }
526
527 public void setProperties(Properties properties) {
528 doIncludes(getBase(), properties);
529 this.properties.putAll(properties);
530 }
531
532 public void addProperties(File file) throws Exception {
533 addIncluded(file);
534 Properties p = loadProperties(file);
535 setProperties(p);
536 }
537
Stuart McCulloch4482c702012-06-15 13:27:53 +0000538 public void addProperties(Map< ? , ? > properties) {
539 for (Entry< ? , ? > entry : properties.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000540 setProperty(entry.getKey().toString(), entry.getValue() + "");
541 }
542 }
543
544 public synchronized void addIncluded(File file) {
545 if (included == null)
546 included = new ArrayList<File>();
547 included.add(file);
548 }
549
550 /**
551 * Inspect the properties and if you find -includes parse the line included
552 * manifest files or properties files. The files are relative from the given
553 * base, this is normally the base for the analyzer.
554 *
555 * @param ubase
556 * @param p
557 * @param done
558 * @throws IOException
559 * @throws IOException
560 */
561
562 private void doIncludes(File ubase, Properties p) {
563 String includes = p.getProperty(INCLUDE);
564 if (includes != null) {
565 includes = getReplacer().process(includes);
566 p.remove(INCLUDE);
567 Collection<String> clauses = new Parameters(includes).keySet();
568
569 for (String value : clauses) {
570 boolean fileMustExist = true;
571 boolean overwrite = true;
572 while (true) {
573 if (value.startsWith("-")) {
574 fileMustExist = false;
575 value = value.substring(1).trim();
576 } else if (value.startsWith("~")) {
577 // Overwrite properties!
578 overwrite = false;
579 value = value.substring(1).trim();
580 } else
581 break;
582 }
583 try {
584 File file = getFile(ubase, value).getAbsoluteFile();
585 if (!file.isFile() && fileMustExist) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000586 error("Included file " + file + (file.exists() ? " does not exist" : " is directory"));
Stuart McCullochf3173222012-06-07 21:57:32 +0000587 } else
588 doIncludeFile(file, overwrite, p);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000589 }
590 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000591 if (fileMustExist)
592 error("Error in processing included file: " + value, e);
593 }
594 }
595 }
596 }
597
598 /**
599 * @param file
600 * @param parent
601 * @param done
602 * @param overwrite
603 * @throws FileNotFoundException
604 * @throws IOException
605 */
606 public void doIncludeFile(File file, boolean overwrite, Properties target) throws Exception {
607 doIncludeFile(file, overwrite, target, null);
608 }
609
610 /**
611 * @param file
612 * @param parent
613 * @param done
614 * @param overwrite
615 * @param extensionName
616 * @throws FileNotFoundException
617 * @throws IOException
618 */
619 public void doIncludeFile(File file, boolean overwrite, Properties target, String extensionName) throws Exception {
620 if (included != null && included.contains(file)) {
621 error("Cyclic or multiple include of " + file);
622 } else {
623 addIncluded(file);
624 updateModified(file.lastModified(), file.toString());
625 InputStream in = new FileInputStream(file);
626 try {
627 Properties sub;
628 if (file.getName().toLowerCase().endsWith(".mf")) {
629 sub = getManifestAsProperties(in);
630 } else
631 sub = loadProperties(in, file.getAbsolutePath());
632
633 doIncludes(file.getParentFile(), sub);
634 // make sure we do not override properties
Stuart McCulloch4482c702012-06-15 13:27:53 +0000635 for (Map.Entry< ? , ? > entry : sub.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000636 String key = (String) entry.getKey();
637 String value = (String) entry.getValue();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000638
Stuart McCullochf3173222012-06-07 21:57:32 +0000639 if (overwrite || !target.containsKey(key)) {
640 target.setProperty(key, value);
641 } else if (extensionName != null) {
642 String extensionKey = extensionName + "." + key;
643 if (!target.containsKey(extensionKey))
644 target.setProperty(extensionKey, value);
645 }
646 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000647 }
648 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000649 IO.close(in);
650 }
651 }
652 }
653
654 public void unsetProperty(String string) {
655 getProperties().remove(string);
656
657 }
658
659 public boolean refresh() {
660 plugins = null; // We always refresh our plugins
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000661
662
Stuart McCullochf3173222012-06-07 21:57:32 +0000663 if (propertiesFile == null)
664 return false;
665
666 boolean changed = updateModified(propertiesFile.lastModified(), "properties file");
667 if (included != null) {
668 for (File file : included) {
669 if (changed)
670 break;
671
Stuart McCulloch4482c702012-06-15 13:27:53 +0000672 changed |= !file.exists() || updateModified(file.lastModified(), "include file: " + file);
Stuart McCullochf3173222012-06-07 21:57:32 +0000673 }
674 }
675
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000676 profile = getProperty(PROFILE); // Used in property access
677
Stuart McCullochf3173222012-06-07 21:57:32 +0000678 if (changed) {
679 forceRefresh();
680 return true;
681 }
682 return false;
683 }
684
685 /**
686 *
687 */
688 public void forceRefresh() {
689 included = null;
690 properties.clear();
691 setProperties(propertiesFile, base);
692 propertiesChanged();
693 }
694
Stuart McCulloch4482c702012-06-15 13:27:53 +0000695 public void propertiesChanged() {}
Stuart McCullochf3173222012-06-07 21:57:32 +0000696
697 /**
698 * Set the properties by file. Setting the properties this way will also set
699 * the base for this analyzer. After reading the properties, this will call
700 * setProperties(Properties) which will handle the includes.
701 *
702 * @param propertiesFile
703 * @throws FileNotFoundException
704 * @throws IOException
705 */
706 public void setProperties(File propertiesFile) throws IOException {
707 propertiesFile = propertiesFile.getAbsoluteFile();
708 setProperties(propertiesFile, propertiesFile.getParentFile());
709 }
710
711 public void setProperties(File propertiesFile, File base) {
712 this.propertiesFile = propertiesFile.getAbsoluteFile();
713 setBase(base);
714 try {
715 if (propertiesFile.isFile()) {
716 // System.err.println("Loading properties " + propertiesFile);
717 long modified = propertiesFile.lastModified();
718 if (modified > System.currentTimeMillis() + 100) {
719 System.err.println("Huh? This is in the future " + propertiesFile);
720 this.modified = System.currentTimeMillis();
721 } else
722 this.modified = modified;
723
724 included = null;
725 Properties p = loadProperties(propertiesFile);
726 setProperties(p);
727 } else {
728 if (fileMustExist) {
729 error("No such properties file: " + propertiesFile);
730 }
731 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000732 }
733 catch (IOException e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000734 error("Could not load properties " + propertiesFile);
735 }
736 }
737
738 protected void begin() {
739 if (isTrue(getProperty(PEDANTIC)))
740 setPedantic(true);
741 }
742
743 public static boolean isTrue(String value) {
744 if (value == null)
745 return false;
746
747 return !"false".equalsIgnoreCase(value);
748 }
749
750 /**
751 * Get a property without preprocessing it with a proper default
752 *
753 * @param headerName
754 * @param deflt
755 * @return
756 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000757
Stuart McCullochf3173222012-06-07 21:57:32 +0000758 public String getUnprocessedProperty(String key, String deflt) {
759 return getProperties().getProperty(key, deflt);
760 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000761
Stuart McCullochf3173222012-06-07 21:57:32 +0000762 /**
763 * Get a property with preprocessing it with a proper default
764 *
765 * @param headerName
766 * @param deflt
767 * @return
768 */
769 public String getProperty(String key, String deflt) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000770
Stuart McCullochf3173222012-06-07 21:57:32 +0000771 String value = null;
772
773 Instruction ins = new Instruction(key);
774 if (!ins.isLiteral()) {
775 // Handle a wildcard key, make sure they're sorted
776 // for consistency
777 SortedList<String> sortedList = SortedList.fromIterator(iterator());
778 StringBuilder sb = new StringBuilder();
779 String del = "";
780 for (String k : sortedList) {
781 if (ins.matches(k)) {
782 String v = getProperty(k, null);
783 if (v != null) {
784 sb.append(del);
785 del = ",";
786 sb.append(v);
787 }
788 }
789 }
790 if (sb.length() == 0)
791 return deflt;
792
793 return sb.toString();
794 }
795
796 Processor source = this;
797
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000798 // Use the key as is first, if found ok
799
Stuart McCullochf3173222012-06-07 21:57:32 +0000800 if (filter != null && filter.contains(key)) {
801 value = (String) getProperties().get(key);
802 } else {
803 while (source != null) {
804 value = (String) source.getProperties().get(key);
805 if (value != null)
806 break;
807
808 source = source.getParent();
809 }
810 }
811
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000812 // Check if we found a value, if not, try to prefix
813 // it with a profile if found and search again. profiles
814 // are a simple name that is prefixed like [profile]. This
815 // allows different variables to be used in different profiles.
816
817 if (value == null && profile != null) {
818 String pkey = "[" + profile + "]" + key;
819 if (filter != null && filter.contains(key)) {
820 value = (String) getProperties().get(pkey);
821 } else {
822 while (source != null) {
823 value = (String) source.getProperties().get(pkey);
824 if (value != null)
825 break;
826
827 source = source.getParent();
828 }
829 }
830 }
831
Stuart McCullochf3173222012-06-07 21:57:32 +0000832 if (value != null)
833 return getReplacer().process(value, source);
834 else if (deflt != null)
835 return getReplacer().process(deflt, this);
836 else
837 return null;
838 }
839
840 /**
841 * Helper to load a properties file from disk.
842 *
843 * @param file
844 * @return
845 * @throws IOException
846 */
847 public Properties loadProperties(File file) throws IOException {
848 updateModified(file.lastModified(), "Properties file: " + file);
849 InputStream in = new FileInputStream(file);
850 try {
851 Properties p = loadProperties(in, file.getAbsolutePath());
852 return p;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000853 }
854 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000855 in.close();
856 }
857 }
858
859 Properties loadProperties(InputStream in, String name) throws IOException {
860 int n = name.lastIndexOf('/');
861 if (n > 0)
862 name = name.substring(0, n);
863 if (name.length() == 0)
864 name = ".";
865
866 try {
867 Properties p = new Properties();
868 p.load(in);
869 return replaceAll(p, "\\$\\{\\.\\}", name);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000870 }
871 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000872 error("Error during loading properties file: " + name + ", error:" + e);
873 return new Properties();
874 }
875 }
876
877 /**
878 * Replace a string in all the values of the map. This can be used to
879 * preassign variables that change. I.e. the base directory ${.} for a
880 * loaded properties
881 */
882
883 public static Properties replaceAll(Properties p, String pattern, String replacement) {
884 Properties result = new Properties();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000885 for (Iterator<Map.Entry<Object,Object>> i = p.entrySet().iterator(); i.hasNext();) {
886 Map.Entry<Object,Object> entry = i.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000887 String key = (String) entry.getKey();
888 String value = (String) entry.getValue();
889 value = value.replaceAll(pattern, replacement);
890 result.put(key, value);
891 }
892 return result;
893 }
894
895 /**
896 * Print a standard Map based OSGi header.
897 *
898 * @param exports
899 * map { name => Map { attribute|directive => value } }
900 * @return the clauses
901 * @throws IOException
902 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000903 public static String printClauses(Map< ? , ? extends Map< ? , ? >> exports) throws IOException {
Stuart McCullochf3173222012-06-07 21:57:32 +0000904 return printClauses(exports, false);
905 }
906
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000907 public static String printClauses(Map< ? , ? extends Map< ? , ? >> exports, @SuppressWarnings("unused")
908 boolean checkMultipleVersions) throws IOException {
Stuart McCullochf3173222012-06-07 21:57:32 +0000909 StringBuilder sb = new StringBuilder();
910 String del = "";
Stuart McCulloch4482c702012-06-15 13:27:53 +0000911 for (Entry< ? , ? extends Map< ? , ? >> entry : exports.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000912 String name = entry.getKey().toString();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000913 Map< ? , ? > clause = entry.getValue();
Stuart McCullochf3173222012-06-07 21:57:32 +0000914
915 // We allow names to be duplicated in the input
916 // by ending them with '~'. This is necessary to use
917 // the package names as keys. However, we remove these
918 // suffixes in the output so that you can set multiple
919 // exports with different attributes.
920 String outname = removeDuplicateMarker(name);
921 sb.append(del);
922 sb.append(outname);
923 printClause(clause, sb);
924 del = ",";
925 }
926 return sb.toString();
927 }
928
Stuart McCulloch4482c702012-06-15 13:27:53 +0000929 public static void printClause(Map< ? , ? > map, StringBuilder sb) throws IOException {
Stuart McCullochf3173222012-06-07 21:57:32 +0000930
Stuart McCulloch4482c702012-06-15 13:27:53 +0000931 for (Entry< ? , ? > entry : map.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000932 Object key = entry.getKey();
933 // Skip directives we do not recognize
Stuart McCulloch4482c702012-06-15 13:27:53 +0000934 if (key.equals(NO_IMPORT_DIRECTIVE) || key.equals(PROVIDE_DIRECTIVE) || key.equals(SPLIT_PACKAGE_DIRECTIVE)
935 || key.equals(FROM_DIRECTIVE))
Stuart McCullochf3173222012-06-07 21:57:32 +0000936 continue;
937
938 String value = ((String) entry.getValue()).trim();
939 sb.append(";");
940 sb.append(key);
941 sb.append("=");
942
943 quote(sb, value);
944 }
945 }
946
947 /**
948 * @param sb
949 * @param value
950 * @return
951 * @throws IOException
952 */
953 public static boolean quote(Appendable sb, String value) throws IOException {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000954 boolean clean = (value.length() >= 2 && value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"')
955 || Verifier.TOKEN.matcher(value).matches();
Stuart McCullochf3173222012-06-07 21:57:32 +0000956 if (!clean)
957 sb.append("\"");
958 sb.append(value);
959 if (!clean)
960 sb.append("\"");
961 return clean;
962 }
963
964 public Macro getReplacer() {
965 if (replacer == null)
966 return replacer = new Macro(this, getMacroDomains());
Stuart McCulloch669423b2012-06-26 16:34:24 +0000967 return replacer;
Stuart McCullochf3173222012-06-07 21:57:32 +0000968 }
969
970 /**
971 * This should be overridden by subclasses to add extra macro command
972 * domains on the search list.
973 *
974 * @return
975 */
976 protected Object[] getMacroDomains() {
977 return new Object[] {};
978 }
979
980 /**
981 * Return the properties but expand all macros. This always returns a new
982 * Properties object that can be used in any way.
983 *
984 * @return
985 */
986 public Properties getFlattenedProperties() {
987 return getReplacer().getFlattenedProperties();
988
989 }
990
991 /**
992 * Return all inherited property keys
993 *
994 * @return
995 */
996 public Set<String> getPropertyKeys(boolean inherit) {
997 Set<String> result;
998 if (parent == null || !inherit) {
999 result = Create.set();
1000 } else
1001 result = parent.getPropertyKeys(inherit);
1002 for (Object o : properties.keySet())
1003 result.add(o.toString());
1004
1005 return result;
1006 }
1007
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001008 public boolean updateModified(long time, @SuppressWarnings("unused")
1009 String reason) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001010 if (time > lastModified) {
1011 lastModified = time;
1012 return true;
1013 }
1014 return false;
1015 }
1016
1017 public long lastModified() {
1018 return lastModified;
1019 }
1020
1021 /**
1022 * Add or override a new property.
1023 *
1024 * @param key
1025 * @param value
1026 */
1027 public void setProperty(String key, String value) {
1028 checkheader: for (int i = 0; i < headers.length; i++) {
1029 if (headers[i].equalsIgnoreCase(value)) {
1030 value = headers[i];
1031 break checkheader;
1032 }
1033 }
1034 getProperties().put(key, value);
1035 }
1036
1037 /**
1038 * Read a manifest but return a properties object.
1039 *
1040 * @param in
1041 * @return
1042 * @throws IOException
1043 */
1044 public static Properties getManifestAsProperties(InputStream in) throws IOException {
1045 Properties p = new Properties();
1046 Manifest manifest = new Manifest(in);
1047 for (Iterator<Object> it = manifest.getMainAttributes().keySet().iterator(); it.hasNext();) {
1048 Attributes.Name key = (Attributes.Name) it.next();
1049 String value = manifest.getMainAttributes().getValue(key);
1050 p.put(key.toString(), value);
1051 }
1052 return p;
1053 }
1054
1055 public File getPropertiesFile() {
1056 return propertiesFile;
1057 }
1058
1059 public void setFileMustExist(boolean mustexist) {
1060 fileMustExist = mustexist;
1061 }
1062
1063 static public String read(InputStream in) throws Exception {
1064 InputStreamReader ir = new InputStreamReader(in, "UTF8");
1065 StringBuilder sb = new StringBuilder();
1066
1067 try {
1068 char chars[] = new char[1000];
1069 int size = ir.read(chars);
1070 while (size > 0) {
1071 sb.append(chars, 0, size);
1072 size = ir.read(chars);
1073 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001074 }
1075 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001076 ir.close();
1077 }
1078 return sb.toString();
1079 }
1080
1081 /**
1082 * Join a list.
1083 *
1084 * @param args
1085 * @return
1086 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001087 public static String join(Collection< ? > list, String delimeter) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001088 return join(delimeter, list);
1089 }
1090
Stuart McCulloch4482c702012-06-15 13:27:53 +00001091 public static String join(String delimeter, Collection< ? >... list) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001092 StringBuilder sb = new StringBuilder();
1093 String del = "";
1094 if (list != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001095 for (Collection< ? > l : list) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001096 for (Object item : l) {
1097 sb.append(del);
1098 sb.append(item);
1099 del = delimeter;
1100 }
1101 }
1102 }
1103 return sb.toString();
1104 }
1105
1106 public static String join(Object[] list, String delimeter) {
1107 if (list == null)
1108 return "";
1109 StringBuilder sb = new StringBuilder();
1110 String del = "";
1111 for (Object item : list) {
1112 sb.append(del);
1113 sb.append(item);
1114 del = delimeter;
1115 }
1116 return sb.toString();
1117 }
1118
Stuart McCulloch4482c702012-06-15 13:27:53 +00001119 public static String join(Collection< ? >... list) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001120 return join(",", list);
1121 }
1122
1123 public static <T> String join(T list[]) {
1124 return join(list, ",");
1125 }
1126
1127 public static void split(String s, Collection<String> set) {
1128
1129 String elements[] = s.trim().split(LIST_SPLITTER);
1130 for (String element : elements) {
1131 if (element.length() > 0)
1132 set.add(element);
1133 }
1134 }
1135
1136 public static Collection<String> split(String s) {
1137 return split(s, LIST_SPLITTER);
1138 }
1139
1140 public static Collection<String> split(String s, String splitter) {
1141 if (s != null)
1142 s = s.trim();
1143 if (s == null || s.trim().length() == 0)
1144 return Collections.emptyList();
1145
1146 return Arrays.asList(s.split(splitter));
1147 }
1148
1149 public static String merge(String... strings) {
1150 ArrayList<String> result = new ArrayList<String>();
1151 for (String s : strings) {
1152 if (s != null)
1153 split(s, result);
1154 }
1155 return join(result);
1156 }
1157
1158 public boolean isExceptions() {
1159 return exceptions;
1160 }
1161
1162 public void setExceptions(boolean exceptions) {
1163 this.exceptions = exceptions;
1164 }
1165
1166 /**
1167 * Make the file short if it is inside our base directory, otherwise long.
1168 *
1169 * @param f
1170 * @return
1171 */
1172 public String normalize(String f) {
1173 if (f.startsWith(base.getAbsolutePath() + "/"))
1174 return f.substring(base.getAbsolutePath().length() + 1);
Stuart McCulloch669423b2012-06-26 16:34:24 +00001175 return f;
Stuart McCullochf3173222012-06-07 21:57:32 +00001176 }
1177
1178 public String normalize(File f) {
1179 return normalize(f.getAbsolutePath());
1180 }
1181
1182 public static String removeDuplicateMarker(String key) {
1183 int i = key.length() - 1;
1184 while (i >= 0 && key.charAt(i) == DUPLICATE_MARKER)
1185 --i;
1186
1187 return key.substring(0, i + 1);
1188 }
1189
1190 public static boolean isDuplicate(String name) {
1191 return name.length() > 0 && name.charAt(name.length() - 1) == DUPLICATE_MARKER;
1192 }
1193
1194 public void setTrace(boolean x) {
1195 trace = x;
1196 }
1197
1198 static class CL extends URLClassLoader {
1199
1200 CL() {
1201 super(new URL[0], Processor.class.getClassLoader());
1202 }
1203
1204 void add(URL url) {
1205 URL urls[] = getURLs();
1206 for (URL u : urls) {
1207 if (u.equals(url))
1208 return;
1209 }
1210 super.addURL(url);
1211 }
1212
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001213 @Override
Stuart McCulloch4482c702012-06-15 13:27:53 +00001214 public Class< ? > loadClass(String name) throws NoClassDefFoundError {
Stuart McCullochf3173222012-06-07 21:57:32 +00001215 try {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001216 Class< ? > c = super.loadClass(name);
Stuart McCullochf3173222012-06-07 21:57:32 +00001217 return c;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001218 }
1219 catch (Throwable t) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001220 StringBuilder sb = new StringBuilder();
1221 sb.append(name);
1222 sb.append(" not found, parent: ");
1223 sb.append(getParent());
1224 sb.append(" urls:");
1225 sb.append(Arrays.toString(getURLs()));
1226 sb.append(" exception:");
1227 sb.append(t);
1228 throw new NoClassDefFoundError(sb.toString());
1229 }
1230 }
1231 }
1232
1233 private CL getLoader() {
1234 if (pluginLoader == null) {
1235 pluginLoader = new CL();
1236 }
1237 return pluginLoader;
1238 }
1239
1240 /*
1241 * Check if this is a valid project.
1242 */
1243 public boolean exists() {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001244 return base != null && base.isDirectory() && propertiesFile != null && propertiesFile.isFile();
Stuart McCullochf3173222012-06-07 21:57:32 +00001245 }
1246
1247 public boolean isOk() {
1248 return isFailOk() || (getErrors().size() == 0);
1249 }
1250
1251 public boolean check(String... pattern) throws IOException {
1252 Set<String> missed = Create.set();
1253
1254 if (pattern != null) {
1255 for (String p : pattern) {
1256 boolean match = false;
1257 Pattern pat = Pattern.compile(p);
1258 for (Iterator<String> i = errors.iterator(); i.hasNext();) {
1259 if (pat.matcher(i.next()).find()) {
1260 i.remove();
1261 match = true;
1262 }
1263 }
1264 for (Iterator<String> i = warnings.iterator(); i.hasNext();) {
1265 if (pat.matcher(i.next()).find()) {
1266 i.remove();
1267 match = true;
1268 }
1269 }
1270 if (!match)
1271 missed.add(p);
1272
1273 }
1274 }
1275 if (missed.isEmpty() && isPerfect())
1276 return true;
1277
1278 if (!missed.isEmpty())
Stuart McCulloch4482c702012-06-15 13:27:53 +00001279 System.err.println("Missed the following patterns in the warnings or errors: " + missed);
Stuart McCullochf3173222012-06-07 21:57:32 +00001280
1281 report(System.err);
1282 return false;
1283 }
1284
1285 protected void report(Appendable out) throws IOException {
1286 if (errors.size() > 0) {
Stuart McCulloch54229442012-07-12 22:12:58 +00001287 out.append(String.format("-----------------%nErrors%n"));
Stuart McCullochf3173222012-06-07 21:57:32 +00001288 for (int i = 0; i < errors.size(); i++) {
Stuart McCulloch54229442012-07-12 22:12:58 +00001289 out.append(String.format("%03d: %s%n", i, errors.get(i)));
Stuart McCullochf3173222012-06-07 21:57:32 +00001290 }
1291 }
1292 if (warnings.size() > 0) {
Stuart McCulloch54229442012-07-12 22:12:58 +00001293 out.append(String.format("-----------------%nWarnings%n"));
Stuart McCullochf3173222012-06-07 21:57:32 +00001294 for (int i = 0; i < warnings.size(); i++) {
Stuart McCulloch54229442012-07-12 22:12:58 +00001295 out.append(String.format("%03d: %s%n", i, warnings.get(i)));
Stuart McCullochf3173222012-06-07 21:57:32 +00001296 }
1297 }
1298 }
1299
1300 public boolean isPerfect() {
1301 return getErrors().size() == 0 && getWarnings().size() == 0;
1302 }
1303
1304 public void setForceLocal(Collection<String> local) {
1305 filter = local;
1306 }
1307
1308 /**
1309 * Answer if the name is a missing plugin's command name. If a bnd file
1310 * contains the command name of a plugin, and that plugin is not available,
1311 * then an error is reported during manifest calculation. This allows the
Stuart McCulloch4482c702012-06-15 13:27:53 +00001312 * plugin to fail to load when it is not needed. We first get the plugins to
1313 * ensure it is properly initialized.
Stuart McCullochf3173222012-06-07 21:57:32 +00001314 *
1315 * @param name
1316 * @return
1317 */
1318 public boolean isMissingPlugin(String name) {
1319 getPlugins();
1320 return missingCommand != null && missingCommand.contains(name);
1321 }
1322
1323 /**
1324 * Append two strings to for a path in a ZIP or JAR file. It is guaranteed
1325 * to return a string that does not start, nor ends with a '/', while it is
1326 * properly separated with slashes. Double slashes are properly removed.
1327 *
1328 * <pre>
1329 * &quot;/&quot; + &quot;abc/def/&quot; becomes &quot;abc/def&quot;
1330 *
1331 * &#064;param prefix
1332 * &#064;param suffix
1333 * &#064;return
Stuart McCullochf3173222012-06-07 21:57:32 +00001334 */
1335 public static String appendPath(String... parts) {
1336 StringBuilder sb = new StringBuilder();
1337 boolean lastSlash = true;
1338 for (String part : parts) {
1339 for (int i = 0; i < part.length(); i++) {
1340 char c = part.charAt(i);
1341 if (c == '/') {
1342 if (!lastSlash)
1343 sb.append('/');
1344 lastSlash = true;
1345 } else {
1346 sb.append(c);
1347 lastSlash = false;
1348 }
1349 }
1350
1351 if (!lastSlash && sb.length() > 0) {
1352 sb.append('/');
1353 lastSlash = true;
1354 }
1355 }
1356 if (lastSlash && sb.length() > 0)
1357 sb.deleteCharAt(sb.length() - 1);
1358
1359 return sb.toString();
1360 }
1361
1362 /**
1363 * Parse the a=b strings and return a map of them.
1364 *
1365 * @param attrs
1366 * @param clazz
1367 * @return
1368 */
1369 public static Attrs doAttrbutes(Object[] attrs, Clazz clazz, Macro macro) {
1370 Attrs map = new Attrs();
1371
1372 if (attrs == null || attrs.length == 0)
1373 return map;
1374
1375 for (Object a : attrs) {
1376 String attr = (String) a;
1377 int n = attr.indexOf("=");
1378 if (n > 0) {
1379 map.put(attr.substring(0, n), macro.process(attr.substring(n + 1)));
1380 } else
1381 throw new IllegalArgumentException(formatArrays(
Stuart McCulloch4482c702012-06-15 13:27:53 +00001382 "Invalid attribute on package-info.java in %s , %s. Must be <key>=<name> ", clazz, attr));
Stuart McCullochf3173222012-06-07 21:57:32 +00001383 }
1384 return map;
1385 }
1386
1387 /**
1388 * This method is the same as String.format but it makes sure that any
1389 * arrays are transformed to strings.
1390 *
1391 * @param string
1392 * @param parms
1393 * @return
1394 */
1395 public static String formatArrays(String string, Object... parms) {
1396 Object[] parms2 = parms;
1397 Object[] output = new Object[parms.length];
1398 for (int i = 0; i < parms.length; i++) {
1399 output[i] = makePrintable(parms[i]);
1400 }
1401 return String.format(string, parms2);
1402 }
1403
1404 /**
1405 * Check if the object is an array and turn it into a string if it is,
1406 * otherwise unchanged.
1407 *
1408 * @param object
1409 * the object to make printable
1410 * @return a string if it was an array or the original object
1411 */
1412 public static Object makePrintable(Object object) {
1413 if (object == null)
1414 return object;
1415
1416 if (object.getClass().isArray()) {
1417 Object[] array = (Object[]) object;
1418 Object[] output = new Object[array.length];
1419 for (int i = 0; i < array.length; i++) {
1420 output[i] = makePrintable(array[i]);
1421 }
1422 return Arrays.toString(output);
1423 }
1424 return object;
1425 }
1426
1427 public static String append(String... strings) {
1428 List<String> result = Create.list();
1429 for (String s : strings) {
1430 result.addAll(split(s));
1431 }
1432 return join(result);
1433 }
1434
Stuart McCulloch4482c702012-06-15 13:27:53 +00001435 public synchronized Class< ? > getClass(String type, File jar) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001436 CL cl = getLoader();
1437 cl.add(jar.toURI().toURL());
1438 return cl.loadClass(type);
1439 }
1440
1441 public boolean isTrace() {
1442 return current().trace;
1443 }
1444
1445 public static long getDuration(String tm, long dflt) {
1446 if (tm == null)
1447 return dflt;
1448
1449 tm = tm.toUpperCase();
1450 TimeUnit unit = TimeUnit.MILLISECONDS;
1451 Matcher m = Pattern
Stuart McCulloch4482c702012-06-15 13:27:53 +00001452 .compile("\\s*(\\d+)\\s*(NANOSECONDS|MICROSECONDS|MILLISECONDS|SECONDS|MINUTES|HOURS|DAYS)?").matcher(
1453 tm);
Stuart McCullochf3173222012-06-07 21:57:32 +00001454 if (m.matches()) {
1455 long duration = Long.parseLong(tm);
1456 String u = m.group(2);
1457 if (u != null)
1458 unit = TimeUnit.valueOf(u);
1459 duration = TimeUnit.MILLISECONDS.convert(duration, unit);
1460 return duration;
1461 }
1462 return dflt;
1463 }
1464
1465 /**
1466 * Generate a random string, which is guaranteed to be a valid Java
1467 * identifier (first character is an ASCII letter, subsequent characters are
1468 * ASCII letters or numbers). Takes an optional parameter for the length of
1469 * string to generate; default is 8 characters.
1470 */
1471 public String _random(String[] args) {
1472 int numchars = 8;
1473 if (args.length > 1) {
1474 try {
1475 numchars = Integer.parseInt(args[1]);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001476 }
1477 catch (NumberFormatException e) {
1478 throw new IllegalArgumentException("Invalid character count parameter in ${random} macro.");
Stuart McCullochf3173222012-06-07 21:57:32 +00001479 }
1480 }
1481
1482 synchronized (Processor.class) {
1483 if (random == null)
1484 random = new Random();
1485 }
1486
1487 char[] letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001488 char[] alphanums = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
Stuart McCullochf3173222012-06-07 21:57:32 +00001489
1490 char[] array = new char[numchars];
1491 for (int i = 0; i < numchars; i++) {
1492 char c;
1493 if (i == 0)
1494 c = letters[random.nextInt(letters.length)];
1495 else
1496 c = alphanums[random.nextInt(alphanums.length)];
1497 array[i] = c;
1498 }
1499
1500 return new String(array);
1501 }
1502
1503 /**
1504 * Set the current command thread. This must be balanced with the
1505 * {@link #end(Processor)} method. The method returns the previous command
Stuart McCulloch4482c702012-06-15 13:27:53 +00001506 * owner or null. The command owner will receive all warnings and error
1507 * reports.
Stuart McCullochf3173222012-06-07 21:57:32 +00001508 */
1509
1510 protected Processor beginHandleErrors(String message) {
1511 trace("begin %s", message);
1512 Processor previous = current.get();
1513 current.set(this);
1514 return previous;
1515 }
1516
1517 /**
1518 * End a command. Will restore the previous command owner.
1519 *
1520 * @param previous
1521 */
1522 protected void endHandleErrors(Processor previous) {
1523 trace("end");
1524 current.set(previous);
1525 }
1526
1527 public static Executor getExecutor() {
1528 return executor;
1529 }
1530
1531 /**
1532 * These plugins are added to the total list of plugins. The separation is
1533 * necessary because the list of plugins is refreshed now and then so we
1534 * need to be able to add them at any moment in time.
1535 *
1536 * @param plugin
1537 */
1538 public synchronized void addBasicPlugin(Object plugin) {
1539 basicPlugins.add(plugin);
1540 if (plugins != null)
1541 plugins.add(plugin);
1542 }
1543
1544 public synchronized void removeBasicPlugin(Object plugin) {
1545 basicPlugins.remove(plugin);
1546 if (plugins != null)
1547 plugins.remove(plugin);
1548 }
1549
1550 public List<File> getIncluded() {
1551 return included;
1552 }
1553
1554 /**
1555 * Overrides for the Domain class
1556 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001557 @Override
1558 public String get(String key) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001559 return getProperty(key);
1560 }
1561
Stuart McCulloch4482c702012-06-15 13:27:53 +00001562 @Override
1563 public String get(String key, String deflt) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001564 return getProperty(key, deflt);
1565 }
1566
Stuart McCulloch4482c702012-06-15 13:27:53 +00001567 @Override
1568 public void set(String key, String value) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001569 getProperties().setProperty(key, value);
1570 }
1571
Stuart McCulloch4482c702012-06-15 13:27:53 +00001572 @Override
1573 public Iterator<String> iterator() {
Stuart McCullochf3173222012-06-07 21:57:32 +00001574 Set<String> keys = keySet();
1575 final Iterator<String> it = keys.iterator();
1576
1577 return new Iterator<String>() {
1578 String current;
1579
1580 public boolean hasNext() {
1581 return it.hasNext();
1582 }
1583
1584 public String next() {
1585 return current = it.next().toString();
1586 }
1587
1588 public void remove() {
1589 getProperties().remove(current);
1590 }
1591 };
1592 }
1593
1594 public Set<String> keySet() {
1595 Set<String> set;
1596 if (parent == null)
1597 set = Create.set();
1598 else
1599 set = parent.keySet();
1600
1601 for (Object o : properties.keySet())
1602 set.add(o.toString());
1603
1604 return set;
1605 }
1606
1607 /**
1608 * Printout of the status of this processor for toString()
1609 */
1610
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001611 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001612 public String toString() {
1613 try {
1614 StringBuilder sb = new StringBuilder();
1615 report(sb);
1616 return sb.toString();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001617 }
1618 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001619 throw new RuntimeException(e);
1620 }
1621 }
1622
1623 /**
1624 * Utiltity to replace an extension
1625 *
1626 * @param s
1627 * @param extension
1628 * @param newExtension
1629 * @return
1630 */
1631 public String replaceExtension(String s, String extension, String newExtension) {
1632 if (s.endsWith(extension))
1633 s = s.substring(0, s.length() - extension.length());
1634
1635 return s + newExtension;
1636 }
Stuart McCulloch1a890552012-06-29 19:23:09 +00001637
1638 /**
1639 * Create a location object and add it to the locations
1640 *
1641 * @param s
1642 * @return
1643 */
1644 List<Location> locations = new ArrayList<Location>();
1645
1646 static class SetLocationImpl extends Location implements SetLocation {
1647 public SetLocationImpl(String s) {
1648 this.message = s;
1649 }
1650
1651 public SetLocation file(String file) {
1652 this.file = file;
1653 return this;
1654 }
1655
1656 public SetLocation header(String header) {
1657 this.header = header;
1658 return this;
1659 }
1660
1661 public SetLocation context(String context) {
1662 this.context = context;
1663 return this;
1664 }
1665
1666 public SetLocation method(String methodName) {
1667 this.methodName = methodName;
1668 return this;
1669 }
1670
1671 public SetLocation line(int n) {
1672 this.line = n;
1673 return this;
1674 }
1675
1676 public SetLocation reference(String reference) {
1677 this.reference = reference;
1678 return this;
1679 }
1680
1681 }
1682
1683 private SetLocation location(String s) {
1684 SetLocationImpl loc = new SetLocationImpl(s);
1685 locations.add(loc);
1686 return loc;
1687 }
1688
1689 public Location getLocation(String msg) {
1690 for (Location l : locations)
1691 if ((l.message != null) && l.message.equals(msg))
1692 return l;
1693
1694 return null;
1695 }
1696
Stuart McCullochf3173222012-06-07 21:57:32 +00001697}