aQute.bnd.osgi.Macro Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bndlib Show documentation
Show all versions of bndlib Show documentation
The bndlib project is a general library to be used with OSGi bundles. It contains
lots of cool functionality that calculates dependencies, etc.
package aQute.bnd.osgi;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.regex.*;
import aQute.bnd.version.*;
import aQute.lib.collections.*;
import aQute.lib.io.*;
/**
* Provide a macro processor. This processor can replace variables in strings
* based on a properties and a domain. The domain can implement functions that
* start with a "_" and take args[], the names of these functions are available
* as functions in the macro processor (without the _). Macros can nest to any
* depth but may not contain loops. Add POSIX macros: ${#parameter} String
* length. ${parameter%word} Remove smallest suffix pattern. ${parameter%%word}
* Remove largest suffix pattern. ${parameter#word} Remove smallest prefix
* pattern. ${parameter##word} Remove largest prefix pattern.
*/
public class Macro {
Processor domain;
Object targets[];
boolean flattening;
public Macro(Processor domain, Object... targets) {
this.domain = domain;
this.targets = targets;
if (targets != null) {
for (Object o : targets) {
assert o != null;
}
}
}
public String process(String line, Processor source) {
return process(line, new Link(source, null, line));
}
String process(String line, Link link) {
StringBuilder sb = new StringBuilder();
process(line, 0, '\u0000', '\u0000', sb, link);
return sb.toString();
}
int process(CharSequence org, int index, char begin, char end, StringBuilder result, Link link) {
StringBuilder line = new StringBuilder(org);
int nesting = 1;
StringBuilder variable = new StringBuilder();
outer: while (index < line.length()) {
char c1 = line.charAt(index++);
if (c1 == end) {
if (--nesting == 0) {
result.append(replace(variable.toString(), link));
return index;
}
} else if (c1 == begin)
nesting++;
else if (c1 == '\\' && index < line.length() - 1 && line.charAt(index) == '$') {
// remove the escape backslash and interpret the dollar
// as a
// literal
index++;
variable.append('$');
continue outer;
} else if (c1 == '$' && index < line.length() - 2) {
char c2 = line.charAt(index);
char terminator = getTerminator(c2);
if (terminator != 0) {
index = process(line, index + 1, c2, terminator, variable, link);
continue outer;
}
} else if (c1 == '.' && index < line.length() && line.charAt(index) == '/') {
// Found the sequence ./
if (index == 1 || Character.isWhitespace(line.charAt(index - 2))) {
// make sure it is preceded by whitespace or starts at begin
index++;
variable.append(domain.getBase().getAbsolutePath());
variable.append('/');
continue outer;
}
}
variable.append(c1);
}
result.append(variable);
return index;
}
public static char getTerminator(char c) {
switch (c) {
case '(' :
return ')';
case '[' :
return ']';
case '{' :
return '}';
case '<' :
return '>';
case '\u00ab' : // Guillemet double << >>
return '\u00bb';
case '\u2039' : // Guillemet single
return '\u203a';
}
return 0;
}
protected String replace(String key, Link link) {
if (link != null && link.contains(key))
return "${infinite:" + link.toString() + "}";
if (key != null) {
key = key.trim();
if (key.length() > 0) {
Processor source = domain;
String value = null;
if (key.indexOf(';') < 0) {
Instruction ins = new Instruction(key);
if (!ins.isLiteral()) {
SortedList sortedList = SortedList.fromIterator(domain.iterator());
StringBuilder sb = new StringBuilder();
String del = "";
for (String k : sortedList) {
if (ins.matches(k)) {
String v = replace(k, new Link(source, link, key));
if (v != null) {
sb.append(del);
del = ",";
sb.append(v);
}
}
}
return sb.toString();
}
}
while (value == null && source != null) {
value = source.getProperties().getProperty(key);
source = source.getParent();
}
if (value != null)
return process(value, new Link(source, link, key));
value = doCommands(key, link);
if (value != null)
return process(value, new Link(source, link, key));
if (key != null && key.trim().length() > 0) {
value = System.getProperty(key);
if (value != null)
return value;
}
if (!flattening && !key.equals("@"))
domain.warning("No translation found for macro: " + key);
} else {
domain.warning("Found empty macro key");
}
} else {
domain.warning("Found null macro key");
}
return "${" + key + "}";
}
/**
* Parse the key as a command. A command consist of parameters separated by
* ':'.
*
* @param key
* @return
*/
static Pattern commands = Pattern.compile("(?= 0)
args[i] = args[i].replaceAll("\\\\;", ";");
if (args[0].startsWith("^")) {
String varname = args[0].substring(1).trim();
Processor parent = source.start.getParent();
if (parent != null)
return parent.getProperty(varname);
return null;
}
Processor rover = domain;
while (rover != null) {
String result = doCommand(rover, args[0], args);
if (result != null)
return result;
rover = rover.getParent();
}
for (int i = 0; targets != null && i < targets.length; i++) {
String result = doCommand(targets[i], args[0], args);
if (result != null)
return result;
}
return doCommand(this, args[0], args);
}
private String doCommand(Object target, String method, String[] args) {
if (target == null)
; // System.err.println("Huh? Target should never be null " +
// domain);
else {
String cname = "_" + method.replaceAll("-", "_");
try {
Method m = target.getClass().getMethod(cname, new Class[] {
String[].class
});
Object result = m.invoke(target, new Object[] {
args
});
return result == null ? null : result.toString();
}
catch (NoSuchMethodException e) {
// Ignore
}
catch (InvocationTargetException e) {
if (e.getCause() instanceof IllegalArgumentException) {
domain.error("%s, for cmd: %s, arguments; %s", e.getCause().getMessage(), method,
Arrays.toString(args));
} else {
domain.warning("Exception in replace: %s", e.getCause());
e.getCause().printStackTrace();
}
}
catch (Exception e) {
domain.warning("Exception in replace: " + e + " method=" + method);
e.printStackTrace();
}
}
return null;
}
/**
* Return a unique list where the duplicates are removed.
*
* @param args
* @return
*/
static String _uniqHelp = "${uniq; ...}";
public String _uniq(String args[]) {
verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
Set set = new LinkedHashSet();
for (int i = 1; i < args.length; i++) {
Processor.split(args[i], set);
}
return Processor.join(set, ",");
}
public String _pathseparator(String args[]) {
return File.pathSeparator;
}
public String _separator(String args[]) {
return File.separator;
}
public String _filter(String args[]) {
return filter(args, false);
}
public String _filterout(String args[]) {
return filter(args, true);
}
static String _filterHelp = "${%s;;}";
String filter(String[] args, boolean include) {
verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
Collection list = new ArrayList(Processor.split(args[1]));
Pattern pattern = Pattern.compile(args[2]);
for (Iterator i = list.iterator(); i.hasNext();) {
if (pattern.matcher(i.next()).matches() == include)
i.remove();
}
return Processor.join(list);
}
static String _sortHelp = "${sort;...}";
public String _sort(String args[]) {
verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
List result = new ArrayList();
for (int i = 1; i < args.length; i++) {
Processor.split(args[i], result);
}
Collections.sort(result);
return Processor.join(result);
}
static String _joinHelp = "${join;...}";
public String _join(String args[]) {
verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
List result = new ArrayList();
for (int i = 1; i < args.length; i++) {
Processor.split(args[i], result);
}
return Processor.join(result);
}
static String _ifHelp = "${if;; [;] }";
public String _if(String args[]) {
verifyCommand(args, _ifHelp, null, 3, 4);
String condition = args[1].trim();
if (!condition.equalsIgnoreCase("false"))
if (condition.length() != 0)
return args[2];
if (args.length > 3)
return args[3];
return "";
}
public final static String _nowHelp = "${now;pattern|'long'}, returns current time";
public Object _now(String args[]) {
verifyCommand(args, _nowHelp, null, 1, 2);
Date now = new Date();
if (args.length == 2) {
if ("long".equals(args[1]))
return now.getTime();
DateFormat df = new SimpleDateFormat(args[1]);
return df.format(now);
}
return new Date();
}
public final static String _fmodifiedHelp = "${fmodified;...}, return latest modification date";
public String _fmodified(String args[]) throws Exception {
verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
long time = 0;
Collection names = new ArrayList();
for (int i = 1; i < args.length; i++) {
Processor.split(args[i], names);
}
for (String name : names) {
File f = new File(name);
if (f.exists() && f.lastModified() > time)
time = f.lastModified();
}
return "" + time;
}
public String _long2date(String args[]) {
try {
return new Date(Long.parseLong(args[1])).toString();
}
catch (Exception e) {
e.printStackTrace();
}
return "not a valid long";
}
public String _literal(String args[]) {
if (args.length != 2)
throw new RuntimeException("Need a value for the ${literal;} macro");
return "${" + args[1] + "}";
}
public String _def(String args[]) {
if (args.length != 2)
throw new RuntimeException("Need a value for the ${def;} macro");
return domain.getProperty(args[1], "");
}
/**
* replace ; ; regex ; replace
*
* @param args
* @return
*/
public String _replace(String args[]) {
if (args.length != 4) {
domain.warning("Invalid nr of arguments to replace " + Arrays.asList(args));
return null;
}
String list[] = args[1].split("\\s*,\\s*");
StringBuilder sb = new StringBuilder();
String del = "";
for (int i = 0; i < list.length; i++) {
String element = list[i].trim();
if (!element.equals("")) {
sb.append(del);
sb.append(element.replaceAll(args[2], args[3]));
del = ", ";
}
}
return sb.toString();
}
public String _warning(String args[]) {
for (int i = 1; i < args.length; i++) {
domain.warning(process(args[i]));
}
return "";
}
public String _error(String args[]) {
for (int i = 1; i < args.length; i++) {
domain.error(process(args[i]));
}
return "";
}
/**
* toclassname ; .class ( , .class ) *
*
* @param args
* @return
*/
static String _toclassnameHelp = "${classname;}, convert class paths to FQN class names ";
public String _toclassname(String args[]) {
verifyCommand(args, _toclassnameHelp, null, 2, 2);
Collection paths = Processor.split(args[1]);
List names = new ArrayList(paths.size());
for (String path : paths) {
if (path.endsWith(".class")) {
String name = path.substring(0, path.length() - 6).replace('/', '.');
names.add(name);
} else if (path.endsWith(".java")) {
String name = path.substring(0, path.length() - 5).replace('/', '.');
names.add(name);
} else {
domain.warning("in toclassname, " + args[1] + " is not a class path because it does not end in .class");
}
}
return Processor.join(names, ",");
}
/**
* toclassname ; .class ( , .class ) *
*
* @param args
* @return
*/
static String _toclasspathHelp = "${toclasspath;[;boolean]}, convert a list of class names to paths";
public String _toclasspath(String args[]) {
verifyCommand(args, _toclasspathHelp, null, 2, 3);
boolean cl = true;
if (args.length > 2)
cl = Boolean.valueOf(args[2]);
Collection names = Processor.split(args[1]);
Collection paths = new ArrayList(names.size());
for (String name : names) {
String path = name.replace('.', '/') + (cl ? ".class" : "");
paths.add(path);
}
return Processor.join(paths, ",");
}
public String _dir(String args[]) {
if (args.length < 2) {
domain.warning("Need at least one file name for ${dir;...}");
return null;
}
String del = "";
StringBuilder sb = new StringBuilder();
for (int i = 1; i < args.length; i++) {
File f = domain.getFile(args[i]);
if (f.exists() && f.getParentFile().exists()) {
sb.append(del);
sb.append(f.getParentFile().getAbsolutePath());
del = ",";
}
}
return sb.toString();
}
public String _basename(String args[]) {
if (args.length < 2) {
domain.warning("Need at least one file name for ${basename;...}");
return null;
}
String del = "";
StringBuilder sb = new StringBuilder();
for (int i = 1; i < args.length; i++) {
File f = domain.getFile(args[i]);
if (f.exists() && f.getParentFile().exists()) {
sb.append(del);
sb.append(f.getName());
del = ",";
}
}
return sb.toString();
}
public String _isfile(String args[]) {
if (args.length < 2) {
domain.warning("Need at least one file name for ${isfile;...}");
return null;
}
boolean isfile = true;
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]).getAbsoluteFile();
isfile &= f.isFile();
}
return isfile ? "true" : "false";
}
public String _isdir(String args[]) {
if (args.length < 2) {
domain.warning("Need at least one file name for ${isdir;...}");
return null;
}
boolean isdir = true;
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]).getAbsoluteFile();
isdir &= f.isDirectory();
}
return isdir ? "true" : "false";
}
public String _tstamp(String args[]) {
String format = "yyyyMMddHHmm";
long now = System.currentTimeMillis();
TimeZone tz = TimeZone.getTimeZone("UTC");
if (args.length > 1) {
format = args[1];
}
if (args.length > 2) {
tz = TimeZone.getTimeZone(args[2]);
}
if (args.length > 3) {
now = Long.parseLong(args[3]);
}
if (args.length > 4) {
domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
}
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setTimeZone(tz);
return sdf.format(new Date(now));
}
/**
* Wildcard a directory. The lists can contain Instruction that are matched
* against the given directory ${lsr;;(;)*}
* ${lsa;;(;)*}
*
* @author aqute
*/
public String _lsr(String args[]) {
return ls(args, true);
}
public String _lsa(String args[]) {
return ls(args, false);
}
String ls(String args[], boolean relative) {
if (args.length < 2)
throw new IllegalArgumentException("the ${ls} macro must at least have a directory as parameter");
File dir = domain.getFile(args[1]);
if (!dir.isAbsolute())
throw new IllegalArgumentException("the ${ls} macro directory parameter is not absolute: " + dir);
if (!dir.exists())
throw new IllegalArgumentException("the ${ls} macro directory parameter does not exist: " + dir);
if (!dir.isDirectory())
throw new IllegalArgumentException(
"the ${ls} macro directory parameter points to a file instead of a directory: " + dir);
Collection files = new ArrayList(new SortedList(dir.listFiles()));
for (int i = 2; i < args.length; i++) {
Instructions filters = new Instructions(args[i]);
files = filters.select(files, true);
}
List result = new ArrayList();
for (File file : files)
result.add(relative ? file.getName() : file.getAbsolutePath());
return Processor.join(result, ",");
}
public String _currenttime(String args[]) {
return Long.toString(System.currentTimeMillis());
}
/**
* Modify a version to set a version policy. Thed policy is a mask that is
* mapped to a version.
*
*
* + increment
* - decrement
* = maintain
* ˜ discard
*
* ==+ = maintain major, minor, increment micro, discard qualifier
* ˜˜˜= = just get the qualifier
* version="[${version;==;${@}},${version;=+;${@}})"
*
*
* @param args
* @return
*/
final static String MASK_STRING = "[\\-+=~0123456789]{0,3}[=~]?";
final static Pattern MASK = Pattern.compile(MASK_STRING);
final static String _versionHelp = "${version;;}, modify a version\n"
+ " ::= [ M [ M [ M [ MQ ]]]\n"
+ "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
final static Pattern _versionPattern[] = new Pattern[] {
null, null, MASK, Verifier.VERSION
};
public String _version(String args[]) {
verifyCommand(args, _versionHelp, null, 2, 3);
String mask = args[1];
Version version = null;
if (args.length >= 3)
version = new Version(args[2]);
return version(version, mask);
}
String version(Version version, String mask) {
if (version == null) {
String v = domain.getProperty("@");
if (v == null) {
domain.error(
"No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
mask);
v = "0";
}
version = new Version(v);
}
StringBuilder sb = new StringBuilder();
String del = "";
for (int i = 0; i < mask.length(); i++) {
char c = mask.charAt(i);
String result = null;
if (c != '~') {
if (i == 3) {
result = version.getQualifier();
} else if (Character.isDigit(c)) {
// Handle masks like +00, =+0
result = String.valueOf(c);
} else {
int x = version.get(i);
switch (c) {
case '+' :
x++;
break;
case '-' :
x--;
break;
case '=' :
break;
}
result = Integer.toString(x);
}
if (result != null) {
sb.append(del);
del = ".";
sb.append(result);
}
}
}
return sb.toString();
}
/**
* Schortcut for version policy
*
*
* -provide-policy : ${policy;[==,=+)}
* -consume-policy : ${policy;[==,+)}
*
*
* @param args
* @return
*/
static Pattern RANGE_MASK = Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
static String _rangeHelp = "${range;[;]}, range for version, if version not specified lookyp ${@}\n"
+ " ::= [ M [ M [ M [ MQ ]]]\n"
+ "M ::= '+' | '-' | MQ\n"
+ "MQ ::= '~' | '='";
static Pattern _rangePattern[] = new Pattern[] {
null, RANGE_MASK
};
public String _range(String args[]) {
verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
Version version = null;
if (args.length >= 3)
version = new Version(args[2]);
else {
String v = domain.getProperty("@");
if (v == null)
return null;
version = new Version(v);
}
String spec = args[1];
Matcher m = RANGE_MASK.matcher(spec);
m.matches();
String floor = m.group(1);
String floorMask = m.group(2);
String ceilingMask = m.group(3);
String ceiling = m.group(4);
String left = version(version, floorMask);
String right = version(version, ceilingMask);
StringBuilder sb = new StringBuilder();
sb.append(floor);
sb.append(left);
sb.append(",");
sb.append(right);
sb.append(ceiling);
String s = sb.toString();
VersionRange vr = new VersionRange(s);
if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
domain.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
}
return sb.toString();
}
/**
* System command. Execute a command and insert the result.
*
* @param args
* @param help
* @param patterns
* @param low
* @param high
*/
public String system_internal(boolean allowFail, String args[]) throws Exception {
verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
+ ";[;]}, execute a system command", null, 2, 3);
String command = args[1];
String input = null;
if (args.length > 2) {
input = args[2];
}
Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
if (input != null) {
process.getOutputStream().write(input.getBytes("UTF-8"));
}
process.getOutputStream().close();
String s = IO.collect(process.getInputStream(), "UTF-8");
int exitValue = process.waitFor();
if (exitValue != 0)
return exitValue + "";
if (!allowFail && (exitValue != 0)) {
domain.error("System command " + command + " failed with " + exitValue);
}
return s.trim();
}
public String _system(String args[]) throws Exception {
return system_internal(false, args);
}
public String _system_allow_fail(String args[]) throws Exception {
String result = "";
try {
result = system_internal(true, args);
}
catch (Throwable t) {
/* ignore */
}
return result;
}
public String _env(String args[]) {
verifyCommand(args, "${env;}, get the environmet variable", null, 2, 2);
try {
return System.getenv(args[1]);
}
catch (Throwable t) {
return null;
}
}
/**
* Get the contents of a file.
*
* @param in
* @return
* @throws IOException
*/
public String _cat(String args[]) throws IOException {
verifyCommand(args, "${cat;}, get the content of a file", null, 2, 2);
File f = domain.getFile(args[1]);
if (f.isFile()) {
return IO.collect(f);
} else if (f.isDirectory()) {
return Arrays.toString(f.list());
} else {
try {
URL url = new URL(args[1]);
return IO.collect(url, "UTF-8");
}
catch (MalformedURLException mfue) {
// Ignore here
}
return null;
}
}
public static void verifyCommand(String args[], String help, Pattern[] patterns, int low, int high) {
String message = "";
if (args.length > high) {
message = "too many arguments";
} else if (args.length < low) {
message = "too few arguments";
} else {
for (int i = 0; patterns != null && i < patterns.length && i < args.length; i++) {
if (patterns[i] != null) {
Matcher m = patterns[i].matcher(args[i]);
if (!m.matches())
message += String.format("Argument %s (%s) does not match %s%n", i, args[i],
patterns[i].pattern());
}
}
}
if (message.length() != 0) {
StringBuilder sb = new StringBuilder();
String del = "${";
for (String arg : args) {
sb.append(del);
sb.append(arg);
del = ";";
}
sb.append("}, is not understood. ");
sb.append(message);
throw new IllegalArgumentException(sb.toString());
}
}
// Helper class to track expansion of variables
// on the stack.
static class Link {
Link previous;
String key;
Processor start;
public Link(Processor start, Link previous, String key) {
this.previous = previous;
this.key = key;
this.start = start;
}
public boolean contains(String key) {
if (this.key.equals(key))
return true;
if (previous == null)
return false;
return previous.contains(key);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String del = "[";
for (Link r = this; r != null; r = r.previous) {
sb.append(del);
sb.append(r.key);
del = ",";
}
sb.append("]");
return sb.toString();
}
}
/**
* Take all the properties and translate them to actual values. This method
* takes the set properties and traverse them over all entries, including
* the default properties for that properties. The values no longer contain
* macros.
*
* @return A new Properties with the flattened values
*/
public Properties getFlattenedProperties() {
// Some macros only work in a lower processor, so we
// do not report unknown macros while flattening
flattening = true;
try {
Properties flattened = new Properties();
Properties source = domain.getProperties();
for (Enumeration< ? > e = source.propertyNames(); e.hasMoreElements();) {
String key = (String) e.nextElement();
if (!key.startsWith("_"))
if (key.startsWith("-"))
flattened.put(key, source.getProperty(key));
else
flattened.put(key, process(source.getProperty(key)));
}
return flattened;
}
finally {
flattening = false;
}
}
public final static String _fileHelp = "${file; ;...}, create correct OS dependent path";
public String _osfile(String args[]) {
verifyCommand(args, _fileHelp, null, 3, 3);
File base = new File(args[1]);
File f = Processor.getFile(base, args[2]);
return f.getAbsolutePath();
}
public String _path(String args[]) {
List list = new ArrayList();
for (int i = 1; i < args.length; i++) {
list.addAll(Processor.split(args[i]));
}
return Processor.join(list, File.pathSeparator);
}
public static Properties getParent(Properties p) {
try {
Field f = Properties.class.getDeclaredField("defaults");
f.setAccessible(true);
return (Properties) f.get(p);
}
catch (Exception e) {
Field[] fields = Properties.class.getFields();
System.err.println(Arrays.toString(fields));
return null;
}
}
public String process(String line) {
return process(line, domain);
}
}