blob: be1a4c8f1edb17db4a87d5fcd5ba4773c532f836 [file] [log] [blame]
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochbb014372012-06-07 21:57:32 +00002
3import java.io.*;
4import java.lang.reflect.*;
5import java.net.*;
6import java.text.*;
7import java.util.*;
8import java.util.regex.*;
9
Stuart McCulloch6a046662012-07-19 13:11:20 +000010import aQute.bnd.version.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000011import aQute.lib.collections.*;
12import aQute.lib.io.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000013
14/**
15 * Provide a macro processor. This processor can replace variables in strings
16 * based on a properties and a domain. The domain can implement functions that
17 * start with a "_" and take args[], the names of these functions are available
18 * as functions in the macro processor (without the _). Macros can nest to any
Stuart McCulloch2286f232012-06-15 13:27:53 +000019 * depth but may not contain loops. Add POSIX macros: ${#parameter} String
20 * length. ${parameter%word} Remove smallest suffix pattern. ${parameter%%word}
21 * Remove largest suffix pattern. ${parameter#word} Remove smallest prefix
22 * pattern. ${parameter##word} Remove largest prefix pattern.
Stuart McCullochbb014372012-06-07 21:57:32 +000023 */
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +000024public class Macro {
Stuart McCullochbb014372012-06-07 21:57:32 +000025 Processor domain;
26 Object targets[];
27 boolean flattening;
28
29 public Macro(Processor domain, Object... targets) {
30 this.domain = domain;
31 this.targets = targets;
32 if (targets != null) {
33 for (Object o : targets) {
34 assert o != null;
35 }
36 }
37 }
38
39 public String process(String line, Processor source) {
40 return process(line, new Link(source, null, line));
41 }
42
43 String process(String line, Link link) {
44 StringBuilder sb = new StringBuilder();
45 process(line, 0, '\u0000', '\u0000', sb, link);
46 return sb.toString();
47 }
48
49 int process(CharSequence org, int index, char begin, char end, StringBuilder result, Link link) {
50 StringBuilder line = new StringBuilder(org);
51 int nesting = 1;
52
53 StringBuilder variable = new StringBuilder();
54 outer: while (index < line.length()) {
55 char c1 = line.charAt(index++);
56 if (c1 == end) {
57 if (--nesting == 0) {
58 result.append(replace(variable.toString(), link));
59 return index;
60 }
Stuart McCulloch2286f232012-06-15 13:27:53 +000061 } else if (c1 == begin)
62 nesting++;
63 else if (c1 == '\\' && index < line.length() - 1 && line.charAt(index) == '$') {
64 // remove the escape backslash and interpret the dollar
65 // as a
66 // literal
67 index++;
68 variable.append('$');
69 continue outer;
70 } else if (c1 == '$' && index < line.length() - 2) {
71 char c2 = line.charAt(index);
72 char terminator = getTerminator(c2);
73 if (terminator != 0) {
74 index = process(line, index + 1, c2, terminator, variable, link);
75 continue outer;
76 }
77 } else if (c1 == '.' && index < line.length() && line.charAt(index) == '/') {
78 // Found the sequence ./
79 if (index == 1 || Character.isWhitespace(line.charAt(index - 2))) {
80 // make sure it is preceded by whitespace or starts at begin
81 index++;
82 variable.append(domain.getBase().getAbsolutePath());
83 variable.append('/');
84 continue outer;
85 }
Stuart McCullochbb014372012-06-07 21:57:32 +000086 }
Stuart McCullochbb014372012-06-07 21:57:32 +000087 variable.append(c1);
88 }
89 result.append(variable);
90 return index;
91 }
92
93 public static char getTerminator(char c) {
94 switch (c) {
95 case '(' :
96 return ')';
97 case '[' :
98 return ']';
99 case '{' :
100 return '}';
101 case '<' :
102 return '>';
103 case '\u00ab' : // Guillemet double << >>
104 return '\u00bb';
105 case '\u2039' : // Guillemet single
106 return '\u203a';
107 }
108 return 0;
109 }
110
111 protected String replace(String key, Link link) {
112 if (link != null && link.contains(key))
113 return "${infinite:" + link.toString() + "}";
114
115 if (key != null) {
116 key = key.trim();
117 if (key.length() > 0) {
118 Processor source = domain;
119 String value = null;
120
121 if (key.indexOf(';') < 0) {
122 Instruction ins = new Instruction(key);
123 if (!ins.isLiteral()) {
124 SortedList<String> sortedList = SortedList.fromIterator(domain.iterator());
125 StringBuilder sb = new StringBuilder();
126 String del = "";
127 for (String k : sortedList) {
128 if (ins.matches(k)) {
129 String v = replace(k, new Link(source, link, key));
130 if (v != null) {
131 sb.append(del);
132 del = ",";
133 sb.append(v);
134 }
135 }
136 }
137 return sb.toString();
138 }
139 }
140 while (value == null && source != null) {
141 value = source.getProperties().getProperty(key);
142 source = source.getParent();
143 }
144
145 if (value != null)
146 return process(value, new Link(source, link, key));
147
148 value = doCommands(key, link);
149 if (value != null)
150 return process(value, new Link(source, link, key));
151
152 if (key != null && key.trim().length() > 0) {
153 value = System.getProperty(key);
154 if (value != null)
155 return value;
156 }
157 if (!flattening && !key.equals("@"))
158 domain.warning("No translation found for macro: " + key);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000159 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000160 domain.warning("Found empty macro key");
161 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000162 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000163 domain.warning("Found null macro key");
164 }
165 return "${" + key + "}";
166 }
167
168 /**
169 * Parse the key as a command. A command consist of parameters separated by
170 * ':'.
171 *
172 * @param key
173 * @return
174 */
175 static Pattern commands = Pattern.compile("(?<!\\\\);");
176
177 private String doCommands(String key, Link source) {
178 String[] args = commands.split(key);
179 if (args == null || args.length == 0)
180 return null;
181
182 for (int i = 0; i < args.length; i++)
183 if (args[i].indexOf('\\') >= 0)
184 args[i] = args[i].replaceAll("\\\\;", ";");
185
186 if (args[0].startsWith("^")) {
187 String varname = args[0].substring(1).trim();
188
189 Processor parent = source.start.getParent();
190 if (parent != null)
191 return parent.getProperty(varname);
Stuart McCullochd4826102012-06-26 16:34:24 +0000192 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000193 }
194
195 Processor rover = domain;
196 while (rover != null) {
197 String result = doCommand(rover, args[0], args);
198 if (result != null)
199 return result;
200
201 rover = rover.getParent();
202 }
203
204 for (int i = 0; targets != null && i < targets.length; i++) {
205 String result = doCommand(targets[i], args[0], args);
206 if (result != null)
207 return result;
208 }
209
210 return doCommand(this, args[0], args);
211 }
212
213 private String doCommand(Object target, String method, String[] args) {
214 if (target == null)
215 ; // System.err.println("Huh? Target should never be null " +
216 // domain);
217 else {
218 String cname = "_" + method.replaceAll("-", "_");
219 try {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000220 Method m = target.getClass().getMethod(cname, new Class[] {
221 String[].class
222 });
223 return (String) m.invoke(target, new Object[] {
224 args
225 });
Stuart McCullochbb014372012-06-07 21:57:32 +0000226 }
227 catch (NoSuchMethodException e) {
228 // Ignore
229 }
230 catch (InvocationTargetException e) {
231 if (e.getCause() instanceof IllegalArgumentException) {
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000232 domain.error("%s, for cmd: %s, arguments; %s", e.getCause().getMessage(), method,
233 Arrays.toString(args));
Stuart McCulloch2286f232012-06-15 13:27:53 +0000234 } else {
Stuart McCulloch6a046662012-07-19 13:11:20 +0000235 domain.warning("Exception in replace: %s", e.getCause());
Stuart McCullochbb014372012-06-07 21:57:32 +0000236 e.getCause().printStackTrace();
237 }
238 }
239 catch (Exception e) {
240 domain.warning("Exception in replace: " + e + " method=" + method);
241 e.printStackTrace();
242 }
243 }
244 return null;
245 }
246
247 /**
248 * Return a unique list where the duplicates are removed.
249 *
250 * @param args
251 * @return
252 */
253 static String _uniqHelp = "${uniq;<list> ...}";
254
255 public String _uniq(String args[]) {
256 verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
257 Set<String> set = new LinkedHashSet<String>();
258 for (int i = 1; i < args.length; i++) {
259 Processor.split(args[i], set);
260 }
261 return Processor.join(set, ",");
262 }
263
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000264 public String _pathseparator(String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000265 return File.pathSeparator;
266 }
267
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000268 public String _separator(String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000269 return File.separator;
270 }
271
272 public String _filter(String args[]) {
273 return filter(args, false);
274 }
275
276 public String _filterout(String args[]) {
277 return filter(args, true);
278
279 }
280
281 static String _filterHelp = "${%s;<list>;<regex>}";
282
283 String filter(String[] args, boolean include) {
284 verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
285
286 Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
287 Pattern pattern = Pattern.compile(args[2]);
288
289 for (Iterator<String> i = list.iterator(); i.hasNext();) {
290 if (pattern.matcher(i.next()).matches() == include)
291 i.remove();
292 }
293 return Processor.join(list);
294 }
295
296 static String _sortHelp = "${sort;<list>...}";
297
298 public String _sort(String args[]) {
299 verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
300
301 List<String> result = new ArrayList<String>();
302 for (int i = 1; i < args.length; i++) {
303 Processor.split(args[i], result);
304 }
305 Collections.sort(result);
306 return Processor.join(result);
307 }
308
309 static String _joinHelp = "${join;<list>...}";
310
311 public String _join(String args[]) {
312
313 verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
314
315 List<String> result = new ArrayList<String>();
316 for (int i = 1; i < args.length; i++) {
317 Processor.split(args[i], result);
318 }
319 return Processor.join(result);
320 }
321
322 static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";
323
324 public String _if(String args[]) {
325 verifyCommand(args, _ifHelp, null, 3, 4);
326 String condition = args[1].trim();
327 if (!condition.equalsIgnoreCase("false"))
328 if (condition.length() != 0)
329 return args[2];
330
331 if (args.length > 3)
332 return args[3];
Stuart McCullochd4826102012-06-26 16:34:24 +0000333 return "";
Stuart McCullochbb014372012-06-07 21:57:32 +0000334 }
335
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000336 public final static String _nowHelp = "${now;pattern|'long'}, returns current time";
337
338 public Object _now(String args[]) {
339 verifyCommand(args, _nowHelp, null, 1, 2);
340 Date now = new Date();
341
342 if (args.length == 2) {
343 if ("long".equals(args[1]))
344 return now.getTime();
345
346 DateFormat df = new SimpleDateFormat(args[1]);
347 return df.format(now);
348 }
349 return new Date();
Stuart McCullochbb014372012-06-07 21:57:32 +0000350 }
351
352 public final static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
353
354 public String _fmodified(String args[]) throws Exception {
355 verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
356
357 long time = 0;
358 Collection<String> names = new ArrayList<String>();
359 for (int i = 1; i < args.length; i++) {
360 Processor.split(args[i], names);
361 }
362 for (String name : names) {
363 File f = new File(name);
364 if (f.exists() && f.lastModified() > time)
365 time = f.lastModified();
366 }
367 return "" + time;
368 }
369
370 public String _long2date(String args[]) {
371 try {
372 return new Date(Long.parseLong(args[1])).toString();
373 }
374 catch (Exception e) {
375 e.printStackTrace();
376 }
377 return "not a valid long";
378 }
379
380 public String _literal(String args[]) {
381 if (args.length != 2)
382 throw new RuntimeException("Need a value for the ${literal;<value>} macro");
383 return "${" + args[1] + "}";
384 }
385
386 public String _def(String args[]) {
387 if (args.length != 2)
388 throw new RuntimeException("Need a value for the ${def;<value>} macro");
389
390 return domain.getProperty(args[1], "");
391 }
392
393 /**
Stuart McCullochbb014372012-06-07 21:57:32 +0000394 * replace ; <list> ; regex ; replace
395 *
396 * @param args
397 * @return
398 */
399 public String _replace(String args[]) {
400 if (args.length != 4) {
401 domain.warning("Invalid nr of arguments to replace " + Arrays.asList(args));
402 return null;
403 }
404
405 String list[] = args[1].split("\\s*,\\s*");
406 StringBuilder sb = new StringBuilder();
407 String del = "";
408 for (int i = 0; i < list.length; i++) {
409 String element = list[i].trim();
410 if (!element.equals("")) {
411 sb.append(del);
412 sb.append(element.replaceAll(args[2], args[3]));
413 del = ", ";
414 }
415 }
416
417 return sb.toString();
418 }
419
420 public String _warning(String args[]) {
421 for (int i = 1; i < args.length; i++) {
422 domain.warning(process(args[i]));
423 }
424 return "";
425 }
426
427 public String _error(String args[]) {
428 for (int i = 1; i < args.length; i++) {
429 domain.error(process(args[i]));
430 }
431 return "";
432 }
433
434 /**
435 * toclassname ; <path>.class ( , <path>.class ) *
436 *
437 * @param args
438 * @return
439 */
440 static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
441
442 public String _toclassname(String args[]) {
443 verifyCommand(args, _toclassnameHelp, null, 2, 2);
444 Collection<String> paths = Processor.split(args[1]);
445
446 List<String> names = new ArrayList<String>(paths.size());
447 for (String path : paths) {
448 if (path.endsWith(".class")) {
449 String name = path.substring(0, path.length() - 6).replace('/', '.');
450 names.add(name);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000451 } else if (path.endsWith(".java")) {
452 String name = path.substring(0, path.length() - 5).replace('/', '.');
453 names.add(name);
454 } else {
455 domain.warning("in toclassname, " + args[1] + " is not a class path because it does not end in .class");
Stuart McCullochbb014372012-06-07 21:57:32 +0000456 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000457 }
458 return Processor.join(names, ",");
459 }
460
461 /**
462 * toclassname ; <path>.class ( , <path>.class ) *
463 *
464 * @param args
465 * @return
466 */
467
468 static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
469
470 public String _toclasspath(String args[]) {
471 verifyCommand(args, _toclasspathHelp, null, 2, 3);
472 boolean cl = true;
473 if (args.length > 2)
474 cl = Boolean.valueOf(args[2]);
475
476 Collection<String> names = Processor.split(args[1]);
477 Collection<String> paths = new ArrayList<String>(names.size());
478 for (String name : names) {
479 String path = name.replace('.', '/') + (cl ? ".class" : "");
480 paths.add(path);
481 }
482 return Processor.join(paths, ",");
483 }
484
485 public String _dir(String args[]) {
486 if (args.length < 2) {
487 domain.warning("Need at least one file name for ${dir;...}");
488 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000489 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000490 String del = "";
491 StringBuilder sb = new StringBuilder();
492 for (int i = 1; i < args.length; i++) {
493 File f = domain.getFile(args[i]);
494 if (f.exists() && f.getParentFile().exists()) {
495 sb.append(del);
496 sb.append(f.getParentFile().getAbsolutePath());
497 del = ",";
498 }
499 }
500 return sb.toString();
Stuart McCullochbb014372012-06-07 21:57:32 +0000501
502 }
503
504 public String _basename(String args[]) {
505 if (args.length < 2) {
506 domain.warning("Need at least one file name for ${basename;...}");
507 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000508 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000509 String del = "";
510 StringBuilder sb = new StringBuilder();
511 for (int i = 1; i < args.length; i++) {
512 File f = domain.getFile(args[i]);
513 if (f.exists() && f.getParentFile().exists()) {
514 sb.append(del);
515 sb.append(f.getName());
516 del = ",";
517 }
518 }
519 return sb.toString();
Stuart McCullochbb014372012-06-07 21:57:32 +0000520
521 }
522
523 public String _isfile(String args[]) {
524 if (args.length < 2) {
525 domain.warning("Need at least one file name for ${isfile;...}");
526 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000527 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000528 boolean isfile = true;
529 for (int i = 1; i < args.length; i++) {
530 File f = new File(args[i]).getAbsoluteFile();
531 isfile &= f.isFile();
532 }
533 return isfile ? "true" : "false";
Stuart McCullochbb014372012-06-07 21:57:32 +0000534
535 }
536
537 public String _isdir(String args[]) {
538 if (args.length < 2) {
539 domain.warning("Need at least one file name for ${isdir;...}");
540 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000541 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000542 boolean isdir = true;
543 for (int i = 1; i < args.length; i++) {
544 File f = new File(args[i]).getAbsoluteFile();
545 isdir &= f.isDirectory();
546 }
547 return isdir ? "true" : "false";
Stuart McCullochbb014372012-06-07 21:57:32 +0000548
549 }
550
551 public String _tstamp(String args[]) {
552 String format = "yyyyMMddHHmm";
553 long now = System.currentTimeMillis();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000554 TimeZone tz = TimeZone.getTimeZone("UTC");
Stuart McCullochbb014372012-06-07 21:57:32 +0000555
556 if (args.length > 1) {
557 format = args[1];
Stuart McCullochbb014372012-06-07 21:57:32 +0000558 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000559 if (args.length > 2) {
560 tz = TimeZone.getTimeZone(args[2]);
561 }
562 if (args.length > 3) {
563 now = Long.parseLong(args[3]);
564 }
565 if (args.length > 4) {
566 domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
567 }
568
Stuart McCullochbb014372012-06-07 21:57:32 +0000569 SimpleDateFormat sdf = new SimpleDateFormat(format);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000570 sdf.setTimeZone(tz);
571
Stuart McCullochbb014372012-06-07 21:57:32 +0000572 return sdf.format(new Date(now));
573 }
574
575 /**
576 * Wildcard a directory. The lists can contain Instruction that are matched
Stuart McCulloch2286f232012-06-15 13:27:53 +0000577 * against the given directory ${lsr;<dir>;<list>(;<list>)*}
578 * ${lsa;<dir>;<list>(;<list>)*}
Stuart McCullochbb014372012-06-07 21:57:32 +0000579 *
580 * @author aqute
Stuart McCullochbb014372012-06-07 21:57:32 +0000581 */
582
583 public String _lsr(String args[]) {
584 return ls(args, true);
585 }
586
587 public String _lsa(String args[]) {
588 return ls(args, false);
589 }
590
591 String ls(String args[], boolean relative) {
592 if (args.length < 2)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000593 throw new IllegalArgumentException("the ${ls} macro must at least have a directory as parameter");
Stuart McCullochbb014372012-06-07 21:57:32 +0000594
595 File dir = domain.getFile(args[1]);
596 if (!dir.isAbsolute())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000597 throw new IllegalArgumentException("the ${ls} macro directory parameter is not absolute: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000598
599 if (!dir.exists())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000600 throw new IllegalArgumentException("the ${ls} macro directory parameter does not exist: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000601
602 if (!dir.isDirectory())
603 throw new IllegalArgumentException(
Stuart McCulloch2286f232012-06-15 13:27:53 +0000604 "the ${ls} macro directory parameter points to a file instead of a directory: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000605
Stuart McCullochd602fe12012-07-20 16:50:39 +0000606 Collection<File> files = new ArrayList<File>(new SortedList<File>(dir.listFiles()));
Stuart McCullochbb014372012-06-07 21:57:32 +0000607
608 for (int i = 2; i < args.length; i++) {
609 Instructions filters = new Instructions(args[i]);
Stuart McCullochd602fe12012-07-20 16:50:39 +0000610 files = filters.select(files, true);
Stuart McCullochbb014372012-06-07 21:57:32 +0000611 }
612
613 List<String> result = new ArrayList<String>();
614 for (File file : files)
615 result.add(relative ? file.getName() : file.getAbsolutePath());
616
617 return Processor.join(result, ",");
618 }
619
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000620 public String _currenttime(String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000621 return Long.toString(System.currentTimeMillis());
622 }
623
624 /**
625 * Modify a version to set a version policy. Thed policy is a mask that is
626 * mapped to a version.
627 *
628 * <pre>
629 * + increment
630 * - decrement
631 * = maintain
632 * &tilde; discard
633 *
634 * ==+ = maintain major, minor, increment micro, discard qualifier
635 * &tilde;&tilde;&tilde;= = just get the qualifier
636 * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
637 * </pre>
638 *
Stuart McCullochbb014372012-06-07 21:57:32 +0000639 * @param args
640 * @return
641 */
642 final static String MASK_STRING = "[\\-+=~0123456789]{0,3}[=~]?";
643 final static Pattern MASK = Pattern.compile(MASK_STRING);
644 final static String _versionHelp = "${version;<mask>;<version>}, modify a version\n"
645 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch2286f232012-06-15 13:27:53 +0000646 + "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
647 final static Pattern _versionPattern[] = new Pattern[] {
648 null, null, MASK, Verifier.VERSION
649 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000650
651 public String _version(String args[]) {
652 verifyCommand(args, _versionHelp, null, 2, 3);
653
654 String mask = args[1];
655
656 Version version = null;
657 if (args.length >= 3)
658 version = new Version(args[2]);
659
660 return version(version, mask);
661 }
662
663 String version(Version version, String mask) {
664 if (version == null) {
665 String v = domain.getProperty("@");
666 if (v == null) {
667 domain.error(
668 "No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
669 mask);
670 v = "0";
671 }
672 version = new Version(v);
673 }
674
675 StringBuilder sb = new StringBuilder();
676 String del = "";
677
678 for (int i = 0; i < mask.length(); i++) {
679 char c = mask.charAt(i);
680 String result = null;
681 if (c != '~') {
682 if (i == 3) {
683 result = version.getQualifier();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000684 } else if (Character.isDigit(c)) {
685 // Handle masks like +00, =+0
686 result = String.valueOf(c);
687 } else {
688 int x = version.get(i);
689 switch (c) {
690 case '+' :
691 x++;
692 break;
693 case '-' :
694 x--;
695 break;
696 case '=' :
697 break;
698 }
699 result = Integer.toString(x);
Stuart McCullochbb014372012-06-07 21:57:32 +0000700 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000701 if (result != null) {
702 sb.append(del);
703 del = ".";
704 sb.append(result);
705 }
706 }
707 }
708 return sb.toString();
709 }
710
711 /**
712 * Schortcut for version policy
713 *
714 * <pre>
715 * -provide-policy : ${policy;[==,=+)}
716 * -consume-policy : ${policy;[==,+)}
717 * </pre>
718 *
719 * @param args
720 * @return
721 */
722
Stuart McCulloch2286f232012-06-15 13:27:53 +0000723 static Pattern RANGE_MASK = Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
Stuart McCullochbb014372012-06-07 21:57:32 +0000724 static String _rangeHelp = "${range;<mask>[;<version>]}, range for version, if version not specified lookyp ${@}\n"
725 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch2286f232012-06-15 13:27:53 +0000726 + "M ::= '+' | '-' | MQ\n"
727 + "MQ ::= '~' | '='";
728 static Pattern _rangePattern[] = new Pattern[] {
729 null, RANGE_MASK
730 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000731
732 public String _range(String args[]) {
733 verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
734 Version version = null;
735 if (args.length >= 3)
736 version = new Version(args[2]);
737 else {
738 String v = domain.getProperty("@");
739 if (v == null)
740 return null;
741 version = new Version(v);
742 }
743 String spec = args[1];
744
745 Matcher m = RANGE_MASK.matcher(spec);
746 m.matches();
747 String floor = m.group(1);
748 String floorMask = m.group(2);
749 String ceilingMask = m.group(3);
750 String ceiling = m.group(4);
751
752 String left = version(version, floorMask);
753 String right = version(version, ceilingMask);
754 StringBuilder sb = new StringBuilder();
755 sb.append(floor);
756 sb.append(left);
757 sb.append(",");
758 sb.append(right);
759 sb.append(ceiling);
760
761 String s = sb.toString();
762 VersionRange vr = new VersionRange(s);
763 if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000764 domain.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
Stuart McCullochbb014372012-06-07 21:57:32 +0000765 }
766 return sb.toString();
767 }
768
769 /**
770 * System command. Execute a command and insert the result.
771 *
772 * @param args
773 * @param help
774 * @param patterns
775 * @param low
776 * @param high
777 */
778 public String system_internal(boolean allowFail, String args[]) throws Exception {
779 verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
780 + ";<command>[;<in>]}, execute a system command", null, 2, 3);
781 String command = args[1];
782 String input = null;
783
784 if (args.length > 2) {
785 input = args[2];
786 }
787
788 Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
789 if (input != null) {
790 process.getOutputStream().write(input.getBytes("UTF-8"));
791 }
792 process.getOutputStream().close();
793
794 String s = IO.collect(process.getInputStream(), "UTF-8");
795 int exitValue = process.waitFor();
796 if (exitValue != 0)
797 return exitValue + "";
798
799 if (!allowFail && (exitValue != 0)) {
800 domain.error("System command " + command + " failed with " + exitValue);
801 }
802 return s.trim();
803 }
804
805 public String _system(String args[]) throws Exception {
806 return system_internal(false, args);
807 }
808
809 public String _system_allow_fail(String args[]) throws Exception {
810 String result = "";
811 try {
812 result = system_internal(true, args);
813 }
814 catch (Throwable t) {
815 /* ignore */
816 }
817 return result;
818 }
819
820 public String _env(String args[]) {
821 verifyCommand(args, "${env;<name>}, get the environmet variable", null, 2, 2);
822
823 try {
824 return System.getenv(args[1]);
825 }
826 catch (Throwable t) {
827 return null;
828 }
829 }
830
831 /**
832 * Get the contents of a file.
833 *
834 * @param in
835 * @return
836 * @throws IOException
837 */
838
839 public String _cat(String args[]) throws IOException {
840 verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2, 2);
841 File f = domain.getFile(args[1]);
842 if (f.isFile()) {
843 return IO.collect(f);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000844 } else if (f.isDirectory()) {
845 return Arrays.toString(f.list());
846 } else {
847 try {
848 URL url = new URL(args[1]);
849 return IO.collect(url, "UTF-8");
850 }
851 catch (MalformedURLException mfue) {
852 // Ignore here
853 }
854 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000855 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000856 }
857
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000858 public static void verifyCommand(String args[], String help, Pattern[] patterns, int low, int high) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000859 String message = "";
860 if (args.length > high) {
861 message = "too many arguments";
Stuart McCulloch2286f232012-06-15 13:27:53 +0000862 } else if (args.length < low) {
863 message = "too few arguments";
864 } else {
865 for (int i = 0; patterns != null && i < patterns.length && i < args.length; i++) {
866 if (patterns[i] != null) {
867 Matcher m = patterns[i].matcher(args[i]);
868 if (!m.matches())
Stuart McCulloch7adbc952012-07-12 22:12:58 +0000869 message += String.format("Argument %s (%s) does not match %s%n", i, args[i],
Stuart McCulloch2286f232012-06-15 13:27:53 +0000870 patterns[i].pattern());
Stuart McCullochbb014372012-06-07 21:57:32 +0000871 }
872 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000873 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000874 if (message.length() != 0) {
875 StringBuilder sb = new StringBuilder();
876 String del = "${";
877 for (String arg : args) {
878 sb.append(del);
879 sb.append(arg);
880 del = ";";
881 }
882 sb.append("}, is not understood. ");
883 sb.append(message);
884 throw new IllegalArgumentException(sb.toString());
885 }
886 }
887
888 // Helper class to track expansion of variables
889 // on the stack.
890 static class Link {
891 Link previous;
892 String key;
893 Processor start;
894
895 public Link(Processor start, Link previous, String key) {
896 this.previous = previous;
897 this.key = key;
898 this.start = start;
899 }
900
901 public boolean contains(String key) {
902 if (this.key.equals(key))
903 return true;
904
905 if (previous == null)
906 return false;
907
908 return previous.contains(key);
909 }
910
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000911 @Override
Stuart McCullochbb014372012-06-07 21:57:32 +0000912 public String toString() {
913 StringBuilder sb = new StringBuilder();
914 String del = "[";
915 for (Link r = this; r != null; r = r.previous) {
916 sb.append(del);
917 sb.append(r.key);
918 del = ",";
919 }
920 sb.append("]");
921 return sb.toString();
922 }
923 }
924
925 /**
926 * Take all the properties and translate them to actual values. This method
927 * takes the set properties and traverse them over all entries, including
928 * the default properties for that properties. The values no longer contain
929 * macros.
930 *
931 * @return A new Properties with the flattened values
932 */
933 public Properties getFlattenedProperties() {
934 // Some macros only work in a lower processor, so we
935 // do not report unknown macros while flattening
936 flattening = true;
937 try {
938 Properties flattened = new Properties();
939 Properties source = domain.getProperties();
940 for (Enumeration< ? > e = source.propertyNames(); e.hasMoreElements();) {
941 String key = (String) e.nextElement();
942 if (!key.startsWith("_"))
943 if (key.startsWith("-"))
944 flattened.put(key, source.getProperty(key));
945 else
946 flattened.put(key, process(source.getProperty(key)));
947 }
948 return flattened;
949 }
950 finally {
951 flattening = false;
952 }
953 }
954
955 public final static String _fileHelp = "${file;<base>;<paths>...}, create correct OS dependent path";
956
957 public String _osfile(String args[]) {
958 verifyCommand(args, _fileHelp, null, 3, 3);
959 File base = new File(args[1]);
960 File f = Processor.getFile(base, args[2]);
961 return f.getAbsolutePath();
962 }
963
964 public String _path(String args[]) {
965 List<String> list = new ArrayList<String>();
966 for (int i = 1; i < args.length; i++) {
967 list.addAll(Processor.split(args[i]));
968 }
969 return Processor.join(list, File.pathSeparator);
970 }
971
972 public static Properties getParent(Properties p) {
973 try {
974 Field f = Properties.class.getDeclaredField("defaults");
975 f.setAccessible(true);
976 return (Properties) f.get(p);
977 }
978 catch (Exception e) {
979 Field[] fields = Properties.class.getFields();
980 System.err.println(Arrays.toString(fields));
981 return null;
982 }
983 }
984
985 public String process(String line) {
986 return process(line, domain);
987 }
988
989}