blob: 6146a001245de56c6d46c627e7755a14858b28d6 [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 });
Stuart McCulloch1f7cf892012-11-22 00:42:55 +0000223 Object result = m.invoke(target, new Object[] {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000224 args
225 });
Stuart McCulloch1f7cf892012-11-22 00:42:55 +0000226 return result == null ? null : result.toString();
Stuart McCullochbb014372012-06-07 21:57:32 +0000227 }
228 catch (NoSuchMethodException e) {
229 // Ignore
230 }
231 catch (InvocationTargetException e) {
232 if (e.getCause() instanceof IllegalArgumentException) {
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000233 domain.error("%s, for cmd: %s, arguments; %s", e.getCause().getMessage(), method,
234 Arrays.toString(args));
Stuart McCulloch2286f232012-06-15 13:27:53 +0000235 } else {
Stuart McCulloch6a046662012-07-19 13:11:20 +0000236 domain.warning("Exception in replace: %s", e.getCause());
Stuart McCullochbb014372012-06-07 21:57:32 +0000237 e.getCause().printStackTrace();
238 }
239 }
240 catch (Exception e) {
241 domain.warning("Exception in replace: " + e + " method=" + method);
242 e.printStackTrace();
243 }
244 }
245 return null;
246 }
247
248 /**
249 * Return a unique list where the duplicates are removed.
250 *
251 * @param args
252 * @return
253 */
254 static String _uniqHelp = "${uniq;<list> ...}";
255
256 public String _uniq(String args[]) {
257 verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
258 Set<String> set = new LinkedHashSet<String>();
259 for (int i = 1; i < args.length; i++) {
260 Processor.split(args[i], set);
261 }
262 return Processor.join(set, ",");
263 }
264
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000265 public String _pathseparator(String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000266 return File.pathSeparator;
267 }
268
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000269 public String _separator(String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000270 return File.separator;
271 }
272
273 public String _filter(String args[]) {
274 return filter(args, false);
275 }
276
277 public String _filterout(String args[]) {
278 return filter(args, true);
279
280 }
281
282 static String _filterHelp = "${%s;<list>;<regex>}";
283
284 String filter(String[] args, boolean include) {
285 verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
286
287 Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
288 Pattern pattern = Pattern.compile(args[2]);
289
290 for (Iterator<String> i = list.iterator(); i.hasNext();) {
291 if (pattern.matcher(i.next()).matches() == include)
292 i.remove();
293 }
294 return Processor.join(list);
295 }
296
297 static String _sortHelp = "${sort;<list>...}";
298
299 public String _sort(String args[]) {
300 verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
301
302 List<String> result = new ArrayList<String>();
303 for (int i = 1; i < args.length; i++) {
304 Processor.split(args[i], result);
305 }
306 Collections.sort(result);
307 return Processor.join(result);
308 }
309
310 static String _joinHelp = "${join;<list>...}";
311
312 public String _join(String args[]) {
313
314 verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
315
316 List<String> result = new ArrayList<String>();
317 for (int i = 1; i < args.length; i++) {
318 Processor.split(args[i], result);
319 }
320 return Processor.join(result);
321 }
322
323 static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";
324
325 public String _if(String args[]) {
326 verifyCommand(args, _ifHelp, null, 3, 4);
327 String condition = args[1].trim();
328 if (!condition.equalsIgnoreCase("false"))
329 if (condition.length() != 0)
330 return args[2];
331
332 if (args.length > 3)
333 return args[3];
Stuart McCullochd4826102012-06-26 16:34:24 +0000334 return "";
Stuart McCullochbb014372012-06-07 21:57:32 +0000335 }
336
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000337 public final static String _nowHelp = "${now;pattern|'long'}, returns current time";
338
339 public Object _now(String args[]) {
340 verifyCommand(args, _nowHelp, null, 1, 2);
341 Date now = new Date();
342
343 if (args.length == 2) {
344 if ("long".equals(args[1]))
345 return now.getTime();
346
347 DateFormat df = new SimpleDateFormat(args[1]);
348 return df.format(now);
349 }
350 return new Date();
Stuart McCullochbb014372012-06-07 21:57:32 +0000351 }
352
353 public final static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
354
355 public String _fmodified(String args[]) throws Exception {
356 verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
357
358 long time = 0;
359 Collection<String> names = new ArrayList<String>();
360 for (int i = 1; i < args.length; i++) {
361 Processor.split(args[i], names);
362 }
363 for (String name : names) {
364 File f = new File(name);
365 if (f.exists() && f.lastModified() > time)
366 time = f.lastModified();
367 }
368 return "" + time;
369 }
370
371 public String _long2date(String args[]) {
372 try {
373 return new Date(Long.parseLong(args[1])).toString();
374 }
375 catch (Exception e) {
376 e.printStackTrace();
377 }
378 return "not a valid long";
379 }
380
381 public String _literal(String args[]) {
382 if (args.length != 2)
383 throw new RuntimeException("Need a value for the ${literal;<value>} macro");
384 return "${" + args[1] + "}";
385 }
386
387 public String _def(String args[]) {
388 if (args.length != 2)
389 throw new RuntimeException("Need a value for the ${def;<value>} macro");
390
391 return domain.getProperty(args[1], "");
392 }
393
394 /**
Stuart McCullochbb014372012-06-07 21:57:32 +0000395 * replace ; <list> ; regex ; replace
396 *
397 * @param args
398 * @return
399 */
400 public String _replace(String args[]) {
401 if (args.length != 4) {
402 domain.warning("Invalid nr of arguments to replace " + Arrays.asList(args));
403 return null;
404 }
405
406 String list[] = args[1].split("\\s*,\\s*");
407 StringBuilder sb = new StringBuilder();
408 String del = "";
409 for (int i = 0; i < list.length; i++) {
410 String element = list[i].trim();
411 if (!element.equals("")) {
412 sb.append(del);
413 sb.append(element.replaceAll(args[2], args[3]));
414 del = ", ";
415 }
416 }
417
418 return sb.toString();
419 }
420
421 public String _warning(String args[]) {
422 for (int i = 1; i < args.length; i++) {
423 domain.warning(process(args[i]));
424 }
425 return "";
426 }
427
428 public String _error(String args[]) {
429 for (int i = 1; i < args.length; i++) {
430 domain.error(process(args[i]));
431 }
432 return "";
433 }
434
435 /**
436 * toclassname ; <path>.class ( , <path>.class ) *
437 *
438 * @param args
439 * @return
440 */
441 static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
442
443 public String _toclassname(String args[]) {
444 verifyCommand(args, _toclassnameHelp, null, 2, 2);
445 Collection<String> paths = Processor.split(args[1]);
446
447 List<String> names = new ArrayList<String>(paths.size());
448 for (String path : paths) {
449 if (path.endsWith(".class")) {
450 String name = path.substring(0, path.length() - 6).replace('/', '.');
451 names.add(name);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000452 } else if (path.endsWith(".java")) {
453 String name = path.substring(0, path.length() - 5).replace('/', '.');
454 names.add(name);
455 } else {
456 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 +0000457 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000458 }
459 return Processor.join(names, ",");
460 }
461
462 /**
463 * toclassname ; <path>.class ( , <path>.class ) *
464 *
465 * @param args
466 * @return
467 */
468
469 static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
470
471 public String _toclasspath(String args[]) {
472 verifyCommand(args, _toclasspathHelp, null, 2, 3);
473 boolean cl = true;
474 if (args.length > 2)
475 cl = Boolean.valueOf(args[2]);
476
477 Collection<String> names = Processor.split(args[1]);
478 Collection<String> paths = new ArrayList<String>(names.size());
479 for (String name : names) {
480 String path = name.replace('.', '/') + (cl ? ".class" : "");
481 paths.add(path);
482 }
483 return Processor.join(paths, ",");
484 }
485
486 public String _dir(String args[]) {
487 if (args.length < 2) {
488 domain.warning("Need at least one file name for ${dir;...}");
489 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000490 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000491 String del = "";
492 StringBuilder sb = new StringBuilder();
493 for (int i = 1; i < args.length; i++) {
494 File f = domain.getFile(args[i]);
495 if (f.exists() && f.getParentFile().exists()) {
496 sb.append(del);
497 sb.append(f.getParentFile().getAbsolutePath());
498 del = ",";
499 }
500 }
501 return sb.toString();
Stuart McCullochbb014372012-06-07 21:57:32 +0000502
503 }
504
505 public String _basename(String args[]) {
506 if (args.length < 2) {
507 domain.warning("Need at least one file name for ${basename;...}");
508 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000509 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000510 String del = "";
511 StringBuilder sb = new StringBuilder();
512 for (int i = 1; i < args.length; i++) {
513 File f = domain.getFile(args[i]);
514 if (f.exists() && f.getParentFile().exists()) {
515 sb.append(del);
516 sb.append(f.getName());
517 del = ",";
518 }
519 }
520 return sb.toString();
Stuart McCullochbb014372012-06-07 21:57:32 +0000521
522 }
523
524 public String _isfile(String args[]) {
525 if (args.length < 2) {
526 domain.warning("Need at least one file name for ${isfile;...}");
527 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000528 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000529 boolean isfile = true;
530 for (int i = 1; i < args.length; i++) {
531 File f = new File(args[i]).getAbsoluteFile();
532 isfile &= f.isFile();
533 }
534 return isfile ? "true" : "false";
Stuart McCullochbb014372012-06-07 21:57:32 +0000535
536 }
537
538 public String _isdir(String args[]) {
539 if (args.length < 2) {
540 domain.warning("Need at least one file name for ${isdir;...}");
541 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000542 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000543 boolean isdir = true;
544 for (int i = 1; i < args.length; i++) {
545 File f = new File(args[i]).getAbsoluteFile();
546 isdir &= f.isDirectory();
547 }
548 return isdir ? "true" : "false";
Stuart McCullochbb014372012-06-07 21:57:32 +0000549
550 }
551
552 public String _tstamp(String args[]) {
553 String format = "yyyyMMddHHmm";
554 long now = System.currentTimeMillis();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000555 TimeZone tz = TimeZone.getTimeZone("UTC");
Stuart McCullochbb014372012-06-07 21:57:32 +0000556
557 if (args.length > 1) {
558 format = args[1];
Stuart McCullochbb014372012-06-07 21:57:32 +0000559 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000560 if (args.length > 2) {
561 tz = TimeZone.getTimeZone(args[2]);
562 }
563 if (args.length > 3) {
564 now = Long.parseLong(args[3]);
565 }
566 if (args.length > 4) {
567 domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
568 }
569
Stuart McCullochbb014372012-06-07 21:57:32 +0000570 SimpleDateFormat sdf = new SimpleDateFormat(format);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000571 sdf.setTimeZone(tz);
572
Stuart McCullochbb014372012-06-07 21:57:32 +0000573 return sdf.format(new Date(now));
574 }
575
576 /**
577 * Wildcard a directory. The lists can contain Instruction that are matched
Stuart McCulloch2286f232012-06-15 13:27:53 +0000578 * against the given directory ${lsr;<dir>;<list>(;<list>)*}
579 * ${lsa;<dir>;<list>(;<list>)*}
Stuart McCullochbb014372012-06-07 21:57:32 +0000580 *
581 * @author aqute
Stuart McCullochbb014372012-06-07 21:57:32 +0000582 */
583
584 public String _lsr(String args[]) {
585 return ls(args, true);
586 }
587
588 public String _lsa(String args[]) {
589 return ls(args, false);
590 }
591
592 String ls(String args[], boolean relative) {
593 if (args.length < 2)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000594 throw new IllegalArgumentException("the ${ls} macro must at least have a directory as parameter");
Stuart McCullochbb014372012-06-07 21:57:32 +0000595
596 File dir = domain.getFile(args[1]);
597 if (!dir.isAbsolute())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000598 throw new IllegalArgumentException("the ${ls} macro directory parameter is not absolute: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000599
600 if (!dir.exists())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000601 throw new IllegalArgumentException("the ${ls} macro directory parameter does not exist: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000602
603 if (!dir.isDirectory())
604 throw new IllegalArgumentException(
Stuart McCulloch2286f232012-06-15 13:27:53 +0000605 "the ${ls} macro directory parameter points to a file instead of a directory: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000606
Stuart McCullochd602fe12012-07-20 16:50:39 +0000607 Collection<File> files = new ArrayList<File>(new SortedList<File>(dir.listFiles()));
Stuart McCullochbb014372012-06-07 21:57:32 +0000608
609 for (int i = 2; i < args.length; i++) {
610 Instructions filters = new Instructions(args[i]);
Stuart McCullochd602fe12012-07-20 16:50:39 +0000611 files = filters.select(files, true);
Stuart McCullochbb014372012-06-07 21:57:32 +0000612 }
613
614 List<String> result = new ArrayList<String>();
615 for (File file : files)
616 result.add(relative ? file.getName() : file.getAbsolutePath());
617
618 return Processor.join(result, ",");
619 }
620
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000621 public String _currenttime(String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000622 return Long.toString(System.currentTimeMillis());
623 }
624
625 /**
626 * Modify a version to set a version policy. Thed policy is a mask that is
627 * mapped to a version.
628 *
629 * <pre>
630 * + increment
631 * - decrement
632 * = maintain
633 * &tilde; discard
634 *
635 * ==+ = maintain major, minor, increment micro, discard qualifier
636 * &tilde;&tilde;&tilde;= = just get the qualifier
637 * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
638 * </pre>
639 *
Stuart McCullochbb014372012-06-07 21:57:32 +0000640 * @param args
641 * @return
642 */
643 final static String MASK_STRING = "[\\-+=~0123456789]{0,3}[=~]?";
644 final static Pattern MASK = Pattern.compile(MASK_STRING);
645 final static String _versionHelp = "${version;<mask>;<version>}, modify a version\n"
646 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch2286f232012-06-15 13:27:53 +0000647 + "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
648 final static Pattern _versionPattern[] = new Pattern[] {
649 null, null, MASK, Verifier.VERSION
650 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000651
652 public String _version(String args[]) {
653 verifyCommand(args, _versionHelp, null, 2, 3);
654
655 String mask = args[1];
656
657 Version version = null;
658 if (args.length >= 3)
659 version = new Version(args[2]);
660
661 return version(version, mask);
662 }
663
664 String version(Version version, String mask) {
665 if (version == null) {
666 String v = domain.getProperty("@");
667 if (v == null) {
668 domain.error(
669 "No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
670 mask);
671 v = "0";
672 }
673 version = new Version(v);
674 }
675
676 StringBuilder sb = new StringBuilder();
677 String del = "";
678
679 for (int i = 0; i < mask.length(); i++) {
680 char c = mask.charAt(i);
681 String result = null;
682 if (c != '~') {
683 if (i == 3) {
684 result = version.getQualifier();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000685 } else if (Character.isDigit(c)) {
686 // Handle masks like +00, =+0
687 result = String.valueOf(c);
688 } else {
689 int x = version.get(i);
690 switch (c) {
691 case '+' :
692 x++;
693 break;
694 case '-' :
695 x--;
696 break;
697 case '=' :
698 break;
699 }
700 result = Integer.toString(x);
Stuart McCullochbb014372012-06-07 21:57:32 +0000701 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000702 if (result != null) {
703 sb.append(del);
704 del = ".";
705 sb.append(result);
706 }
707 }
708 }
709 return sb.toString();
710 }
711
712 /**
713 * Schortcut for version policy
714 *
715 * <pre>
716 * -provide-policy : ${policy;[==,=+)}
717 * -consume-policy : ${policy;[==,+)}
718 * </pre>
719 *
720 * @param args
721 * @return
722 */
723
Stuart McCulloch2286f232012-06-15 13:27:53 +0000724 static Pattern RANGE_MASK = Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
Stuart McCullochbb014372012-06-07 21:57:32 +0000725 static String _rangeHelp = "${range;<mask>[;<version>]}, range for version, if version not specified lookyp ${@}\n"
726 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch2286f232012-06-15 13:27:53 +0000727 + "M ::= '+' | '-' | MQ\n"
728 + "MQ ::= '~' | '='";
729 static Pattern _rangePattern[] = new Pattern[] {
730 null, RANGE_MASK
731 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000732
733 public String _range(String args[]) {
734 verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
735 Version version = null;
736 if (args.length >= 3)
737 version = new Version(args[2]);
738 else {
739 String v = domain.getProperty("@");
740 if (v == null)
741 return null;
742 version = new Version(v);
743 }
744 String spec = args[1];
745
746 Matcher m = RANGE_MASK.matcher(spec);
747 m.matches();
748 String floor = m.group(1);
749 String floorMask = m.group(2);
750 String ceilingMask = m.group(3);
751 String ceiling = m.group(4);
752
753 String left = version(version, floorMask);
754 String right = version(version, ceilingMask);
755 StringBuilder sb = new StringBuilder();
756 sb.append(floor);
757 sb.append(left);
758 sb.append(",");
759 sb.append(right);
760 sb.append(ceiling);
761
762 String s = sb.toString();
763 VersionRange vr = new VersionRange(s);
764 if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000765 domain.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
Stuart McCullochbb014372012-06-07 21:57:32 +0000766 }
767 return sb.toString();
768 }
769
770 /**
771 * System command. Execute a command and insert the result.
772 *
773 * @param args
774 * @param help
775 * @param patterns
776 * @param low
777 * @param high
778 */
779 public String system_internal(boolean allowFail, String args[]) throws Exception {
780 verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
781 + ";<command>[;<in>]}, execute a system command", null, 2, 3);
782 String command = args[1];
783 String input = null;
784
785 if (args.length > 2) {
786 input = args[2];
787 }
788
789 Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
790 if (input != null) {
791 process.getOutputStream().write(input.getBytes("UTF-8"));
792 }
793 process.getOutputStream().close();
794
795 String s = IO.collect(process.getInputStream(), "UTF-8");
796 int exitValue = process.waitFor();
797 if (exitValue != 0)
798 return exitValue + "";
799
800 if (!allowFail && (exitValue != 0)) {
801 domain.error("System command " + command + " failed with " + exitValue);
802 }
803 return s.trim();
804 }
805
806 public String _system(String args[]) throws Exception {
807 return system_internal(false, args);
808 }
809
810 public String _system_allow_fail(String args[]) throws Exception {
811 String result = "";
812 try {
813 result = system_internal(true, args);
814 }
815 catch (Throwable t) {
816 /* ignore */
817 }
818 return result;
819 }
820
821 public String _env(String args[]) {
822 verifyCommand(args, "${env;<name>}, get the environmet variable", null, 2, 2);
823
824 try {
825 return System.getenv(args[1]);
826 }
827 catch (Throwable t) {
828 return null;
829 }
830 }
831
832 /**
833 * Get the contents of a file.
834 *
835 * @param in
836 * @return
837 * @throws IOException
838 */
839
840 public String _cat(String args[]) throws IOException {
841 verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2, 2);
842 File f = domain.getFile(args[1]);
843 if (f.isFile()) {
844 return IO.collect(f);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000845 } else if (f.isDirectory()) {
846 return Arrays.toString(f.list());
847 } else {
848 try {
849 URL url = new URL(args[1]);
850 return IO.collect(url, "UTF-8");
851 }
852 catch (MalformedURLException mfue) {
853 // Ignore here
854 }
855 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000856 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000857 }
858
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000859 public static void verifyCommand(String args[], String help, Pattern[] patterns, int low, int high) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000860 String message = "";
861 if (args.length > high) {
862 message = "too many arguments";
Stuart McCulloch2286f232012-06-15 13:27:53 +0000863 } else if (args.length < low) {
864 message = "too few arguments";
865 } else {
866 for (int i = 0; patterns != null && i < patterns.length && i < args.length; i++) {
867 if (patterns[i] != null) {
868 Matcher m = patterns[i].matcher(args[i]);
869 if (!m.matches())
Stuart McCulloch7adbc952012-07-12 22:12:58 +0000870 message += String.format("Argument %s (%s) does not match %s%n", i, args[i],
Stuart McCulloch2286f232012-06-15 13:27:53 +0000871 patterns[i].pattern());
Stuart McCullochbb014372012-06-07 21:57:32 +0000872 }
873 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000874 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000875 if (message.length() != 0) {
876 StringBuilder sb = new StringBuilder();
877 String del = "${";
878 for (String arg : args) {
879 sb.append(del);
880 sb.append(arg);
881 del = ";";
882 }
883 sb.append("}, is not understood. ");
884 sb.append(message);
885 throw new IllegalArgumentException(sb.toString());
886 }
887 }
888
889 // Helper class to track expansion of variables
890 // on the stack.
891 static class Link {
892 Link previous;
893 String key;
894 Processor start;
895
896 public Link(Processor start, Link previous, String key) {
897 this.previous = previous;
898 this.key = key;
899 this.start = start;
900 }
901
902 public boolean contains(String key) {
903 if (this.key.equals(key))
904 return true;
905
906 if (previous == null)
907 return false;
908
909 return previous.contains(key);
910 }
911
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000912 @Override
Stuart McCullochbb014372012-06-07 21:57:32 +0000913 public String toString() {
914 StringBuilder sb = new StringBuilder();
915 String del = "[";
916 for (Link r = this; r != null; r = r.previous) {
917 sb.append(del);
918 sb.append(r.key);
919 del = ",";
920 }
921 sb.append("]");
922 return sb.toString();
923 }
924 }
925
926 /**
927 * Take all the properties and translate them to actual values. This method
928 * takes the set properties and traverse them over all entries, including
929 * the default properties for that properties. The values no longer contain
930 * macros.
931 *
932 * @return A new Properties with the flattened values
933 */
934 public Properties getFlattenedProperties() {
935 // Some macros only work in a lower processor, so we
936 // do not report unknown macros while flattening
937 flattening = true;
938 try {
939 Properties flattened = new Properties();
940 Properties source = domain.getProperties();
941 for (Enumeration< ? > e = source.propertyNames(); e.hasMoreElements();) {
942 String key = (String) e.nextElement();
943 if (!key.startsWith("_"))
944 if (key.startsWith("-"))
945 flattened.put(key, source.getProperty(key));
946 else
947 flattened.put(key, process(source.getProperty(key)));
948 }
949 return flattened;
950 }
951 finally {
952 flattening = false;
953 }
954 }
955
956 public final static String _fileHelp = "${file;<base>;<paths>...}, create correct OS dependent path";
957
958 public String _osfile(String args[]) {
959 verifyCommand(args, _fileHelp, null, 3, 3);
960 File base = new File(args[1]);
961 File f = Processor.getFile(base, args[2]);
962 return f.getAbsolutePath();
963 }
964
965 public String _path(String args[]) {
966 List<String> list = new ArrayList<String>();
967 for (int i = 1; i < args.length; i++) {
968 list.addAll(Processor.split(args[i]));
969 }
970 return Processor.join(list, File.pathSeparator);
971 }
972
973 public static Properties getParent(Properties p) {
974 try {
975 Field f = Properties.class.getDeclaredField("defaults");
976 f.setAccessible(true);
977 return (Properties) f.get(p);
978 }
979 catch (Exception e) {
980 Field[] fields = Properties.class.getFields();
981 System.err.println(Arrays.toString(fields));
982 return null;
983 }
984 }
985
986 public String process(String line) {
987 return process(line, domain);
988 }
989
990}