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.Processor Maven / Gradle / Ivy
package aQute.bnd.osgi;
import static aQute.libg.slf4j.GradleLogging.LIFECYCLE;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.Spliterator;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.osgi.util.promise.PromiseFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.service.Plugin;
import aQute.bnd.service.Registry;
import aQute.bnd.service.RegistryDonePlugin;
import aQute.bnd.service.RegistryPlugin;
import aQute.bnd.service.url.URLConnectionHandler;
import aQute.bnd.version.Version;
import aQute.bnd.version.VersionRange;
import aQute.lib.collections.Iterables;
import aQute.lib.exceptions.Exceptions;
import aQute.lib.hex.Hex;
import aQute.lib.io.IO;
import aQute.lib.io.IOConstants;
import aQute.lib.strings.Strings;
import aQute.lib.utf8properties.UTF8Properties;
import aQute.libg.cryptography.SHA1;
import aQute.libg.generics.Create;
import aQute.libg.reporter.ReporterAdapter;
import aQute.service.reporter.Reporter;
public class Processor extends Domain implements Reporter, Registry, Constants, Closeable {
private static final Logger logger = LoggerFactory.getLogger(Processor.class);
public static Reporter log;;
static {
ReporterAdapter reporterAdapter = new ReporterAdapter(System.out);
reporterAdapter.setTrace(true);
reporterAdapter.setExceptions(true);
reporterAdapter.setPedantic(true);
log = reporterAdapter;
}
static final int BUFFER_SIZE = IOConstants.PAGE_SIZE * 1;
static Pattern PACKAGES_IGNORED = Pattern
.compile("(java\\.lang\\.reflect|sun\\.reflect).*");
static ThreadLocal current = new ThreadLocal<>();
private final static ScheduledExecutorService scheduledExecutor;
private final static ExecutorService executor;
static {
ThreadFactory threadFactory = Executors.defaultThreadFactory();
executor = new ThreadPoolExecutor(0, 64, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory,
new RejectedExecutionHandler() {
/*
* We are stealing another's thread because we have hit max pool
* size, so we cannot let the runnable's exception propagate
* back up this thread.
*/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (executor.isShutdown()) {
return;
}
try {
r.run();
} catch (Throwable t) {
try {
Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler()
.uncaughtException(thread, t);
} catch (Throwable for_real) {
// we will ignore this
}
}
}
});
scheduledExecutor = new ScheduledThreadPoolExecutor(4, threadFactory);
}
private static PromiseFactory promiseFactory = new PromiseFactory(executor,
scheduledExecutor);
static Random random = new Random();
// TODO handle include files out of date
public final static String LIST_SPLITTER = "\\s*,\\s*";
private final static Pattern LIST_SPLITTER_PATTERN = Pattern.compile(LIST_SPLITTER);
final List errors = new ArrayList<>();
final List warnings = new ArrayList<>();
final Set basicPlugins = new HashSet<>();
private final Set toBeClosed = new HashSet<>();
private Set plugins;
boolean pedantic;
boolean trace;
boolean exceptions;
boolean fileMustExist = true;
private File base = new File("").getAbsoluteFile();
private URI baseURI = base.toURI();
Properties properties;
String profile;
private Macro replacer;
private long lastModified;
private File propertiesFile;
private boolean fixup = true;
long modified;
Processor parent;
private final CopyOnWriteArrayList included = new CopyOnWriteArrayList<>();
CL pluginLoader;
Collection filter;
HashSet missingCommand;
Boolean strict;
boolean fixupMessages;
public static class FileLine {
public static final FileLine DUMMY = new FileLine(null, 0, 0);
public File file;
public int line;
public int length;
public int start;
public int end;
public FileLine() {
}
public FileLine(File file, int line, int length) {
this.file = file;
this.line = line;
this.length = length;
}
public void set(SetLocation sl) {
sl.file(IO.absolutePath(file));
sl.line(line);
sl.length(length);
}
}
public Processor() {
properties = new UTF8Properties();
}
public Processor(Properties parent) {
properties = new UTF8Properties(parent);
}
public Processor(Processor processor) {
this(processor.getProperties0());
this.parent = processor;
}
public Processor(Properties props, boolean copy) {
if (copy)
properties = new UTF8Properties(props);
else
properties = props;
}
public void setParent(Processor processor) {
this.parent = processor;
Properties updated = new UTF8Properties(processor.getProperties0());
updated.putAll(getProperties0());
properties = updated;
}
public Processor getParent() {
return parent;
}
public Processor getTop() {
if (parent == null)
return this;
return parent.getTop();
}
public void getInfo(Reporter processor, String prefix) {
if (prefix == null)
prefix = getBase() + " :";
if (isFailOk())
addAll(warnings, processor.getErrors(), prefix, processor);
else
addAll(errors, processor.getErrors(), prefix, processor);
addAll(warnings, processor.getWarnings(), prefix, processor);
processor.getErrors()
.clear();
processor.getWarnings()
.clear();
}
public void getInfo(Reporter processor) {
getInfo(processor, "");
}
private void addAll(List to, List from, String prefix, Reporter reporter) {
try {
for (String message : from) {
String newMessage = prefix.isEmpty() ? message : prefix + message;
to.add(newMessage);
Location location = reporter.getLocation(message);
if (location != null) {
SetLocation newer = location(newMessage);
for (Field f : newer.getClass()
.getFields()) {
if (!"message".equals(f.getName())) {
f.set(newer, f.get(location));
}
}
}
}
} catch (Exception e) {
throw Exceptions.duck(e);
}
}
/**
* A processor can mark itself current for a thread.
*/
private Processor current() {
Processor p = current.get();
if (p == null)
return this;
return p;
}
@Override
public SetLocation warning(String string, Object... args) {
fixupMessages = false;
Processor p = current();
String s = formatArrays(string, args);
if (!p.warnings.contains(s))
p.warnings.add(s);
p.signal();
return p.location(s);
}
@Override
public SetLocation error(String string, Object... args) {
fixupMessages = false;
Processor p = current();
try {
if (p.isFailOk())
return p.warning(string, args);
String s = formatArrays(string, args);
if (!p.errors.contains(s))
p.errors.add(s);
return p.location(s);
} finally {
p.signal();
}
}
/**
* @deprecated Use SLF4J
* Logger.info(aQute.libg.slf4j.GradleLogging.LIFECYCLE)
* instead.
*/
@Override
@Deprecated
public void progress(float progress, String format, Object... args) {
Logger l = getLogger();
if (l.isInfoEnabled(LIFECYCLE)) {
String message = formatArrays(format, args);
if (progress > 0)
l.info(LIFECYCLE, "[{}] {}", (int) progress, message);
else
l.info(LIFECYCLE, "{}", message);
}
}
public void progress(String format, Object... args) {
progress(-1f, format, args);
}
public SetLocation error(String format, Throwable t, Object... args) {
return exception(t, format, args);
}
@Override
public SetLocation exception(Throwable t, String format, Object... args) {
Processor p = current();
if (p.trace) {
p.getLogger()
.info("Reported exception", t);
} else {
p.getLogger()
.debug("Reported exception", t);
}
if (p.exceptions) {
printExceptionSummary(t, System.err);
}
// unwrap InvocationTargetException
while ((t instanceof InvocationTargetException) && (t.getCause() != null)) {
t = t.getCause();
}
String s = formatArrays("Exception: %s", Exceptions.toString(t));
if (p.isFailOk()) {
p.warnings.add(s);
} else {
p.errors.add(s);
}
return error(format, args);
}
public int printExceptionSummary(Throwable e, PrintStream out) {
if (e == null) {
return 0;
}
int count = 10;
int n = printExceptionSummary(e.getCause(), out);
if (n == 0) {
out.println("Root cause: " + e.getMessage() + " :" + e.getClass()
.getName());
count = Integer.MAX_VALUE;
} else {
out.println("Rethrown from: " + e.toString());
}
out.println();
printStackTrace(e, count, out);
System.err.println();
return n + 1;
}
public void printStackTrace(Throwable e, int count, PrintStream out) {
e.printStackTrace(out);
}
public void signal() {}
@Override
public List getWarnings() {
fixupMessages();
return warnings;
}
@Override
public List getErrors() {
fixupMessages();
return errors;
}
/**
* Standard OSGi header parser.
*
* @param value
*/
static public Parameters parseHeader(String value, Processor logger) {
return new Parameters(value, logger);
}
public Parameters parseHeader(String value) {
return new Parameters(value, this);
}
public void addClose(Closeable jar) {
assert jar != null;
toBeClosed.add(jar);
}
public void removeClose(Closeable jar) {
assert jar != null;
toBeClosed.remove(jar);
}
@Override
public boolean isPedantic() {
return current().pedantic;
}
public void setPedantic(boolean pedantic) {
this.pedantic = pedantic;
}
public void use(Processor reporter) {
setPedantic(reporter.isPedantic());
setTrace(reporter.isTrace());
setExceptions(reporter.isExceptions());
setFailOk(reporter.isFailOk());
}
public static File getFile(File base, String file) {
return IO.getFile(base, file);
}
public File getFile(String file) {
return getFile(base, file);
}
/**
* Return a list of plugins that implement the given class.
*
* @param clazz Each returned plugin implements this class/interface
* @return A list of plugins
*/
@Override
public List getPlugins(Class clazz) {
List plugins = getPlugins().stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.collect(toList());
return plugins;
}
/**
* Returns the first plugin it can find of the given type.
*
* @param
* @param clazz
*/
@Override
public T getPlugin(Class clazz) {
Optional plugin = getPlugins().stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.findFirst();
return plugin.orElse(null);
}
/**
* Return a list of plugins. Plugins are defined with the -plugin command.
* They are class names, optionally associated with attributes. Plugins can
* implement the Plugin interface to see these attributes. Any object can be
* a plugin.
*/
public Set getPlugins() {
Set p;
synchronized (this) {
p = plugins;
if (p != null)
return p;
plugins = p = new CopyOnWriteArraySet<>();
missingCommand = new HashSet<>();
}
// We only use plugins now when they are defined on our level
// and not if it is in our parent. We inherit from our parent
// through the previous block.
String spe = getProperty(PLUGIN);
if (NONE.equals(spe))
return p;
// The owner of the plugin is always in there.
p.add(this);
setTypeSpecificPlugins(p);
if (parent != null)
p.addAll(parent.getPlugins());
//
// Look only local
//
spe = mergeLocalProperties(PLUGIN);
String pluginPath = mergeProperties(PLUGINPATH);
loadPlugins(p, spe, pluginPath);
addExtensions(p);
for (RegistryDonePlugin rdp : getPlugins(RegistryDonePlugin.class)) {
try {
rdp.done();
} catch (Exception e) {
error("Calling done on %s, gives an exception %s", rdp, e);
}
}
return p;
}
/**
* Is called when all plugins are loaded
*
* @param p
*/
protected void addExtensions(Set p) {
}
/**
* Magic to load the plugins. This is quite tricky actually since we allow
* plugins to be downloaded (this is mainly intended for repositories since
* in general plugins should use extensions, however to bootstrap the
* extensions we need more). Since downloads might need plugins for
* passwords and protocols we need to first load the paths specified on the
* plugin clause, then check if there are any local plugins (starting with
* aQute.bnd and be able to load from our own class loader).
*
* After that, we load the plugin paths, these can use the built in
* connectors.
*
* Last but not least, we load the remaining plugins.
*
* @param instances
* @param pluginString
*/
protected void loadPlugins(Set instances, String pluginString, String pluginPathString) {
Parameters plugins = new Parameters(pluginString, this);
CL loader = getLoader();
// First add the plugin-specific paths from their path: directives
for (Entry entry : plugins.entrySet()) {
String key = removeDuplicateMarker(entry.getKey());
String path = entry.getValue()
.get(PATH_DIRECTIVE);
if (path != null) {
String parts[] = path.split("\\s*,\\s*");
try {
for (String p : parts) {
File f = getFile(p).getAbsoluteFile();
loader.addURL(f.toURI()
.toURL());
}
} catch (Exception e) {
error("Problem adding path %s to loader for plugin %s. Exception: (%s)", path, key, e);
}
}
}
//
// Try to load any plugins that are local
// these must start with aQute.bnd.* and
// and be possible to load. The main intention
// of this code is to load the URL connectors so that
// any access to remote plugins can use the connector
// model.
//
Set loaded = new HashSet<>();
for (Entry entry : plugins.entrySet()) {
String className = removeDuplicateMarker(entry.getKey());
Attrs attrs = entry.getValue();
logger.debug("Trying pre-plugin {}", className);
Object plugin = loadPlugin(getClass().getClassLoader(), attrs, className, true);
if (plugin != null) {
// with the marker!!
loaded.add(entry.getKey());
instances.add(plugin);
}
}
//
// Make sure we load each plugin only once
// by removing the entries that were successfully loaded
//
plugins.keySet()
.removeAll(loaded);
loadPluginPath(instances, pluginPathString, loader);
//
// Load the remaining plugins
//
for (Entry entry : plugins.entrySet()) {
String className = removeDuplicateMarker(entry.getKey());
Attrs attrs = entry.getValue();
logger.debug("Loading secondary plugin {}", className);
// We can defer the error if the plugin specifies
// a command name. In that case, we'll verify that
// a bnd file does not contain any references to a
// plugin
// command. The reason this feature was added was
// to compile plugin classes with the same build.
String commands = attrs.get(COMMAND_DIRECTIVE);
Object plugin = loadPlugin(loader, attrs, className, commands != null);
if (plugin != null)
instances.add(plugin);
else {
if (commands == null)
error("Cannot load the plugin %s", className);
else {
Collection cs = split(commands);
missingCommand.addAll(cs);
}
}
}
}
/**
* Add the @link {@link Constants#PLUGINPATH} entries (which are file names)
* to the class loader. If this file does not exist, and there is a
* {@link Constants#PLUGINPATH_URL_ATTR} attribute then we download it first
* from that url. You can then also specify a
* {@link Constants#PLUGINPATH_SHA1_ATTR} attribute to verify the file.
*
* @see PLUGINPATH
* @param pluginPath the clauses for the plugin path
* @param loader The class loader to extend
*/
private void loadPluginPath(Set instances, String pluginPath, CL loader) {
Parameters pluginpath = new Parameters(pluginPath, this);
nextClause: for (Entry entry : pluginpath.entrySet()) {
File f = getFile(entry.getKey()).getAbsoluteFile();
if (!f.isFile()) {
//
// File does not exist! Check if we need to download
//
String url = entry.getValue()
.get(PLUGINPATH_URL_ATTR);
if (url != null) {
try {
logger.debug("downloading {} to {}", url, f.getAbsoluteFile());
URL u = new URL(url);
URLConnection connection = u.openConnection();
//
// Allow the URLCOnnectionHandlers to interact with the
// connection so they can sign it or decorate it with
// a password etc.
//
for (Object plugin : instances) {
if (plugin instanceof URLConnectionHandler) {
URLConnectionHandler handler = (URLConnectionHandler) plugin;
if (handler.matches(u))
handler.handle(connection);
}
}
//
// Copy the url to the file
//
IO.mkdirs(f.getParentFile());
IO.copy(connection.getInputStream(), f);
//
// If there is a sha specified, we verify the download
// of the
// the file.
//
String digest = entry.getValue()
.get(PLUGINPATH_SHA1_ATTR);
if (digest != null) {
if (Hex.isHex(digest.trim())) {
byte[] sha1 = Hex.toByteArray(digest);
byte[] filesha1 = SHA1.digest(f)
.digest();
if (!Arrays.equals(sha1, filesha1)) {
error(
"Plugin path: %s, specified url %s and a sha1 but the file does not match the sha",
entry.getKey(), url);
}
} else {
error("Plugin path: %s, specified url %s and a sha1 '%s' but this is not a hexadecimal",
entry.getKey(), url, digest);
}
}
} catch (Exception e) {
error("Failed to download plugin %s from %s, error %s", entry.getKey(), url, e);
continue nextClause;
}
} else {
error("No such file %s from %s and no 'url' attribute on the path so it can be downloaded",
entry.getKey(), this);
continue nextClause;
}
}
logger.debug("Adding {} to loader for plugins", f);
try {
loader.addURL(f.toURI()
.toURL());
} catch (MalformedURLException e) {
// Cannot happen since every file has a correct url
}
}
}
/**
* Load a plugin and customize it. If the plugin cannot be loaded then we
* return null.
*
* @param loader Name of the loader
* @param attrs
* @param className
*/
private Object loadPlugin(ClassLoader loader, Attrs attrs, String className, boolean ignoreError) {
try {
Class> c = loader.loadClass(className);
Object plugin = c.getConstructor()
.newInstance();
customize(plugin, attrs);
if (plugin instanceof Closeable) {
addClose((Closeable) plugin);
}
return plugin;
} catch (NoClassDefFoundError e) {
if (!ignoreError)
exception(e, "Failed to load plugin %s;%s, error: %s ", className, attrs, e);
} catch (ClassNotFoundException e) {
if (!ignoreError)
exception(e, "Failed to load plugin %s;%s, error: %s ", className, attrs, e);
} catch (Exception e) {
exception(e, "Unexpected error loading plugin %s-%s: %s", className, attrs, e);
}
return null;
}
protected void setTypeSpecificPlugins(Set list) {
list.add(getExecutor());
list.add(getPromiseFactory());
list.add(random);
list.addAll(basicPlugins);
}
/**
* Set the initial parameters of a plugin
*
* @param plugin
* @param map
*/
protected T customize(T plugin, Attrs map) {
if (plugin instanceof Plugin) {
((Plugin) plugin).setReporter(this);
try {
if (map == null)
map = Attrs.EMPTY_ATTRS;
((Plugin) plugin).setProperties(map);
} catch (Exception e) {
error("While setting properties %s on plugin %s, %s", map, plugin, e);
}
}
if (plugin instanceof RegistryPlugin) {
((RegistryPlugin) plugin).setRegistry(this);
}
return plugin;
}
/**
* Indicates that this run should ignore errors and succeed anyway
*
* @return true if this processor should return errors
*/
@Override
public boolean isFailOk() {
String v = getProperty(Constants.FAIL_OK, null);
return v != null && v.equalsIgnoreCase("true");
}
public File getBase() {
return base;
}
public URI getBaseURI() {
return baseURI;
}
public void setBase(File base) {
if (base == null) {
this.base = null;
baseURI = null;
} else {
this.base = base.getAbsoluteFile();
baseURI = base.toURI();
}
}
public void clear() {
errors.clear();
warnings.clear();
locations.clear();
fixupMessages = false;
}
public Logger getLogger() {
return logger;
}
/**
* @deprecated Use SLF4J Logger.debug instead.
*/
@Override
@Deprecated
public void trace(String msg, Object... parms) {
Processor p = current();
Logger l = p.getLogger();
if (p.trace) {
if (l.isInfoEnabled()) {
l.info("{}", formatArrays(msg, parms));
}
} else {
if (l.isDebugEnabled()) {
l.debug("{}", formatArrays(msg, parms));
}
}
}
public List newList() {
return new ArrayList<>();
}
public Set newSet() {
return new TreeSet<>();
}
public static Map newMap() {
return new LinkedHashMap<>();
}
public static Map newHashMap() {
return new LinkedHashMap<>();
}
public List newList(Collection t) {
return new ArrayList<>(t);
}
public Set newSet(Collection t) {
return new TreeSet<>(t);
}
public Map newMap(Map t) {
return new LinkedHashMap<>(t);
}
@Override
public void close() throws IOException {
for (Closeable c : toBeClosed) {
IO.close(c);
}
synchronized (this) {
plugins = null;
}
if (pluginLoader != null) {
IO.close(pluginLoader);
pluginLoader = null;
}
toBeClosed.clear();
}
public String _basedir(@SuppressWarnings("unused") String args[]) {
if (base == null)
throw new IllegalArgumentException("No base dir set");
return IO.absolutePath(base);
}
public String _propertiesname(String[] args) {
if (args.length > 1) {
error("propertiesname does not take arguments");
return null;
}
File pf = getPropertiesFile();
if (pf == null)
return "";
return pf.getName();
}
public String _propertiesdir(String[] args) {
if (args.length > 1) {
error("propertiesdir does not take arguments");
return null;
}
File pf = getPropertiesFile();
if (pf == null)
return "";
return IO.absolutePath(pf.getParentFile());
}
static String _uri = "${uri;[;]}, Resolve the uri against the baseuri. baseuri defaults to the processor base.";
public String _uri(String args[]) throws Exception {
Macro.verifyCommand(args, _uri, null, 2, 3);
URI uri = new URI(args[1]);
if (!uri.isAbsolute() || uri.getScheme()
.equals("file")) {
URI base;
if (args.length > 2) {
base = new URI(args[2]);
} else {
base = getBaseURI();
if (base == null) {
throw new IllegalArgumentException("No base dir set");
}
}
uri = base.resolve(uri.getSchemeSpecificPart());
}
return uri.toString();
}
static String _fileuri = "${fileuri;}, Return a file uri for the specified path. Relative paths are resolved against the processor base.";
public String _fileuri(String args[]) throws Exception {
Macro.verifyCommand(args, _fileuri, null, 2, 2);
File f = IO.getFile(getBase(), args[1])
.getCanonicalFile();
return f.toURI()
.toString();
}
/**
* Property handling ...
*/
public Properties getProperties() {
if (fixup) {
fixup = false;
begin();
}
fixupMessages = false;
return getProperties0();
}
private Properties getProperties0() {
return properties;
}
public String getProperty(String key) {
return getProperty(key, null);
}
public void mergeProperties(File file, boolean override) {
if (file.isFile()) {
try {
Properties properties = loadProperties(file);
mergeProperties(properties, override);
} catch (Exception e) {
error("Error loading properties file: %s", file);
}
} else {
if (!file.exists())
error("Properties file does not exist: %s", file);
else
error("Properties file must a file, not a directory: %s", file);
}
}
public void mergeProperties(Properties properties, boolean override) {
for (Enumeration> e = properties.propertyNames(); e.hasMoreElements();) {
String key = (String) e.nextElement();
String value = properties.getProperty(key);
if (override || !getProperties().containsKey(key))
setProperty(key, value);
}
}
public void setProperties(Properties properties) {
doIncludes(getBase(), properties);
getProperties0().putAll(properties);
mergeProperties(Constants.INIT); // execute macros in -init
getProperties0().remove(Constants.INIT);
}
public void setProperties(File base, Properties properties) {
doIncludes(base, properties);
getProperties0().putAll(properties);
}
public void addProperties(File file) throws Exception {
addIncluded(file);
Properties p = loadProperties(file);
setProperties(p);
}
public void addProperties(Map, ?> properties) {
for (Entry, ?> entry : properties.entrySet()) {
setProperty(entry.getKey()
.toString(), entry.getValue() + "");
}
}
public void addIncluded(File file) {
addIncludedIfAbsent(file);
}
private boolean addIncludedIfAbsent(File file) {
return included.addIfAbsent(file);
}
private boolean removeIncluded(File file) {
return included.remove(file);
}
/**
* Inspect the properties and if you find -includes parse the line included
* manifest files or properties files. The files are relative from the given
* base, this is normally the base for the analyzer.
*
* @param ubase
* @param p
* @param done
* @throws IOException
* @throws IOException
*/
private void doIncludes(File ubase, Properties p) {
String includes = p.getProperty(INCLUDE);
if (includes != null) {
includes = getReplacer().process(includes);
p.remove(INCLUDE);
Collection clauses = new Parameters(includes, this).keySet();
for (String value : clauses) {
boolean fileMustExist = true;
boolean overwrite = true;
while (true) {
if (value.startsWith("-")) {
fileMustExist = false;
value = value.substring(1)
.trim();
} else if (value.startsWith("~")) {
// Overwrite properties!
overwrite = false;
value = value.substring(1)
.trim();
} else
break;
}
try {
File file = getFile(ubase, value).getAbsoluteFile();
if (!file.isFile()) {
try {
URL url = new URL(value);
int n = value.lastIndexOf('.');
String ext = ".jar";
if (n >= 0)
ext = value.substring(n);
Path tmp = Files.createTempFile("url", ext);
try (Resource resource = Resource.fromURL(url)) {
try (OutputStream out = IO.outputStream(tmp)) {
resource.write(out);
}
Files.setLastModifiedTime(tmp, FileTime.fromMillis(resource.lastModified()));
doIncludeFile(tmp.toFile(), overwrite, p);
} finally {
removeIncluded(tmp.toFile());
IO.delete(tmp);
}
} catch (MalformedURLException mue) {
if (fileMustExist)
error("Included file %s %s", file,
(file.isDirectory() ? "is directory" : "does not exist"));
} catch (Exception e) {
if (fileMustExist)
exception(e, "Error in processing included URL: %s", value);
}
} else
doIncludeFile(file, overwrite, p);
} catch (Exception e) {
if (fileMustExist)
exception(e, "Error in processing included file: %s", value);
}
}
}
}
/**
* @param file
* @param overwrite
* @throws FileNotFoundException
* @throws IOException
*/
public void doIncludeFile(File file, boolean overwrite, Properties target) throws Exception {
doIncludeFile(file, overwrite, target, null);
}
/**
* @param file
* @param overwrite
* @param extensionName
* @throws FileNotFoundException
* @throws IOException
*/
public void doIncludeFile(File file, boolean overwrite, Properties target, String extensionName) throws Exception {
if (!addIncludedIfAbsent(file)) {
error("Cyclic or multiple include of %s", file);
}
updateModified(file.lastModified(), file.toString());
Properties sub;
if (file.getName()
.toLowerCase()
.endsWith(".mf")) {
try (InputStream in = IO.stream(file)) {
sub = getManifestAsProperties(in);
}
} else
sub = loadProperties(file);
doIncludes(file.getParentFile(), sub);
// make sure we do not override properties
for (Map.Entry, ?> entry : sub.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (overwrite || !target.containsKey(key)) {
target.setProperty(key, value);
} else if (extensionName != null) {
String extensionKey = extensionName + "." + key;
if (!target.containsKey(extensionKey))
target.setProperty(extensionKey, value);
}
}
}
public void unsetProperty(String string) {
getProperties().remove(string);
}
public boolean refresh() {
synchronized (this) {
plugins = null; // We always refresh our plugins
}
if (pluginLoader != null) {
IO.close(pluginLoader);
pluginLoader = null;
}
if (propertiesFile == null)
return false;
boolean changed = updateModified(propertiesFile.lastModified(), "properties file");
for (File file : getIncluded()) {
if (changed)
break;
changed |= !file.exists() || updateModified(file.lastModified(), "include file: " + file);
}
profile = getProperty(PROFILE); // Used in property access
if (changed) {
forceRefresh();
return true;
}
return false;
}
/**
* If strict is true, then extra verification is done.
*/
boolean isStrict() {
if (strict == null)
strict = isTrue(getProperty(STRICT)); // Used in property access
return strict;
}
/**
*
*/
public void forceRefresh() {
included.clear();
Processor p = getParent();
properties = (p != null) ? new UTF8Properties(p.getProperties0()) : new UTF8Properties();
setProperties(propertiesFile, base);
propertiesChanged();
}
public void propertiesChanged() {}
/**
* Set the properties by file. Setting the properties this way will also set
* the base for this analyzer. After reading the properties, this will call
* setProperties(Properties) which will handle the includes.
*
* @param propertiesFile
*/
public void setProperties(File propertiesFile) {
propertiesFile = propertiesFile.getAbsoluteFile();
setProperties(propertiesFile, propertiesFile.getParentFile());
}
public void setProperties(File propertiesFile, File base) {
this.propertiesFile = propertiesFile.getAbsoluteFile();
setBase(base);
try {
if (propertiesFile.isFile()) {
// System.err.println("Loading properties " + propertiesFile);
long modified = propertiesFile.lastModified();
if (modified > System.currentTimeMillis() + 100) {
System.err.println("Huh? This is in the future " + propertiesFile);
this.modified = System.currentTimeMillis();
} else
this.modified = modified;
included.clear();
Properties p = loadProperties(propertiesFile);
setProperties(p);
} else {
if (fileMustExist) {
error("No such properties file: %s", propertiesFile);
}
}
} catch (IOException e) {
error("Could not load properties %s", propertiesFile);
}
}
protected void begin() {
if (isTrue(getProperty(PEDANTIC)))
setPedantic(true);
}
public static boolean isTrue(String value) {
if (value == null)
return false;
value = value.trim();
if (value.isEmpty())
return false;
if (value.startsWith("!"))
if (value.equals("!"))
return false;
else
return !isTrue(value.substring(1));
if ("false".equalsIgnoreCase(value))
return false;
if ("off".equalsIgnoreCase(value))
return false;
if ("not".equalsIgnoreCase(value))
return false;
return true;
}
/**
* Get a property without preprocessing it with a proper default
*
* @param key
* @param deflt
*/
public String getUnprocessedProperty(String key, String deflt) {
if (filter != null && filter.contains(key)) {
return (String) getProperties().getOrDefault(key, deflt);
}
return getProperties().getProperty(key, deflt);
}
/**
* Get a property with preprocessing it with a proper default
*
* @param key
* @param deflt
*/
public String getProperty(String key, String deflt) {
return getProperty(key, deflt, ",");
}
public String getProperty(String key, String deflt, String separator) {
return getProperty(key, deflt, separator, true);
}
@SuppressWarnings("resource")
private String getProperty(String key, String deflt, String separator, boolean inherit) {
Instruction ins = new Instruction(key);
if (!ins.isLiteral()) {
return getWildcardProperty(deflt, separator, inherit, ins);
}
return getLiteralProperty(key, deflt, this, inherit);
}
private String getWildcardProperty(String deflt, String separator, boolean inherit, Instruction ins) {
// Handle a wildcard key, make sure they're sorted
// for consistency
String result = stream(inherit).filter(ins::matches)
.sorted()
.map(k -> getLiteralProperty(k, null, this, inherit))
.filter(v -> (v != null) && !v.isEmpty())
.collect(Strings.joining(separator, "", "", deflt));
return result;
}
private String getLiteralProperty(String key, String deflt, Processor source, boolean inherit) {
String value = null;
// Use the key as is first, if found ok
if (filter != null && filter.contains(key)) {
Object raw = getProperties().get(key);
if (raw != null) {
if (raw instanceof String) {
value = (String) raw;
} else {
warning("Key '%s' has a non-String value: %s:%s", key, raw == null ? ""
: raw.getClass()
.getName(),
raw);
}
}
} else {
for (Processor proc = source; proc != null; proc = proc.getParent()) {
Object raw = proc.getProperties()
.get(key);
if (raw != null) {
if (raw instanceof String) {
value = (String) raw;
} else {
warning("Key '%s' has a non-String value: %s:%s", key, raw == null ? ""
: raw.getClass()
.getName(),
raw);
}
source = proc;
break;
}
if (!inherit)
break;
}
//
// Check if we can find a replacement through the
// replacer, which takes profiles into account
if (value == null) {
value = getReplacer().getMacro(key, null);
}
}
if (value != null)
return getReplacer().process(value, source);
else if (deflt != null)
return getReplacer().process(deflt, this);
else
return null;
}
/**
* Helper to load a properties file from disk.
*
* @param file
* @throws IOException
*/
public Properties loadProperties(File file) throws IOException {
updateModified(file.lastModified(), "Properties file: " + file);
UTF8Properties p = loadProperties0(file);
return p;
}
/**
* Load Properties from disk. The default encoding is ISO-8859-1 but
* nowadays all files are encoded with UTF-8. So we try to load it first as
* UTF-8 and if this fails we fail back to ISO-8859-1
*
* @param in The stream to load from
* @param name The name of the file for doc reasons
* @return a Properties
* @throws IOException
*/
UTF8Properties loadProperties0(File file) throws IOException {
try {
UTF8Properties p = new UTF8Properties();
p.load(file, this);
return p.replaceHere(file.getParentFile());
} catch (Exception e) {
error("Error during loading properties file: %s, error: %s", file, e);
return new UTF8Properties();
}
}
/**
* Replace a string in all the values of the map. This can be used to
* preassign variables that change. I.e. the base directory ${.} for a
* loaded properties
*/
public static Properties replaceAll(Properties p, String pattern, String replacement) {
UTF8Properties result = new UTF8Properties();
Pattern regex = Pattern.compile(pattern);
for (Map.Entry entry : p.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
value = regex.matcher(value)
.replaceAll(replacement);
result.put(key, value);
}
return result;
}
/**
* Print a standard Map based OSGi header.
*
* @param exports map { name => Map { attribute|directive => value } }
* @return the clauses
* @throws IOException
*/
public static String printClauses(Map, ? extends Map, ?>> exports) throws IOException {
return printClauses(exports, false);
}
public static String printClauses(Map, ? extends Map, ?>> exports,
@SuppressWarnings("unused") boolean checkMultipleVersions) throws IOException {
StringBuilder sb = new StringBuilder();
String del = "";
for (Entry, ? extends Map, ?>> entry : exports.entrySet()) {
String name = entry.getKey()
.toString();
Map, ?> clause = entry.getValue();
// We allow names to be duplicated in the input
// by ending them with '~'. This is necessary to use
// the package names as keys. However, we remove these
// suffixes in the output so that you can set multiple
// exports with different attributes.
String outname = removeDuplicateMarker(name);
sb.append(del);
sb.append(outname);
printClause(clause, sb);
del = ",";
}
return sb.toString();
}
public static void printClause(Map, ?> map, StringBuilder sb) throws IOException {
if (map instanceof Attrs) {
Attrs attrs = (Attrs) map;
for (Entry entry : attrs.entrySet()) {
String key = entry.getKey();
// Skip directives we do not recognize
if (skipPrint(key))
continue;
sb.append(";");
attrs.append(sb, entry);
}
} else {
for (Entry, ?> entry : map.entrySet()) {
String key = entry.getKey()
.toString();
// Skip directives we do not recognize
if (skipPrint(key))
continue;
sb.append(";");
sb.append(key);
sb.append("=");
String value = ((String) entry.getValue()).trim();
quote(sb, value);
}
}
}
private static boolean skipPrint(String key) {
switch (key) {
case INTERNAL_SOURCE_DIRECTIVE :
case INTERNAL_EXPORTED_DIRECTIVE :
case NO_IMPORT_DIRECTIVE :
case PROVIDE_DIRECTIVE :
case SPLIT_PACKAGE_DIRECTIVE :
case FROM_DIRECTIVE :
return true;
default :
return false;
}
}
/**
* @param sb
* @param value
* @throws IOException
*/
public static boolean quote(Appendable sb, String value) throws IOException {
return OSGiHeader.quote(sb, value);
}
public Macro getReplacer() {
if (replacer == null)
return replacer = new Macro(this, getMacroDomains());
return replacer;
}
/**
* This should be overridden by subclasses to add extra macro command
* domains on the search list.
*/
protected Object[] getMacroDomains() {
return new Object[] {};
}
/**
* Return the properties but expand all macros. This always returns a new
* Properties object that can be used in any way.
*/
public Properties getFlattenedProperties() {
return getReplacer().getFlattenedProperties();
}
/**
* Return the properties but expand all macros. This always returns a new
* Properties object that can be used in any way.
*/
public Properties getFlattenedProperties(boolean ignoreInstructions) {
return getReplacer().getFlattenedProperties(ignoreInstructions);
}
/**
* Return all inherited property keys. The keys are sorted for consistent
* ordering.
*/
public Set getPropertyKeys(boolean inherit) {
Set result;
if (parent == null || !inherit) {
result = new TreeSet<>();
} else {
result = parent.getPropertyKeys(inherit);
}
for (Object o : getProperties0().keySet()) {
result.add(o.toString());
}
return result;
}
public boolean updateModified(long time, @SuppressWarnings("unused") String reason) {
if (time > lastModified) {
lastModified = time;
return true;
}
return false;
}
public long lastModified() {
return lastModified;
}
/**
* Add or override a new property.
*
* @param key
* @param value
*/
public void setProperty(String key, String value) {
checkheader: for (int i = 0; i < headers.length; i++) {
if (headers[i].equalsIgnoreCase(key)) {
key = headers[i];
break checkheader;
}
}
getProperties().put(key, value);
}
/**
* Read a manifest but return a properties object.
*
* @param in
* @throws IOException
*/
public static Properties getManifestAsProperties(InputStream in) throws IOException {
Properties p = new UTF8Properties();
Manifest manifest = new Manifest(in);
for (Iterator it = manifest.getMainAttributes()
.keySet()
.iterator(); it.hasNext();) {
Attributes.Name key = (Attributes.Name) it.next();
String value = manifest.getMainAttributes()
.getValue(key);
p.put(key.toString(), value);
}
return p;
}
public File getPropertiesFile() {
return propertiesFile;
}
public void setFileMustExist(boolean mustexist) {
fileMustExist = mustexist;
}
static public String read(InputStream in) throws Exception {
return IO.collect(in, UTF_8);
}
/**
* Join a list.
*/
public static String join(Collection> list) {
return join(list, ",");
}
public static String join(Collection> list, String delimeter) {
if (list == null || list.isEmpty())
return "";
StringBuilder sb = new StringBuilder();
String del = "";
for (Object item : list) {
sb.append(del);
sb.append(item);
del = delimeter;
}
return sb.toString();
}
public static String join(Collection>... lists) {
return join(",", lists);
}
public static String join(String delimeter, Collection>... lists) {
if (lists == null || lists.length == 0)
return "";
StringBuilder sb = new StringBuilder();
String del = "";
for (Collection> list : lists) {
for (Object item : list) {
sb.append(del);
sb.append(item);
del = delimeter;
}
}
return sb.toString();
}
public static String join(Object[] list, String delimeter) {
if (list == null || list.length == 0)
return "";
StringBuilder sb = new StringBuilder();
String del = "";
for (Object item : list) {
sb.append(del);
sb.append(item);
del = delimeter;
}
return sb.toString();
}
public static String join(T[] list) {
return join(list, ",");
}
public static void split(String s, Collection set) {
if (s == null || (s = s.trim()).isEmpty())
return;
for (String element : LIST_SPLITTER_PATTERN.split(s, 0)) {
if (!element.isEmpty())
set.add(element);
}
}
public static Collection split(String s) {
if (s == null || (s = s.trim()).isEmpty())
return Collections.emptyList();
return Arrays.asList(LIST_SPLITTER_PATTERN.split(s, 0));
}
public static Collection split(String s, String splitter) {
if (s == null || (s = s.trim()).isEmpty())
return Collections.emptyList();
return Arrays.asList(s.split(splitter, 0));
}
public static String merge(String... strings) {
ArrayList result = new ArrayList<>();
for (String s : strings) {
if (s != null)
split(s, result);
}
return join(result);
}
public boolean isExceptions() {
return exceptions;
}
public void setExceptions(boolean exceptions) {
this.exceptions = exceptions;
}
/**
* Make the file short if it is inside our base directory, otherwise long.
*
* @param file
*/
public String normalize(String file) {
file = IO.normalizePath(file);
String path = IO.absolutePath(base);
int len = path.length();
if (file.startsWith(path) && file.charAt(len) == '/') {
return file.substring(len + 1);
}
return file;
}
public String normalize(File file) {
return normalize(file.getAbsolutePath());
}
public static String removeDuplicateMarker(String key) {
int i = key.length() - 1;
while (i >= 0 && key.charAt(i) == DUPLICATE_MARKER)
--i;
return key.substring(0, i + 1);
}
public static boolean isDuplicate(String name) {
return name.length() > 0 && name.charAt(name.length() - 1) == DUPLICATE_MARKER;
}
public void setTrace(boolean x) {
trace = x;
}
public static class CL extends URLClassLoader {
CL(Processor p) {
super(new URL[0], p.getClass()
.getClassLoader());
}
@Override
protected void addURL(URL url) {
URL urls[] = getURLs();
for (URL u : urls) {
if (u.equals(url))
return;
}
super.addURL(url);
}
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
try {
Class> c = super.loadClass(name);
return c;
} catch (Throwable t) {
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append(" not found, parent: ");
sb.append(getParent());
sb.append(" urls:");
sb.append(Arrays.toString(getURLs()));
sb.append(" exception:");
sb.append(Exceptions.toString(t));
throw new ClassNotFoundException(sb.toString(), t);
}
}
}
protected CL getLoader() {
if (pluginLoader == null) {
pluginLoader = new CL(this);
}
return pluginLoader;
}
/*
* Check if this is a valid project.
*/
public boolean exists() {
return base != null && base.isDirectory() && propertiesFile != null && propertiesFile.isFile();
}
@Override
public boolean isOk() {
return isFailOk() || (getErrors().size() == 0);
}
/**
* Move errors and warnings to their proper place by scanning the fixup
* messages property.
*/
private void fixupMessages() {
if (fixupMessages)
return;
fixupMessages = true;
Parameters fixup = getMergedParameters(Constants.FIXUPMESSAGES);
if (fixup.isEmpty())
return;
Instructions instrs = new Instructions(fixup);
doFixup(instrs, errors, warnings, FIXUPMESSAGES_IS_ERROR);
doFixup(instrs, warnings, errors, FIXUPMESSAGES_IS_WARNING);
}
private void doFixup(Instructions instrs, List messages, List other, String type) {
for (int i = 0; i < messages.size(); i++) {
String message = messages.get(i);
Instruction matcher = instrs.finder(message);
if (matcher == null || matcher.isNegated())
continue;
Attrs attrs = instrs.get(matcher);
//
// Default the pattern applies to the errors and warnings
// but we can restrict it: e.g. restrict:=error
//
String restrict = attrs.get(FIXUPMESSAGES_RESTRICT_DIRECTIVE);
if (restrict != null && !restrict.equals(type))
continue;
//
// We can optionally replace the message with another text. E.g.
// replace:"hello world". This can use macro expansion, the ${@}
// macro is set to the old message.
//
String replace = attrs.get(FIXUPMESSAGES_REPLACE_DIRECTIVE);
if (replace != null) {
logger.debug("replacing {} with {}", message, replace);
setProperty("@", message);
message = getReplacer().process(replace);
messages.set(i, message);
unsetProperty("@");
}
//
//
String is = attrs.get(FIXUPMESSAGES_IS_DIRECTIVE);
if (attrs.isEmpty() || FIXUPMESSAGES_IS_IGNORE.equals(is)) {
messages.remove(i--);
} else {
if (is != null && !type.equals(is)) {
messages.remove(i--);
other.add(message);
}
}
}
}
public boolean check(String... pattern) throws IOException {
Set missed = Create.set();
if (pattern != null) {
for (String p : pattern) {
boolean match = false;
Pattern pat = Pattern.compile(p);
for (Iterator i = errors.iterator(); i.hasNext();) {
if (pat.matcher(i.next())
.find()) {
i.remove();
match = true;
}
}
for (Iterator i = warnings.iterator(); i.hasNext();) {
if (pat.matcher(i.next())
.find()) {
i.remove();
match = true;
}
}
if (!match)
missed.add(p);
}
}
if (missed.isEmpty() && isPerfect())
return true;
if (!missed.isEmpty())
System.err.println("Missed the following patterns in the warnings or errors: " + missed);
report(System.err);
return false;
}
protected void report(Appendable out) throws IOException {
if (errors.size() > 0) {
out.append(String.format("-----------------%nErrors%n"));
for (int i = 0; i < errors.size(); i++) {
out.append(String.format("%03d: %s%n", i, errors.get(i)));
}
}
if (warnings.size() > 0) {
out.append(String.format("-----------------%nWarnings%n"));
for (int i = 0; i < warnings.size(); i++) {
out.append(String.format("%03d: %s%n", i, warnings.get(i)));
}
}
}
public boolean isPerfect() {
return getErrors().size() == 0 && getWarnings().size() == 0;
}
public void setForceLocal(Collection local) {
filter = local;
}
/**
* Answer if the name is a missing plugin's command name. If a bnd file
* contains the command name of a plugin, and that plugin is not available,
* then an error is reported during manifest calculation. This allows the
* plugin to fail to load when it is not needed. We first get the plugins to
* ensure it is properly initialized.
*
* @param name
*/
public boolean isMissingPlugin(String name) {
getPlugins();
return missingCommand != null && missingCommand.contains(name);
}
/**
* Append two strings to for a path in a ZIP or JAR file. It is guaranteed
* to return a string that does not start, nor ends with a '/', while it is
* properly separated with slashes. Double slashes are properly removed.
*
*
* "/" + "abc/def/" becomes "abc/def"
* @param prefix @param suffix @return
*/
public static String appendPath(String... parts) {
StringBuilder sb = new StringBuilder();
boolean lastSlash = true;
for (String part : parts) {
for (int i = 0; i < part.length(); i++) {
char c = part.charAt(i);
if (c == '/') {
if (!lastSlash)
sb.append('/');
lastSlash = true;
} else {
sb.append(c);
lastSlash = false;
}
}
if (!lastSlash && sb.length() > 0) {
sb.append('/');
lastSlash = true;
}
}
if (lastSlash && sb.length() > 0)
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* Parse the a=b strings and return a map of them.
*
* @param attrs
* @param clazz
*/
public static Attrs doAttrbutes(Object[] attrs, Clazz clazz, Macro macro) {
Attrs map = new Attrs();
if (attrs == null || attrs.length == 0)
return map;
for (Object a : attrs) {
String attr = (String) a;
int n = attr.indexOf('=');
if (n > 0) {
map.put(attr.substring(0, n), macro.process(attr.substring(n + 1)));
} else
throw new IllegalArgumentException(formatArrays(
"Invalid attribute on package-info.java in %s , %s. Must be = ", clazz, attr));
}
return map;
}
/**
* This method is the same as String.format but it makes sure that any
* arrays are transformed to strings.
*
* @param string
* @param parms
*/
public static String formatArrays(String string, Object... parms) {
return Strings.format(string, parms);
}
/**
* Check if the object is an array and turn it into a string if it is,
* otherwise unchanged.
*
* @param object the object to make printable
* @return a string if it was an array or the original object
*/
public static Object makePrintable(Object object) {
if (object == null)
return null;
if (object.getClass()
.isArray()) {
return Arrays.toString(makePrintableArray(object));
}
return object;
}
private static Object[] makePrintableArray(Object array) {
final int length = Array.getLength(array);
Object[] output = new Object[length];
for (int i = 0; i < length; i++) {
output[i] = makePrintable(Array.get(array, i));
}
return output;
}
public static String append(String... strings) {
List result = Create.list();
for (String s : strings) {
result.addAll(split(s));
}
return join(result);
}
public synchronized Class> getClass(String type, File jar) throws Exception {
CL cl = getLoader();
cl.addURL(jar.toURI()
.toURL());
return cl.loadClass(type);
}
public boolean isTrace() {
return current().trace;
}
public static long getDuration(String tm, long dflt) {
if (tm == null)
return dflt;
tm = tm.toUpperCase();
TimeUnit unit = TimeUnit.MILLISECONDS;
Matcher m = Pattern.compile("\\s*(\\d+)\\s*(NANOSECONDS|MICROSECONDS|MILLISECONDS|SECONDS|MINUTES|HOURS|DAYS)?")
.matcher(tm);
if (m.matches()) {
long duration = Long.parseLong(tm);
String u = m.group(2);
if (u != null)
unit = TimeUnit.valueOf(u);
duration = TimeUnit.MILLISECONDS.convert(duration, unit);
return duration;
}
return dflt;
}
/**
* Generate a random string, which is guaranteed to be a valid Java
* identifier (first character is an ASCII letter, subsequent characters are
* ASCII letters or numbers). Takes an optional parameter for the length of
* string to generate; default is 8 characters.
*/
public String _random(String[] args) {
int numchars = 8;
if (args.length > 1) {
try {
numchars = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid character count parameter in ${random} macro.");
}
}
synchronized (Processor.class) {
if (random == null)
random = new Random();
}
char[] letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
char[] alphanums = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
char[] array = new char[numchars];
for (int i = 0; i < numchars; i++) {
char c;
if (i == 0)
c = letters[random.nextInt(letters.length)];
else
c = alphanums[random.nextInt(alphanums.length)];
array[i] = c;
}
return new String(array);
}
/**
*
* Generates a Capability string, in the format specified by the OSGi
* Provide-Capability header, representing the current native platform
* according to OSGi RFC 188. For example on Windows7 running on an x86_64
* processor it should generate the following:
*
*
*
* osgi.native;osgi.native.osname:List<String>="Windows7,Windows
* 7,Win32";osgi.native.osversion:Version=6.1.0;osgi.native.processor:List&
* lt;String>="x86-64,amd64,em64t,x86_64"
*
*
* @param args The array of properties. For example: the macro invocation of
* "${native_capability;osversion=3.2.4;osname=Linux}" results in
* an args array of
* [native_capability, osversion=3.2.4, osname=Linux]
*/
public String _native_capability(String... args) throws Exception {
return OSInformation.getNativeCapabilityClause(this, args);
}
/**
* Set the current command thread. This must be balanced with the
* {@link #endHandleErrors(Processor)} method. The method returns the
* previous command owner or null. The command owner will receive all
* warnings and error reports.
*/
protected Processor beginHandleErrors(String message) {
logger.debug("begin {}", message);
Processor previous = current.get();
current.set(this);
return previous;
}
/**
* End a command. Will restore the previous command owner.
*
* @param previous
*/
protected void endHandleErrors(Processor previous) {
logger.debug("end");
current.set(previous);
}
public static Executor getExecutor() {
return executor;
}
public static ScheduledExecutorService getScheduledExecutor() {
return scheduledExecutor;
}
public static PromiseFactory getPromiseFactory() {
return promiseFactory;
}
/**
* These plugins are added to the total list of plugins. The separation is
* necessary because the list of plugins is refreshed now and then so we
* need to be able to add them at any moment in time.
*
* @param plugin
*/
public synchronized void addBasicPlugin(Object plugin) {
basicPlugins.add(plugin);
Set p = plugins;
if (p != null)
p.add(plugin);
}
public synchronized void removeBasicPlugin(Object plugin) {
basicPlugins.remove(plugin);
Set p = plugins;
if (p != null)
p.remove(plugin);
}
public List getIncluded() {
return included;
}
/**
* Overrides for the Domain class
*/
@Override
public String get(String key) {
return getProperty(key);
}
@Override
public String get(String key, String deflt) {
return getProperty(key, deflt);
}
@Override
public void set(String key, String value) {
getProperties().setProperty(key, value);
}
Stream stream() {
return stream(true);
}
private Stream stream(boolean inherit) {
return StreamSupport.stream(iterable(inherit).spliterator(), false);
}
@Override
public Iterator iterator() {
return iterable(true).iterator();
}
@Override
public Spliterator spliterator() {
return iterable(true).spliterator();
}
private Iterable iterable(boolean inherit) {
Set first = getProperties0().keySet();
Iterable extends Object> second;
if (parent == null || !inherit) {
second = Collections.emptyList();
} else {
second = parent.iterable(inherit);
}
Iterable iterable = Iterables.distinct(first, second, o -> (o instanceof String) ? (String) o : null);
return iterable;
}
public Set keySet() {
return getPropertyKeys(true);
}
/**
* Printout of the status of this processor for toString()
*/
@Override
public String toString() {
try {
StringBuilder sb = new StringBuilder();
report(sb);
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Utiltity to replace an extension
*
* @param s
* @param extension
* @param newExtension
*/
public String replaceExtension(String s, String extension, String newExtension) {
if (s.endsWith(extension))
s = s.substring(0, s.length() - extension.length());
return s + newExtension;
}
/**
* Create a location object and add it to the locations
*/
List locations = new ArrayList<>();
static class SetLocationImpl extends Location implements SetLocation {
public SetLocationImpl(String s) {
this.message = s;
}
@Override
public SetLocation file(String file) {
this.file = (file != null) ? IO.normalizePath(file) : null;
return this;
}
@Override
public SetLocation header(String header) {
this.header = header;
return this;
}
@Override
public SetLocation context(String context) {
this.context = context;
return this;
}
@Override
public SetLocation method(String methodName) {
this.methodName = methodName;
return this;
}
@Override
public SetLocation line(int n) {
this.line = n;
return this;
}
@Override
public SetLocation reference(String reference) {
this.reference = reference;
return this;
}
@Override
public SetLocation details(Object details) {
this.details = details;
return this;
}
@Override
public Location location() {
return this;
}
@Override
public SetLocation length(int length) {
this.length = length;
return this;
}
}
private SetLocation location(String s) {
SetLocationImpl loc = new SetLocationImpl(s);
locations.add(loc);
return loc;
}
@Override
public Location getLocation(String msg) {
for (Location l : locations)
if ((l.message != null) && l.message.equals(msg))
return l;
return null;
}
/**
* Get a header relative to this processor, tking its parents and includes
* into account.
*
* @param header
* @throws IOException
*/
public FileLine getHeader(String header) throws Exception {
return getHeader(
Pattern.compile("^[ \t]*" + Pattern.quote(header), Pattern.MULTILINE + Pattern.CASE_INSENSITIVE));
}
public static Pattern toFullHeaderPattern(String header) {
StringBuilder sb = new StringBuilder();
sb.append("^[ \t]*(")
.append(header)
.append(")(\\.[^\\s:=]*)?[ \t]*[ \t:=][ \t]*");
sb.append("[^\\\\\n\r]*(\\\\\n[^\\\\\n\r]*)*");
try {
return Pattern.compile(sb.toString(), Pattern.MULTILINE + Pattern.CASE_INSENSITIVE);
} catch (Exception e) {
return Pattern.compile("^[ \t]*" + Pattern.quote(header), Pattern.MULTILINE + Pattern.CASE_INSENSITIVE);
}
}
public FileLine getHeader(Pattern header) throws Exception {
return getHeader(header, null);
}
public FileLine getHeader(String header, String clause) throws Exception {
return getHeader(toFullHeaderPattern(header), clause == null ? null : Pattern.compile(Pattern.quote(clause)));
}
public FileLine getHeader(Pattern header, Pattern clause) throws Exception {
FileLine fl = getHeader0(header, clause);
if (fl != null)
return fl;
@SuppressWarnings("resource")
Processor rover = this;
while (rover.getPropertiesFile() == null)
if (rover.parent == null) {
return new FileLine(new File("ANONYMOUS"), 0, 0);
} else
rover = rover.parent;
return new FileLine(rover.getPropertiesFile(), 0, 0);
}
private FileLine getHeader0(Pattern header, Pattern clause) throws Exception {
FileLine fl;
File f = getPropertiesFile();
if (f != null) {
// Find in "our" local file
fl = findHeader(f, header, clause);
if (fl != null)
return fl;
// Get the includes (actually should parse the header
// to see if they override or only provide defaults?
for (Iterator iter = new ArrayDeque<>(getIncluded()).descendingIterator(); iter.hasNext();) {
File file = iter.next();
fl = findHeader(file, header);
if (fl != null) {
return fl;
}
}
}
// Ok, not on this level ...
if (getParent() != null) {
fl = getParent().getHeader(header, clause);
if (fl != null)
return fl;
}
// Ok, report the error on the sub file
// Sometimes we do not have a file ...
if (f == null && parent != null)
f = parent.getPropertiesFile();
if (f == null)
return null;
return new FileLine(f, 0, 0);
}
public static FileLine findHeader(File f, String header) throws IOException {
return findHeader(f,
Pattern.compile("^[ \t]*" + Pattern.quote(header), Pattern.MULTILINE + Pattern.CASE_INSENSITIVE));
}
public static FileLine findHeader(File f, Pattern header) throws IOException {
return findHeader(f, header, null);
}
public static FileLine findHeader(File f, Pattern header, Pattern clause) throws IOException {
if (f.isFile()) {
String s = IO.collect(f);
Matcher matcher = header.matcher(s);
while (matcher.find()) {
FileLine fl = new FileLine();
fl.file = f;
fl.start = matcher.start();
fl.end = matcher.end();
fl.length = fl.end - fl.start;
fl.line = getLine(s, fl.start);
if (clause != null) {
Matcher mclause = clause.matcher(s);
mclause.region(fl.start, fl.end);
if (mclause.find()) {
fl.start = mclause.start();
fl.end = mclause.end();
} else
//
// If no clause matches, maybe
// we have merged headers
//
continue;
}
return fl;
}
}
return null;
}
public static int getLine(String s, int index) {
int n = 0;
while (--index > 0) {
char c = s.charAt(index);
if (c == '\n') {
n++;
}
}
return n;
}
/**
* This method is about compatibility. New behavior can be conditionally
* introduced by calling this method and passing what version this behavior
* was introduced. This allows users of bnd to set the -upto instructions to
* the version that they want to be compatible with. If this instruction is
* not set, we assume the latest version.
*/
Version upto = null;
public boolean since(Version introduced) {
if (upto == null) {
String uptov = getProperty(UPTO);
if (uptov == null) {
upto = Version.HIGHEST;
return true;
}
if (!Version.VERSION.matcher(uptov)
.matches()) {
error("The %s given version is not a version: %s", UPTO, uptov);
upto = Version.HIGHEST;
return true;
}
upto = new Version(uptov);
}
return upto.compareTo(introduced) >= 0;
}
/**
* Report the details of this processor. Should in general be overridden
*
* @param table
* @throws Exception
*/
public void report(Map table) throws Exception {
table.put("Included Files", getIncluded());
table.put("Base", getBase());
table.put("Properties", getProperties0().entrySet());
}
/**
* Simplified way to check booleans
*/
public boolean is(String propertyName) {
return isTrue(getProperty(propertyName));
}
/**
* Return merged properties. The parameters provide a list of property names
* which are concatenated in the output, separated by a comma. Not only are
* those property names looked for, also all property names that have that
* constant as a prefix, a '.', and then whatever (.*). The result is either
* null if nothing was found or a list of properties
*/
public String mergeProperties(String key) {
return mergeProperties(key, ",");
}
public String mergeLocalProperties(String key) {
if (since(About._3_3)) {
return getProperty(makeWildcard(key), null, ",", false);
} else
return mergeProperties(key);
}
public String mergeProperties(String key, String separator) {
if (since(About._2_4))
return getProperty(makeWildcard(key), null, separator, true);
else
return getProperty(key);
}
private String makeWildcard(String key) {
return key + ".*";
}
/**
* Get a Parameters from merged properties
*/
public Parameters getMergedParameters(String key) {
return new Parameters(mergeProperties(key), this);
}
/**
* Add an element to an array, creating a new one if necessary
*/
public T[] concat(Class type, T[] prefix, T suffix) {
@SuppressWarnings("unchecked")
T[] result = (T[]) Array.newInstance(type, (prefix != null ? prefix.length : 0) + 1);
if (result.length > 1) {
System.arraycopy(prefix, 0, result, 0, result.length - 1);
}
result[result.length - 1] = suffix;
return result;
}
/**
* Try to get a Jar from a file name/path or a url, or in last resort from
* the classpath name part of their files.
*
* @param name URL or filename relative to the base
* @param from Message identifying the caller for errors
* @return null or a Jar with the contents for the name
*/
public Jar getJarFromName(String name, String from) {
File file = new File(name);
if (!file.isAbsolute())
file = new File(getBase(), name);
if (file.exists())
try {
Jar jar = new Jar(file);
addClose(jar);
return jar;
} catch (Exception e) {
error("Exception in parsing jar file for %s: %s %s", from, name, e);
}
// It is not a file ...
try {
// Lets try a URL
URL url = new URL(name);
URLConnection connection = url.openConnection();
try (InputStream in = connection.getInputStream()) {
long lastModified = connection.getLastModified();
if (lastModified == 0L)
// We assume the worst :-(
lastModified = System.currentTimeMillis();
Jar jar = new Jar(fileName(url.getPath()), in, lastModified);
addClose(jar);
return jar;
}
} catch (IOException ee) {
// ignore
}
return null;
}
private String fileName(String path) {
int n = path.lastIndexOf('/');
if (n > 0)
return path.substring(n + 1);
return path;
}
/**
* Return the name of the properties file
*/
public String _thisfile(String[] args) {
if (propertiesFile == null) {
error("${thisfile} executed on a processor without a properties file");
return null;
}
return IO.absolutePath(propertiesFile);
}
/**
* Copy the settings of another processor
*/
public void getSettings(Processor p) {
this.trace = p.isTrace();
this.pedantic = p.isPedantic();
this.exceptions = p.isExceptions();
}
/**
* Return a range expression for a filter from a version. By default this is
* based on consumer compatibility. You can specify a third argument (true)
* to get provider compatibility.
*
*
* ${frange;1.2.3} ->
* (&(version>=1.2.3)(!(version>=2.0.0)) ${frange;1.2.3, true} ->
* (&(version>=1.2.3)(!(version>=1.3.0)) ${frange;[1.2.3,2.3.4)} ->
* (&(version>=1.2.3)(!(version>=2.3.4))
*
*/
public String _frange(String[] args) {
if (args.length < 2 || args.length > 3) {
error("Invalid filter range, 2 or 3 args ${frange;[;true|false]}");
return null;
}
String v = args[1];
boolean isProvider = args.length == 3 && isTrue(args[2]);
VersionRange vr;
if (Verifier.isVersion(v)) {
Version l = new Version(v);
Version h = isProvider ? new Version(l.getMajor(), l.getMinor() + 1, 0)
: new Version(l.getMajor() + 1, 0, 0);
vr = new VersionRange(true, l, h, false);
} else if (Verifier.isVersionRange(v)) {
vr = new VersionRange(v);
} else {
error("The _frange parameter %s is neither a version nor a version range", v);
return null;
}
return vr.toFilter();
}
public String _findfile(String args[]) {
File f = getFile(args[1]);
List files = new ArrayList<>();
tree(files, f, "", new Instruction(args[2]));
return join(files);
}
void tree(List list, File current, String path, Instruction instr) {
if (path.length() > 0)
path = path + "/";
String subs[] = current.list();
if (subs != null) {
for (String sub : subs) {
File f = new File(current, sub);
if (f.isFile()) {
if (instr.matches(sub) && !instr.isNegated())
list.add(path + sub);
} else
tree(list, f, path + sub, instr);
}
}
}
}