Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
aQute.bnd.osgi.Macro Maven / Gradle / Ivy
package aQute.bnd.osgi;
import static aQute.bnd.exceptions.FunctionWithException.asFunction;
import static aQute.bnd.osgi.Processor.removeDuplicateMarker;
import static java.lang.invoke.MethodHandles.publicLookup;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toMap;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.WrongMethodTypeException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Deque;
import java.util.Formatter;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.header.Parameters;
import aQute.bnd.memoize.Memoize;
import aQute.bnd.osgi.Processor.FileLine;
import aQute.bnd.version.MavenVersion;
import aQute.bnd.version.Version;
import aQute.bnd.version.VersionRange;
import aQute.lib.base64.Base64;
import aQute.lib.date.Dates;
import aQute.lib.filter.ExtendedFilter;
import aQute.lib.formatter.Formatters;
import aQute.lib.hex.Hex;
import aQute.lib.io.IO;
import aQute.lib.strings.Strings;
import aQute.lib.utf8properties.UTF8Properties;
import aQute.libg.glob.Glob;
import aQute.service.reporter.Reporter;
import aQute.service.reporter.Reporter.SetLocation;
/**
* 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 {
private final static String NULLVALUE = "c29e43048791e250dfd5723e7b8aa048df802c9262cfa8fbc4475b2e392a8ad2";
protected final static String LITERALVALUE = "017a3ddbfc0fcd27bcdb2590cdb713a379ae59ef";
private final static Pattern NUMERIC_P = Pattern
.compile("[-+]?(\\d*\\.?\\d+|\\d+\\.)(e[-+]?[0-9]+)?");
Processor domain;
Reporter reporter;
Object targets[];
boolean flattening;
private boolean nosystem;
public boolean inTest;
private final Map, Map>> macrosByClass = new ConcurrentHashMap<>();
public Macro(Processor domain, Object... targets) {
this.domain = domain;
this.reporter = 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(CharSequence line, Link link) {
StringBuilder sb = new StringBuilder();
process(line, 0, '\u0000', '\u0000', sb, link, false);
return sb.toString();
}
int process(CharSequence org, int index, char begin, char end, StringBuilder result, Link link, boolean inMacro) {
if (org == null) { // treat null like empty string
return index;
}
StringBuilder line = new StringBuilder(org);
int nesting = 1;
boolean first;
List args = inMacro ? new ArrayList<>() : Collections.emptyList();
StringBuilder variable = new StringBuilder();
int pStart = 0;
outer: while (index < line.length()) {
char c1 = line.charAt(index++);
if (c1 == end) {
if (--nesting == 0) {
args.add(variable.substring(pStart));
result.append(replace(variable.toString(), args, link, begin, end));
return index;
}
} else if (c1 == begin)
nesting++;
else if (c1 == '\\' && index < line.length() - 1
&& (line.charAt(index) == '$' || line.charAt(index) == ';')) {
// remove the escape backslash and interpret the dollar or ;
// as a literal
variable.append(line.charAt(index));
index++;
continue outer;
} else if (c1 == '$' && index < line.length() - 2 && !inMacro) {
char c2 = line.charAt(index);
char terminator = getTerminator(c2);
if (terminator != 0) {
index = process(line, index + 1, c2, terminator, variable, link, true);
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(IO.absolutePath(domain.getBase()));
variable.append('/');
continue outer;
}
} else if (inMacro && c1 == ';' && nesting == 1) {
args.add(variable.substring(pStart));
pStart = variable.length() + 1;
}
variable.append(c1);
}
result.append(variable);
return index;
}
public static char getTerminator(char c) {
return switch (c) {
case '(' -> ')';
case '[' -> ']';
case '{' -> '}';
case '<' -> '>';
// Guillemet double << >>
case '\u00ab' -> '\u00bb';
// Guillemet single
case '\u2039' -> '\u203a';
default -> 0;
};
}
protected String getMacro(String key, Link link) {
return getMacro(key, null, link, '{', '}');
}
private String getMacro(String key, List args2, Link link, char begin, char end) {
if (link != null && link.contains(key))
return "${infinite:" + link.toString() + "}";
if (key != null) {
key = key.trim();
String[] args;
if (args2 == null) {
args = SEMICOLON_P.split(key, 0);
} else {
args = args2.toArray(new String[args2.size()]);
for (int i = 0; i < args.length; i++) {
args[i] = process(args[i], link);
}
}
if (!args[0].isEmpty()) {
//
// If the profile is set, we try to get the key first with the
// profile prefix. If this fails we try to get the correct one.
//
//
// Check if we have a wildcard key. In that case
// we go through all the matching keys and append the values
//
if (args.length == 1) {
Instruction ins = new Instruction(args[0]);
if (!ins.isLiteral()) {
String keyname = key;
return domain.stream()
.filter(ins::matches)
.sorted()
.map(k -> replace(k, null, new Link(domain, link, keyname), begin, end))
.filter(Objects::nonNull)
.collect(Strings.joining());
}
//
// NOTE: To access parameters with wildcard in them you need
// to escape them, e.g. foo\[3\]. The following removes the
// escapes
//
args[0] = ins.getLiteral();
}
//
// Check if the macro is defined as a raw property
//
String value = domain.getUnexpandedProperty(args[0]);
if (value != null) {
Link next = new Link(domain, link, key);
if (args.length > 1) {
return processWithArgs(value, args, next);
} else {
return process(value, next);
}
}
//
// Not found, look it up as a command
//
value = doCommands(args, link);
if (value != null) {
if (value == NULLVALUE)
return null;
if (value == LITERALVALUE)
return LITERALVALUE;
return process(value, new Link(domain, link, key));
}
//
// Last resort, try to find it as a system property
// or environment variable
//
if (args.length == 1) {
value = System.getProperty(args[0]);
if (value != null)
return value;
if (key.startsWith("env.")) {
value = System.getenv(args[0].substring(4));
if (value != null)
return value;
}
}
if (!args[0].startsWith("[")) {
String profile = domain.getUnexpandedProperty(Constants.PROFILE);
if (profile != null) {
profile = process(profile, link);
String profiledKey = "[" + profile + "]" + args[0];
value = domain.getUnexpandedProperty(profiledKey);
if (value != null) {
Link next = new Link(domain, link, key);
if (args.length > 1) {
return processWithArgs(value, args, next);
} else {
return process(value, next);
}
}
}
}
} else {
reporter.warning("Found empty macro key '%s'", key);
}
} else {
reporter.warning("Found null macro key");
}
return null;
}
/*
* Process the template but setup local arguments and # for the joined list
*/
private String processWithArgs(String template, String[] args, Link next) {
try (Processor custom = new Processor(domain)) {
for (int i = 0; i < 16; i++) {
custom.setProperty(Integer.toString(i), i < args.length ? args[i] : "null");
}
String joinedArgs = Arrays.stream(args, 1, args.length)
.collect(Strings.joining());
custom.setProperty("#", joinedArgs);
return custom.getReplacer()
.process(template, next);
} catch (IOException e) {
throw Exceptions.duck(e);
}
}
public String replace(String key, Link link) {
return replace(key, null, link, '{', '}');
}
protected String replace(String key, List args, Link link, char begin, char end) {
String value = getMacro(key, args, link, begin, end);
if (value != LITERALVALUE) {
if (value != null)
return value;
if (!flattening && !key.startsWith("@"))
reporter.warning("No translation found for macro: %s", key);
}
return "$" + begin + key + end;
}
/**
* Parse the key as a command. A command consist of parameters separated by
* ':'.
*/
// Handle up to 4 sequential backslashes in the negative lookbehind.
private static final String ESCAPING = "(?= 0) {
args[i] = ESCAPED_SEMICOLON_P.matcher(args[i])
.replaceAll(SEMICOLON);
}
if (args[0].startsWith("^")) {
String varname = args[0].substring(1)
.trim();
if (source != null) {
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);
}
protected BiFunction getFunction(String method) {
Processor rover = domain;
while (rover != null) {
BiFunction function = getFunction(rover, method);
if (function != null)
return function;
rover = rover.getParent();
}
for (int i = 0; targets != null && i < targets.length; i++) {
BiFunction function = getFunction(targets[i], method);
if (function != null)
return function;
}
BiFunction function = getFunction(this, method);
return function;
}
private String doCommand(Object target, String method, String[] args) {
if (target == null)
; // System.err.println("Huh? Target should never be null " +
// domain);
else {
for (int i = 0, len = method.length(); i < len; i++) {
char c = method.charAt(i);
if (c == '-') {
// Assume macro names do not start with '-'
if (i == 0) {
return null;
}
} else if (!Character.isJavaIdentifierPart(c)) {
return null;
}
}
BiFunction invoker = getFunction(target, method);
if (invoker == null) {
return null;
}
try {
Object result = invoker.apply(target, args);
return result == null ? NULLVALUE : result.toString();
} catch (Error e) {
throw e;
} catch (WrongMethodTypeException e) {
reporter.warning("Exception in replace: method=%s %s ", method, Exceptions.toString(e));
return NULLVALUE;
} catch (Exception e) {
reporter.error("%s, for cmd: %s, arguments; %s", e.getMessage(), method, Arrays.toString(args));
return NULLVALUE;
} catch (Throwable e) {
reporter.warning("Exception in replace: method=%s %s ", method, Exceptions.toString(e));
return NULLVALUE;
}
}
return null;
}
BiFunction getFunction(Object target, String method) {
Map> macros = macrosByClass.computeIfAbsent(target.getClass(),
c -> Arrays.stream(c.getMethods())
.filter(m -> (m.getName()
.charAt(0) == '_') && (m.getParameterCount() == 1) && (m.getParameterTypes()[0] == String[].class))
.collect(toMap(m -> m.getName()
.substring(1), m -> {
Memoize mh = Memoize.supplier(() -> {
try {
return publicLookup().unreflect(m);
} catch (Exception e) {
throw Exceptions.duck(e);
}
});
if (Modifier.isStatic(m.getModifiers())) {
return (Object t, String[] a) -> {
try {
return mh.get()
.invoke(a);
} catch (Throwable e) {
throw Exceptions.duck(e);
}
};
} else {
return (Object t, String[] a) -> {
try {
return mh.get()
.invoke(t, a);
} catch (Throwable e) {
throw Exceptions.duck(e);
}
};
}
})));
String macro = method.replace('-', '_');
BiFunction invoker = macros.get(macro);
return invoker;
}
/**
* Return a unique list where the duplicates are removed.
*/
static final String _uniqHelp = "${uniq; ...}";
public String _uniq(String[] args) {
verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.distinct()
.collect(Strings.joining());
return result;
}
/**
* Return the first list where items from the second list are removed.
*/
static final String _removeallHelp = "${removeall;;}";
public String _removeall(String[] args) {
verifyCommand(args, _removeallHelp, null, 1, 3);
if (args.length < 2) {
return "";
}
List result = Strings.splitQuoted(args[1]);
if (args.length > 2) {
result.removeAll(Strings.splitQuoted(args[2]));
}
return Strings.join(result);
}
/**
* Return the first list where items not in the second list are removed.
*/
static final String _retainallHelp = "${retainall;;}";
public String _retainall(String[] args) {
verifyCommand(args, _retainallHelp, null, 1, 3);
if (args.length < 3) {
return "";
}
List result = Strings.splitQuoted(args[1]);
result.retainAll(Strings.splitQuoted(args[2]));
return Strings.join(result);
}
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 _select(String[] args) {
return filter(args, false);
}
public String _filterout(String[] args) {
return filter(args, true);
}
public String _reject(String[] args) {
return filter(args, true);
}
static final String _filterHelp = "${%s;;}";
String filter(String[] args, boolean include) {
verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
Pattern pattern = Pattern.compile(args[2]);
String result = Strings.splitQuotedAsStream(args[1])
.filter(s -> pattern.matcher(s)
.matches() != include)
.collect(Strings.joining());
return result;
}
static final String _sortHelp = "${sort;...}";
public String _sort(String[] args) {
verifyCommand(args, _sortHelp, null, 1, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.sorted()
.collect(Strings.joining());
return result;
}
static final String _nsortHelp = "${nsort;...}";
public String _nsort(String[] args) {
verifyCommand(args, _nsortHelp, null, 1, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitAsStream)
.sorted((a, b) -> {
while (a.startsWith("0")) {
a = a.substring(1);
}
while (b.startsWith("0")) {
b = b.substring(1);
}
if (a.length() == b.length()) {
return a.compareTo(b);
}
return (a.length() > b.length()) ? 1 : -1;
})
.collect(Strings.joining());
return result;
}
static final String _joinHelp = "${join;...}";
public String _join(String[] args) {
verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitAsStream)
.collect(Strings.joining());
return result;
}
static final String _sjoinHelp = "${sjoin;;...}";
public String _sjoin(String[] args) throws Exception {
verifyCommand(args, _sjoinHelp, null, 2, Integer.MAX_VALUE);
String result = Arrays.stream(args, 2, args.length)
.flatMap(Strings::splitQuotedAsStream)
.collect(Collectors.joining(args[1]));
return result;
}
static final String _ifHelp = "${if;; [;] } condition is either a filter expression or truthy";
public String _if(String[] args) throws Exception {
verifyCommand(args, _ifHelp, null, 2, 4);
String condition = args[1];
if (isTruthy(condition))
return args.length > 2 ? args[2] : "true";
if (args.length > 3)
return args[3];
return "";
}
public boolean isTruthy(String condition) throws Exception {
if (condition == null)
return false;
condition = condition.trim();
if (condition.startsWith("(") && condition.endsWith(")")) {
return doCondition(condition);
}
return !condition.equalsIgnoreCase("false") && !condition.equals("0") && !condition.equals("0.0")
&& condition.length() != 0;
}
private static final DateTimeFormatter DATE_TOSTRING = Dates.DATE_TOSTRING.withZone(Dates.UTC_ZONE_ID);
public final static String _nowHelp = "${now;pattern|'long'}, returns current time";
public Object _now(String[] args) {
verifyCommand(args, _nowHelp, null, 1, 2);
long now = getBuildNow();
if (args.length == 2) {
if ("long".equals(args[1])) {
return Long.toString(now);
}
DateFormat df = new SimpleDateFormat(args[1], Locale.ROOT);
df.setTimeZone(Dates.UTC_TIME_ZONE);
return df.format(new Date(now));
}
return Dates.formatMillis(DATE_TOSTRING, now);
}
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 = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.map(File::new)
.filter(File::exists)
.mapToLong(File::lastModified)
.max()
.orElse(0);
return Long.toString(time);
}
public String _long2date(String[] args) {
try {
return Dates.formatMillis(DATE_TOSTRING, Long.parseLong(args[1]));
} catch (Exception e) {
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] + "}";
}
static final String _defHelp = "${def;[;]}, get the property or a default value if unset";
public String _def(String[] args) {
verifyCommand(args, _defHelp, null, 2, 3);
return domain.getProperty(args[1], args.length == 3 ? args[2] : "");
}
static final String _listHelp = "${list;[...]}, returns a list of the values of the named properties with escaped semicolons";
public String _list(String[] args) {
verifyCommand(args, _listHelp, null, 1, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.map(domain::getProperty)
.flatMap(Strings::splitQuotedAsStream)
.map(element -> (element.indexOf(';') < 0) ? element
: SEMICOLON_P.matcher(element)
.replaceAll(ESCAPED_SEMICOLON))
.collect(Strings.joining());
return result;
}
static final String _replaceHelp = "${replace;;;[[;delimiter]]}";
public String _replace(String[] args) {
return replace0(_replaceHelp, Strings::splitAsStream, args);
}
static final String _replacelistHelp = "${replacelist;;;[[;delimiter]]}";
public String _replacelist(String[] args) {
return replace0(_replacelistHelp, Strings::splitQuotedAsStream, args);
}
private String replace0(String help, Function> splitter, String[] args) {
verifyCommand(args, help, null, 3, 5);
Pattern regex = Pattern.compile(args[2]);
String replace = (args.length > 3) ? args[3] : "";
Collector joining = (args.length > 4) ? Collectors.joining(args[4])
: Strings.joining();
String result = splitter.apply(args[1])
.map(element -> regex.matcher(element)
.replaceAll(replace))
.collect(joining);
return result;
}
static final String _replacestringHelp = "${replacesting;;;[]}";
public String _replacestring(String[] args) {
verifyCommand(args, _replacestringHelp, null, 3, 4);
Pattern regex = Pattern.compile(args[2]);
String replace = (args.length > 3) ? args[3] : "";
String result = regex.matcher(args[1])
.replaceAll(replace);
return result;
}
private static final Pattern ANY = Pattern.compile(".*");
private static final Pattern ERROR_P = Pattern.compile("\\$\\{error;");
private static final Pattern WARNING_P = Pattern.compile("\\$\\{warning;");
public String _warning(String[] args) throws Exception {
for (int i = 1; i < args.length; i++) {
SetLocation warning = reporter.warning("%s", process(args[i]));
FileLine header = domain.getHeader(ANY, WARNING_P);
if (header != null)
header.set(warning);
}
return "";
}
public String _error(String[] args) throws Exception {
for (int i = 1; i < args.length; i++) {
SetLocation error = reporter.error("%s", process(args[i]));
FileLine header = domain.getHeader(ANY, ERROR_P);
if (header != null)
header.set(error);
}
return "";
}
/**
* toclassname ; .class ( , .class ) *
*/
static final String _toclassnameHelp = "${toclassname;}, convert class paths to FQN class names ";
public String _toclassname(String[] args) {
verifyCommand(args, _toclassnameHelp, null, 2, 2);
String result = Strings.splitAsStream(args[1])
.map(path -> {
if (path.endsWith(".class")) {
return path.substring(0, path.length() - 6)
.replace('/', '.');
}
if (path.endsWith(".java")) {
return path.substring(0, path.length() - 5)
.replace('/', '.');
}
reporter.warning("in toclassname, %s is not a class path because it does not end in .class", path);
return null;
})
.filter(Objects::nonNull)
.collect(Strings.joining());
return result;
}
/**
* toclassname ; .class ( , .class ) *
*/
static final 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 = (args.length > 2) ? Boolean.parseBoolean(args[2]) : true;
Function mapper = cl ? name -> name.replace('.', '/') + ".class"
: name -> name.replace('.', '/');
String result = Strings.splitAsStream(args[1])
.map(mapper)
.collect(Strings.joining());
return result;
}
public String _dir(String[] args) {
if (args.length < 2) {
reporter.warning("Need at least one file name for ${dir;...}");
return null;
}
String result = Arrays.stream(args, 1, args.length)
.map(domain::getFile)
.filter(File::exists)
.map(File::getParentFile)
.map(IO::absolutePath)
.collect(Strings.joining());
return result;
}
public String _basename(String[] args) {
if (args.length < 2) {
reporter.warning("Need at least one file name for ${basename;...}");
return null;
}
String result = Arrays.stream(args, 1, args.length)
.map(domain::getFile)
.filter(File::exists)
.map(File::getName)
.collect(Strings.joining());
return result;
}
public String _isfile(String[] args) {
if (args.length < 2) {
reporter.warning("Need at least one file name for ${isfile;...}");
return null;
}
boolean isfile = Arrays.stream(args, 1, args.length)
.map(File::new)
.map(File::getAbsoluteFile)
.allMatch(File::isFile);
return Boolean.toString(isfile);
}
public String _isdir(String[] args) {
// if (args.length < 2) {
// reporter.warning("Need at least one file name for ${isdir;...}");
// return null;
// }
// If no dirs provided, return false
boolean isdir = (args.length < 2) ? false
: Arrays.stream(args, 1, args.length)
.map(File::new)
.map(File::getAbsoluteFile)
.allMatch(File::isDirectory);
return Boolean.toString(isdir);
}
public String _tstamp(String[] args) {
String format;
long now;
TimeZone tz;
if (args.length > 1) {
format = args[1];
} else {
format = "yyyyMMddHHmm";
}
if (args.length > 2) {
tz = TimeZone.getTimeZone(args[2]);
} else {
tz = Dates.UTC_TIME_ZONE;
}
if (args.length > 3) {
now = Long.parseLong(args[3]);
} else {
now = getBuildNow();
}
if (args.length > 4) {
reporter.warning("Too many arguments for tstamp: %s", Arrays.toString(args));
}
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US);
sdf.setTimeZone(tz);
return sdf.format(new Date(now));
}
private long getBuildNow() {
long now;
String tstamp = domain.getProperty(Constants.TSTAMP);
if (tstamp != null) {
try {
now = Long.parseLong(tstamp);
} catch (NumberFormatException e) {
// ignore, just use current time
now = System.currentTimeMillis();
}
} else {
now = System.currentTimeMillis();
}
return now;
}
static final String _lsrHelp = "${lsr;;[...]}";
public String _lsr(String[] args) {
return ls(_lsrHelp, args, true);
}
static final String _lsaHelp = "${lsa;;[...]}";
public String _lsa(String[] args) {
return ls(_lsaHelp, args, false);
}
private String ls(String help, String[] args, boolean relative) {
verifyCommand(args, help, null, 2, Integer.MAX_VALUE);
File dir = domain.getFile(args[1]);
if (!dir.isAbsolute())
throw new IllegalArgumentException(
String.format("the ${%s} macro directory parameter is not absolute: %s", args[0], dir));
if (!dir.exists())
throw new IllegalArgumentException(
String.format("the ${%s} macro directory parameter does not exist: %s", args[0], dir));
if (!dir.isDirectory())
throw new IllegalArgumentException(String.format(
"the ${%s} macro directory parameter points to a file instead of a directory: %s", args[0], dir));
List files = IO.listFiles(dir);
if (files.isEmpty()) {
return "";
}
Function mapper = relative ? File::getName : IO::absolutePath;
if (args.length < 3) {
String result = files.stream()
.map(mapper)
.collect(Strings.joining());
return result;
}
List result = new ArrayList<>(files.size());
Arrays.stream(args, 2, args.length)
.flatMap(Strings::splitQuotedAsStream)
.map(Instruction::new)
.forEachOrdered(ins -> {
for (Iterator iter = files.iterator(); iter.hasNext();) {
File file = iter.next();
if (ins.matches(file.getPath())) {
iter.remove();
if (!ins.isNegated()) {
result.add(mapper.apply(file));
}
}
}
});
return Strings.join(result);
}
public String _currenttime(String[] args) {
return Long.toString(System.currentTimeMillis());
}
/**
* Modify a version to set a version policy. The policy is a mask that is
* mapped to a version.
*
*
* + increment - decrement = maintain s only
* pos=3 (qualifier). If qualifer == SNAPSHOT, return m.m.m-SNAPSHOT else
* m.m.m.q s only pos=3 (qualifier). If qualifer == SNAPSHOT, return
* m.m.m-SNAPSHOT else m.m.m ˜ discard ==+ = maintain major, minor,
* increment micro, discard qualifier ˜˜˜= = just get the
* qualifier version="[${version;==;${@}},${version;=+;${@}})"
*
*/
private final static String MASK_M = "[-+=~\\d]";
private final static String MASK_Q = "[=~sS\\d]";
private final static String MASK_STRING = MASK_M + "(?:" + MASK_M + "(?:" + MASK_M + "(?:" + MASK_Q
+ ")?)?)?";
private final static Pattern VERSION_MASK = Pattern.compile(MASK_STRING);
final static String _versionmaskHelp = "${versionmask;;}, modify a version\n"
+ " ::= [ M [ M [ M [ MQ ]]]\n" + "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
final static String _versionHelp = _versionmaskHelp;
final static Pattern[] _versionPattern = new Pattern[] {
null, VERSION_MASK
};
public String _version(String[] args) {
return _versionmask(args);
}
public String _versionmask(String[] args) {
verifyCommand(args, _versionmaskHelp, _versionPattern, 2, 3);
String mask = args[1];
Version version;
if (args.length >= 3) {
if (isLocalTarget(args[2]))
return LITERALVALUE;
version = Version.parseVersion(args[2]);
} else {
String v = domain.getProperty(Constants.CURRENT_VERSION);
if (v == null) {
return LITERALVALUE;
}
version = new Version(v);
}
return version(version, mask);
}
static String version(Version version, String mask) {
StringBuilder sb = new StringBuilder();
String del = "";
maskloop: for (int i = 0, len = mask.length(); i < len; i++) {
char c = mask.charAt(i);
if (i == 3) {
switch (c) {
case '~' :
break;
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
sb.append(del)
.append(c);
break;
case 's' : {
MavenVersion mv = new MavenVersion(version);
// we have a request for a Maven snapshot
if (mv.isSnapshot()) {
sb.append("-SNAPSHOT");
}
break;
}
case 'S' : {
MavenVersion mv = new MavenVersion(version);
// we have a request for a Maven snapshot
if (mv.isSnapshot()) {
sb.append("-SNAPSHOT");
break;
}
// FALL-THROUGH
}
case '=' : {
String qualifier = version.getQualifier();
if (qualifier != null) {
sb.append(del)
.append(qualifier);
}
break;
}
default :
throw new IllegalArgumentException("Invalid mask character " + c + " at index " + i);
}
return sb.toString();
}
switch (c) {
case '~' :
continue maskloop; // don't modify del
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
sb.append(del)
.append(c);
break;
case '+' :
sb.append(del)
.append(version.get(i) + 1);
break;
case '-' :
sb.append(del)
.append(Math.max(0, version.get(i) - 1));
break;
case '=' :
sb.append(del)
.append(version.get(i));
break;
default :
throw new IllegalArgumentException("Invalid mask character " + c + " at index " + i);
}
del = ".";
}
return sb.toString();
}
/**
* Schortcut for version policy
*
*
* -provide-policy : ${policy;[==,=+)}
* -consume-policy : ${policy;[==,+)}
*
*/
private static final Pattern RANGE_MASK = Pattern
.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
static final String _rangeHelp = "${range;[;]}, range for version, if version not specified lookup ${@}\n"
+ " ::= [ M [ M [ M [ MQ ]]]\n" + "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
static final Pattern _rangePattern[] = new Pattern[] {
null, RANGE_MASK
};
public String _range(String[] args) {
verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
Version version;
if (args.length >= 3) {
String string = args[2];
if (isLocalTarget(string))
return LITERALVALUE;
version = new Version(string);
} else {
String v = domain.getProperty(Constants.CURRENT_VERSION);
if (v == null)
return LITERALVALUE;
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()))) {
reporter.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
}
return sb.toString();
}
private static final String LOCALTARGET_NAME = "@[^${}\\[\\]()<>«»‹›]*";
private static final Pattern LOCALTARGET_P = Pattern
.compile("\\$(\\{" + LOCALTARGET_NAME + "\\}|\\[" + LOCALTARGET_NAME + "\\]|\\(" + LOCALTARGET_NAME + "\\)|<"
+ LOCALTARGET_NAME + ">|«" + LOCALTARGET_NAME + "»|‹" + LOCALTARGET_NAME + "›)");
boolean isLocalTarget(String string) {
return LOCALTARGET_P.matcher(string)
.matches();
}
/**
* System command. Execute a command and insert the result.
*/
public String system_internal(boolean allowFail, String[] args) throws Exception {
if (nosystem)
throw new RuntimeException("Macros in this mode cannot excute system commands");
verifyCommand(args, allowFail ? _system_allow_failHelp : _systemHelp, null, 2, 3);
String command = args[1];
String input = null;
if (args.length > 2) {
input = args[2];
}
return domain.system(allowFail, command, input);
}
static final String _systemHelp = "${system;[;]}, execute a system command";
public String _system(String[] args) throws Exception {
return system_internal(false, args);
}
static final String _system_allow_failHelp = "${system-allow-fail;[;]}, execute a system command allowing command failure";
public String _system_allow_fail(String[] args) throws Exception {
String result = "";
try {
result = system_internal(true, args);
return result == null ? "" : result;
} catch (Throwable t) {
/* ignore */
return "";
}
}
static final String _envHelp = "${env;[;alternative]}, get the environment variable";
public String _env(String[] args) {
verifyCommand(args, _envHelp, null, 2, 3);
try {
String ret = System.getenv(args[1]);
if (ret != null)
return ret;
if (args.length > 2)
return args[2];
} catch (Throwable t) {
// ignore
}
return "";
}
static final String _catHelp = "${cat;}, get the content of a file";
/**
* Get the contents of a file.
*
* @throws IOException
*/
public String _cat(String[] args) throws IOException {
verifyCommand(args, _catHelp, null, 2, 2);
File f = domain.getFile(args[1]);
if (f.isFile()) {
return IO.collect(f)
.replaceAll("\\\\", "\\\\\\\\");
} else if (f.isDirectory()) {
return IO.list(f)
.toString();
} else {
try {
URL url = new URL(args[1]);
return IO.collect(url, UTF_8);
} catch (MalformedURLException mfue) {
// Ignore here
}
return null;
}
}
static final String _base64Help = "${base64;[;fileSizeLimit]}, get the Base64 encoding of a file";
/**
* Get the Base64 encoding of a file.
*
* @throws IOException
*/
public String _base64(String... args) throws IOException {
verifyCommand(args, _base64Help, null, 2, 3);
File file = domain.getFile(args[1]);
long maxLength = 100_000;
if (args.length > 2)
maxLength = Long.parseLong(args[2]);
if (file.length() > maxLength)
throw new IllegalArgumentException(
"Maximum file size (" + maxLength + ") for base64 macro exceeded for file " + file);
return Base64.encodeBase64(file);
}
static final String _digestHelp = "${digest;;}, get a digest (e.g. MD5, SHA-256) of a file";
/**
* Get a digest of a file.
*
* @throws NoSuchAlgorithmException
* @throws IOException
*/
public String _digest(String... args) throws NoSuchAlgorithmException, IOException {
verifyCommand(args, _digestHelp, null, 3, 3);
MessageDigest digester = MessageDigest.getInstance(args[1]);
File f = domain.getFile(args[2]);
IO.copy(f, digester);
byte[] digest = digester.digest();
return Hex.toHexString(digest);
}
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 = SEMICOLON;
}
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 {
final Link previous;
final String key;
final Processor start;
public Link(Processor start, Link previous, String key) {
this.start = Objects.requireNonNull(start);
this.previous = previous;
this.key = key;
}
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.
*
* There are some rules
*
* Property names starting with an underscore ('_') are ignored. These
* are reserved for properties that cause an unwanted side effect when
* expanded unnecessary
* Property names starting with a minus sign ('-') are not expanded to
* maintain readability
*
*
* @return A new Properties with the flattened values
*/
public Properties getFlattenedProperties() {
return getFlattenedProperties(true);
}
/**
* 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.
*
* Property names starting with an underscore ('_') are ignored. These are
* reserved for properties that cause an unwanted side effect when expanded
* unnecessary
*
* @return A new Properties with the flattened values
*/
public Properties getFlattenedProperties(boolean ignoreInstructions) {
// Some macros only work in a lower processor, so we
// do not report unknown macros while flattening
flattening = true;
try {
Stream keys = StreamSupport.stream(domain.spliterator(), false);
Properties flattened = keys.filter(key -> !key.startsWith("_"))
.map(key -> {
String value = null;
for (Processor proc = domain; proc != null; proc = proc.getParent()) {
Object raw = proc.getProperties()
.get(key);
if (raw != null) {
if (raw instanceof String string) {
value = string;
} else if (reporter.isPedantic()) {
reporter.warning("Key '%s' has a non-String value: %s:%s", key, raw.getClass()
.getName(), raw);
}
break;
}
Collection keyFilter = proc.filter;
if ((keyFilter != null) && (keyFilter.contains(key))) {
break;
}
}
if (value == null) {
return null;
}
if (!ignoreInstructions || !key.startsWith("-")) {
value = process(value);
}
return new AbstractMap.SimpleEntry<>(key, value);
})
.filter(Objects::nonNull)
.collect(toMap(Entry::getKey, Entry::getValue, (oldValue, newValue) -> newValue, UTF8Properties::new));
return flattened;
} finally {
flattening = false;
}
}
static final String _osfileHelp = "${osfile; ;}, create correct OS dependent path";
public String _osfile(String[] args) {
verifyCommand(args, _osfileHelp, null, 3, 3);
File base = new File(args[1]);
File f = IO.getFile(base, args[2]);
return f.getAbsolutePath(); // Need to return path in OS specific form
}
public String _path(String[] args) {
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.collect(Collectors.joining(File.pathSeparator));
return result;
}
public final static String _sizeHelp = "${size;;...}, count the number of elements (of all collections combined)";
public int _size(String[] args) {
verifyCommand(args, _sizeHelp, null, 1, Integer.MAX_VALUE);
long size = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.count();
return (int) size;
}
public static Properties getParent(Properties p) {
try {
Field f = Properties.class.getDeclaredField("defaults");
f.setAccessible(true);
MethodHandle mh = publicLookup().unreflectGetter(f);
return (Properties) mh.invoke(p);
} catch (Error e) {
throw e;
} catch (Throwable e) {
return null;
}
}
public String process(String line) {
return process(line, domain);
}
public boolean isNosystem() {
return nosystem;
}
public boolean setNosystem(boolean nosystem) {
boolean tmp = this.nosystem;
this.nosystem = nosystem;
return tmp;
}
public String _unescape(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < args.length; i++) {
sb.append(args[i]);
}
for (int j = 0; j < sb.length() - 1; j++) {
if (sb.charAt(j) == '\\') {
switch (sb.charAt(j + 1)) {
case 'n' :
sb.replace(j, j + 2, "\n");
break;
case 'r' :
sb.replace(j, j + 2, "\r");
break;
case 'b' :
sb.replace(j, j + 2, "\b");
break;
case 'f' :
sb.replace(j, j + 2, "\f");
break;
case 't' :
sb.replace(j, j + 2, "\t");
break;
default :
break;
}
}
}
return sb.toString();
}
static final String _startswithHelp = "${startswith;;}";
public String _startswith(String[] args) throws Exception {
verifyCommand(args, _startswithHelp, null, 3, 3);
if (args[1].startsWith(args[2]))
return args[1];
else
return "";
}
static final String _endswithHelp = "${endswith;;}";
public String _endswith(String[] args) throws Exception {
verifyCommand(args, _endswithHelp, null, 3, 3);
if (args[1].endsWith(args[2]))
return args[1];
else
return "";
}
static final String _extensionHelp = "${extension;}";
public String _extension(String[] args) throws Exception {
verifyCommand(args, _extensionHelp, null, 2, 2);
String result = Optional.of(args[1])
.map(IO::normalizePath)
.map(path -> Optional.ofNullable(Strings.lastPathSegment(path))
.map(tuple -> tuple[1])
.orElse(path))
.flatMap(name -> Optional.ofNullable(Strings.extension(name))
.map(tuple -> tuple[1]))
.orElse("");
return result;
}
static final String _basenameextHelp = "${basenameext;[;]}";
public String _basenameext(String[] args) throws Exception {
verifyCommand(args, _basenameextHelp, null, 2, 3);
String extension = Optional.ofNullable((args.length > 2 && !args[2].isEmpty()) ? args[2] : null)
.map(ext -> ext.startsWith(".") ? ext.substring(1) : ext)
.orElse(".");
String result = Optional.of(args[1])
.map(IO::normalizePath)
.map(path -> Optional.ofNullable(Strings.lastPathSegment(path))
.map(tuple -> tuple[1])
.orElse(path))
.map(name -> Optional.ofNullable(Strings.extension(name))
.filter(tuple -> extension.equals(tuple[1]))
.map(tuple -> tuple[0])
.orElse(name))
.orElse("");
return result;
}
static final String _bndversionHelp = "${bndversion}, returns the currently running bnd version";
public String _bndversion(String[] args) throws Exception {
verifyCommand(args, _bndversionHelp, null, 1, 1);
return About.CURRENT.toStringWithoutQualifier();
}
static final String _stemHelp = "${stem;}";
public String _stem(String[] args) throws Exception {
verifyCommand(args, _stemHelp, null, 2, 2);
String name = args[1];
int n = name.indexOf('.');
if (n < 0)
return name;
return name.substring(0, n);
}
static final String _substringHelp = "${substring;;[;]}";
public String _substring(String[] args) throws Exception {
verifyCommand(args, _substringHelp, null, 3, 4);
String string = args[1];
int start = Integer.parseInt(args[2].equals("") ? "0" : args[2]);
int end = string.length();
if (args.length > 3) {
end = Integer.parseInt(args[3]);
if (end < 0)
end = string.length() + end;
}
if (start < 0)
start = string.length() + start;
if (start > end) {
int t = start;
start = end;
end = t;
}
return string.substring(start, end);
}
static final String _randHelp = "${rand;[[;]]}";
static final Random random = new Random();
public long _rand(String[] args) throws Exception {
verifyCommand(args, _randHelp, null, 2, 3);
int min = 0;
int max = 100;
if (args.length > 1) {
max = Integer.parseInt(args[1]);
if (args.length > 2) {
min = Integer.parseInt(args[2]);
}
}
int diff = max - min;
double d = random.nextDouble() * diff + min;
return Math.round(d);
}
static final String _lengthHelp = "${length;}";
public int _length(String[] args) throws Exception {
verifyCommand(args, _lengthHelp, null, 1, 2);
if (args.length == 1)
return 0;
return args[1].length();
}
static final String _getHelp = "${get;;}";
public String _get(String[] args) throws Exception {
verifyCommand(args, _getHelp, null, 3, 3);
int index = Integer.parseInt(args[1]);
List list = toList(args, 2, args.length);
if (index < 0)
index = list.size() + index;
return list.get(index);
}
static final String _sublistHelp = "${sublist;;[;...]}";
public String _sublist(String[] args) throws Exception {
verifyCommand(args, _sublistHelp, null, 4, Integer.MAX_VALUE);
int start = Integer.parseInt(args[1]);
int end = Integer.parseInt(args[2]);
List list = toList(args, 3, args.length);
if (start < 0)
start = list.size() + start + 1;
if (end < 0)
end = list.size() + end + 1;
if (start > end) {
int t = start;
start = end;
end = t;
}
return Strings.join(list.subList(start, end));
}
private List toList(String[] args, int startInclusive, int endExclusive) {
List list = Arrays.stream(args, startInclusive, endExclusive)
.flatMap(Strings::splitQuotedAsStream)
.collect(Collectors.toList());
return list;
}
static final String _firstHelp = "${first;[;...]}";
public String _first(String[] args) throws Exception {
verifyCommand(args, _firstHelp, null, 1, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.findFirst()
.orElse("");
return result;
}
static final String _lastHelp = "${last;[;...]}";
public String _last(String[] args) throws Exception {
verifyCommand(args, _lastHelp, null, 1, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.reduce((first, second) -> second)
.orElse("");
return result;
}
static final String _maxHelp = "${max;[;...]}";
public String _max(String[] args) throws Exception {
verifyCommand(args, _maxHelp, null, 2, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.max(String::compareTo)
.orElse("");
return result;
}
static final String _minHelp = "${min;[;...]}";
public String _min(String[] args) throws Exception {
verifyCommand(args, _minHelp, null, 2, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.min(String::compareTo)
.orElse("");
return result;
}
static final String _nmaxHelp = "${nmax;[;...]}";
public String _nmax(String[] args) throws Exception {
verifyCommand(args, _nmaxHelp, null, 2, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitAsStream)
.max(Comparator.comparingDouble(Double::parseDouble))
.orElse("NaN");
return result;
}
static final String _nminHelp = "${nmin;[;...]}";
public String _nmin(String[] args) throws Exception {
verifyCommand(args, _nminHelp, null, 2, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitAsStream)
.min(Comparator.comparingDouble(Double::parseDouble))
.orElse("NaN");
return result;
}
static final String _vmaxHelp = "${vmax;[;...]}";
public String _vmax(String[] args) throws Exception {
verifyCommand(args, _vmaxHelp, null, 2, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitAsStream)
.max(Comparator.comparing(Version::valueOf))
.orElse("0");
return result;
}
static final String _vminHelp = "${vmin;[;...]}";
public String _vmin(String[] args) throws Exception {
verifyCommand(args, _vminHelp, null, 2, Integer.MAX_VALUE);
String result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitAsStream)
.min(Comparator.comparing(Version::valueOf))
.orElse("0");
return result;
}
static final String _sumHelp = "${sum;[;...]}";
public String _sum(String[] args) throws Exception {
verifyCommand(args, _sumHelp, null, 2, Integer.MAX_VALUE);
double result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitAsStream)
.mapToDouble(Double::parseDouble)
.sum();
return toString(result);
}
static final String _averageHelp = "${average;[;...]}";
public String _average(String[] args) throws Exception {
verifyCommand(args, _sumHelp, null, 2, Integer.MAX_VALUE);
double result = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.mapToDouble(Double::parseDouble)
.average()
.orElseThrow(() -> new IllegalArgumentException("No members in list to calculate average"));
return toString(result);
}
static final String _reverseHelp = "${reverse;[;...]}";
public String _reverse(String[] args) throws Exception {
verifyCommand(args, _reverseHelp, null, 2, Integer.MAX_VALUE);
Deque reversed = Arrays.stream(args, 1, args.length)
.flatMap(Strings::splitQuotedAsStream)
.collect(Collector.of(ArrayDeque::new, ArrayDeque::addFirst, (d1, d2) -> {
d2.addAll(d1);
return d2;
}));
return Strings.join(reversed);
}
static final String _indexofHelp = "${indexof;;[;...]}";
public int _indexof(String[] args) throws Exception {
verifyCommand(args, _indexofHelp, null, 3, Integer.MAX_VALUE);
String value = args[1];
List list = toList(args, 2, args.length);
return list.indexOf(value);
}
static final String _lastindexofHelp = "${lastindexof;;[;...]}";
public int _lastindexof(String[] args) throws Exception {
verifyCommand(args, _lastindexofHelp, null, 3, Integer.MAX_VALUE);
String value = args[1];
List list = toList(args, 2, args.length);
return list.lastIndexOf(value);
}
static final String _findHelp = "${find;;}";
public int _find(String[] args) throws Exception {
verifyCommand(args, _findHelp, null, 3, 3);
return args[1].indexOf(args[2]);
}
static final String _findlastHelp = "${findlast;;}";
public int _findlast(String[] args) throws Exception {
verifyCommand(args, _findlastHelp, null, 3, 3);
return args[2].lastIndexOf(args[1]);
}
static final String _splitHelp = "${split;[;...]}";
public String _split(String[] args) throws Exception {
verifyCommand(args, _splitHelp, null, 2, Integer.MAX_VALUE);
Pattern regex = Pattern.compile(args[1]);
String result = Arrays.stream(args, 2, args.length)
.flatMap(regex::splitAsStream)
.filter(element -> !element.isEmpty())
.collect(Strings.joining());
return result;
}
static final String _jsHelp = "${js [;...]}";
public Object _js(String[] args) throws Exception {
verifyCommand(args, _jsHelp, null, 2, Integer.MAX_VALUE);
String script = Arrays.stream(args, 1, args.length)
.collect(Collectors.joining(SEMICOLON));
StringWriter stdout = new StringWriter();
StringWriter stderr = new StringWriter();
try {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
ScriptContext context = engine.getContext();
Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("domain", domain);
String javascript = domain.mergeProperties("javascript", ";");
if (javascript != null && javascript.length() > 0) {
engine.eval(javascript, context);
}
context.setErrorWriter(stderr);
context.setWriter(stdout);
Object eval = engine.eval(script, context);
String out = stdout.toString();
if (!out.isEmpty()) {
reporter.error("Executing js: %s: %s", script, out);
}
if (eval != null) {
return toString(eval);
}
return "";
} finally {
stdout.getBuffer()
.setLength(0);
stderr.getBuffer()
.setLength(0);
}
}
private String toString(Object eval) {
if (eval == null)
return "null";
if (eval instanceof Double || eval instanceof Float) {
String v = eval.toString();
return v.endsWith(".0") ? v.substring(0, v.length() - 2) : v;
}
return eval.toString();
}
private String toString(double eval) {
String v = Double.toString(eval);
return v.endsWith(".0") ? v.substring(0, v.length() - 2) : v;
}
static final String _toupperHelp = "${toupper;}";
public String _toupper(String[] args) throws Exception {
verifyCommand(args, _tolowerHelp, null, 2, 2);
return args[1].toUpperCase(Locale.ROOT);
}
static final String _tolowerHelp = "${tolower;}";
public String _tolower(String[] args) throws Exception {
verifyCommand(args, _tolowerHelp, null, 2, 2);
return args[1].toLowerCase(Locale.ROOT);
}
static final String _compareHelp = "${compare;;}";
public int _compare(String[] args) throws Exception {
verifyCommand(args, _compareHelp, null, 3, 3);
int n = args[1].compareTo(args[2]);
return Integer.signum(n);
}
static final String _ncompareHelp = "${ncompare;;}";
public int _ncompare(String[] args) throws Exception {
verifyCommand(args, _ncompareHelp, null, 3, 3);
double a = Double.parseDouble(args[1]);
double b = Double.parseDouble(args[2]);
return Integer.signum(Double.compare(a, b));
}
static final String _vcompareHelp = "${vcompare;;}";
public int _vcompare(String[] args) throws Exception {
verifyCommand(args, _vcompareHelp, null, 3, 3);
Version a = Version.valueOf(args[1]);
Version b = Version.valueOf(args[2]);
return Integer.signum(a.compareTo(b));
}
static final String _matchesHelp = "${matches;;}";
public boolean _matches(String[] args) throws Exception {
verifyCommand(args, _matchesHelp, null, 3, 3);
return args[1].matches(args[2]);
}
static final String _substHelp = "${subst;;[;[;count]]}";
public StringBuffer _subst(String[] args) throws Exception {
verifyCommand(args, _substHelp, null, 3, 5);
Pattern p = Pattern.compile(args[2]);
Matcher matcher = p.matcher(args[1]);
String replace = (args.length > 3) ? args[3] : "";
int count = (args.length > 4) ? Integer.parseInt(args[4]) : Integer.MAX_VALUE;
StringBuffer sb = new StringBuffer();
for (int i = 0; (i < count) && matcher.find(); i++) {
matcher.appendReplacement(sb, replace);
}
matcher.appendTail(sb);
return sb;
}
static final String _trimHelp = "${trim;}";
public String _trim(String[] args) throws Exception {
verifyCommand(args, _trimHelp, null, 2, 2);
return args[1].trim();
}
static final String _formatHelp = "${format;[;args...]}";
public String _format(String[] args) throws Exception {
verifyCommand(args, _formatHelp, null, 2, Integer.MAX_VALUE);
return Formatters.format(args[1], asFunction(this::isTruthy), 2, args);
}
static final String _isemptyHelp = "${isempty;[...]}";
public boolean _isempty(String[] args) throws Exception {
verifyCommand(args, _isemptyHelp, null, 1, Integer.MAX_VALUE);
boolean result = Arrays.stream(args, 1, args.length)
.noneMatch(s -> !s.trim()
.isEmpty());
return result;
}
static final String _isnumberHelp = "${isnumber;[;...]}";
public boolean _isnumber(String[] args) throws Exception {
verifyCommand(args, _isnumberHelp, null, 2, Integer.MAX_VALUE);
boolean result = Arrays.stream(args, 1, args.length)
.allMatch(s -> NUMERIC_P.matcher(s)
.matches());
return result;
}
static final String _isHelp = "${is;;}";
public boolean _is(String[] args) throws Exception {
verifyCommand(args, _isHelp, null, 3, Integer.MAX_VALUE);
String a = args[1];
boolean result = Arrays.stream(args, 2, args.length)
.allMatch(a::equals);
return result;
}
/**
* Map a value from a list to a new value
*/
static final String _mapHelp = "${map;[;...]}";
public String _map(String[] args) throws Exception {
verifyCommand(args, _mapHelp, null, 2, Integer.MAX_VALUE);
String delimiter = SEMICOLON;
String prefix = "${" + args[1] + delimiter;
String suffix = "}";
String result = Arrays.stream(args, 2, args.length)
.flatMap(Strings::splitQuotedAsStream)
.map(s -> process(prefix + s + suffix))
.collect(Strings.joining());
return result;
}
/**
* Map a value from a list to a new value, providing the value and the index
*/
static final String _foreachHelp = "${foreach;[;...]}";
public String _foreach(String[] args) throws Exception {
verifyCommand(args, _foreachHelp, null, 2, Integer.MAX_VALUE);
String delimiter = SEMICOLON;
String prefix = "${" + args[1] + delimiter;
String suffix = "}";
List list = toList(args, 2, args.length);
String result = IntStream.range(0, list.size())
.mapToObj(n -> process(prefix + list.get(n) + delimiter + n + suffix))
.collect(Strings.joining());
return result;
}
/**
* Take a list and convert this to the arguments
*/
static final String _applyHelp = "${apply;[;...]}";
public String _apply(String[] args) throws Exception {
verifyCommand(args, _applyHelp, null, 2, Integer.MAX_VALUE);
String delimiter = SEMICOLON;
String prefix = "${" + args[1] + delimiter;
String suffix = "}";
String result = Arrays.stream(args, 2, args.length)
.flatMap(Strings::splitQuotedAsStream)
.collect(Collectors.joining(delimiter, prefix, suffix));
return process(result);
}
/**
* Format bytes
*/
public String _bytes(String[] args) {
try (Formatter sb = new Formatter()) {
for (String arg : args) {
long l = Long.parseLong(arg);
bytes(sb, l, 0, new String[] {
"b", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb", "Bb", "Geopbyte"
});
}
return sb.toString();
}
}
private void bytes(Formatter sb, double l, int i, String[] strings) {
if (l > 1024 && i < strings.length - 1) {
bytes(sb, l / 1024, i + 1, strings);
return;
}
l = Math.round(l * 10) / 10;
sb.format("%s %s", l, strings[i]);
}
static final String _globHelp = "${glob;} (turn it into a regular expression)";
public String _glob(String[] args) {
verifyCommand(args, _globHelp, null, 2, 2);
String glob = args[1];
boolean negate = false;
if (glob.startsWith("!")) {
glob = glob.substring(1);
negate = true;
}
Pattern pattern = Glob.toPattern(glob);
if (negate)
return "(?!" + pattern.pattern() + ")";
else
return pattern.pattern();
}
public boolean doCondition(String arg) throws Exception {
ExtendedFilter f = new ExtendedFilter(arg);
return f.match(key -> {
if (key.endsWith("[]")) {
key = key.substring(0, key.length() - 2);
return Strings.split(domain.getProperty(key));
} else
return domain.getProperty(key);
});
}
/**
* Get all the commands available
*
* @return a map with commands and their help
*/
public Map getCommands() {
Set targets = new LinkedHashSet<>();
Collections.addAll(targets, this.targets);
Processor rover = domain;
while (rover != null) {
targets.add(rover);
rover = rover.getParent();
}
targets.add(this);
return targets.stream()
.map(Object::getClass)
.map(Class::getMethods)
.flatMap(Arrays::stream)
.filter(m -> !Modifier.isStatic(m.getModifiers()) && Modifier.isPublic(m.getModifiers()) && m.getName()
.startsWith("_"))
.collect(toMap(m -> m.getName()
.substring(1), m -> {
try {
Field f = m.getDeclaringClass()
.getDeclaredField(m.getName() + "Help");
f.setAccessible(true);
MethodHandle mh = publicLookup().unreflectGetter(f);
return (String) mh.invoke();
} catch (NoSuchFieldException nsfe) {
return "";
} catch (Exception e) {
return "";
} catch (Throwable e) {
throw Exceptions.duck(e);
}
}, (u, v) -> u, TreeMap::new));
}
/**
* Take a macro name that maps to a Parameters and expand its entries using
* a template. The macro takes a macro name. It will merge and decorate this
* name before it applies it to the template. Each entry is mapped to the
* template. The template can use {@code ${@}} for the key and
* {@code ${@attribute}} for attributes.
*
* It would be nice to take the parameters value directly but this is really
* hard to do with the quoting. That is why we use a name. It is always
* possible to have an intermediate macro
*
* @param args 'template', macro-name of Parameters, template, separator=','
* @return the expanded template.
* @throws IOException
*/
public String _template(String args[]) throws IOException {
verifyCommand(args, _templateHelp, null, 3, 30);
Parameters parameters = domain.decorated(args[1]);
String template = Arrays.stream(args, 2, args.length)
.collect(Collectors.joining(SEMICOLON));
try (Processor scope = new Processor(domain)) {
Properties properties = scope.getProperties();
Macro replacer = scope.getReplacer();
String templated = parameters.stream()
.mapToObj((key, value) -> {
properties.clear(); // avoid attr leakage between keys
properties.setProperty("@", removeDuplicateMarker(key));
value.forEach((attrKey, attrValue) -> properties.setProperty("@".concat(attrKey), attrValue));
String instance = replacer.process(template);
return instance;
})
.collect(Strings.joining());
return templated;
}
}
final static String _templateHelp = "${template;macro-name[;template]+}";
/**
* Return the merged and decorated value of a macro
*/
public String _decorated(String args[]) throws Exception {
verifyCommand(args, _decoratedHelp, null, 2, 3);
boolean literals = args.length < 3 ? false : isTruthy(args[2]);
Parameters decorated = domain.decorated(args[1], literals);
return decorated.toString();
}
final static String _decoratedHelp = "${decorated;macro-name[;literals]}";
/**
* Test macro to have exceptions, only active when {@link #inTest} is
* active.
*
* @param args currently only 'exception'
* @return nothing of valeue
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
public String __testdebug(String[] args) throws Throwable {
if (inTest) {
if ("exception".equals(args[1])) {
Class extends Throwable> c = args.length > 2 ? (Class) Class.forName(args[2])
: RuntimeException.class;
Throwable e = args.length > 3 ? c.getConstructor(String.class)
.newInstance(args[3])
: c.getConstructor()
.newInstance();
throw e;
}
}
return null;
}
static final String _fileuriHelp = "${fileuri;}, Return a file uri for the specified path. Relative paths are resolved against the processor base.";
public String _fileuri(String args[]) throws Exception {
verifyCommand(args, _fileuriHelp, null, 2, 2);
File f = domain.getFile(args[1])
.getCanonicalFile();
return f.toURI()
.toString();
}
static final String _version_cleanupHelp = "${version_cleanup;}, Cleanup a potential maven version to make it match the OSGi Version syntax.";
public String _version_cleanup(String args[]) {
verifyCommand(args, _version_cleanupHelp, null, 2, 2);
return Analyzer.cleanupVersion(args[1]);
}
}