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 java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
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.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.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
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.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.ExtList;
import aQute.lib.collections.SortedList;
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 {
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 sheduledExecutor;
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
}
}
}
});
sheduledExecutor = new ScheduledThreadPoolExecutor(4, threadFactory);
}
static Random random = new Random();
// TODO handle include files out of date
// TODO make splitter skip eagerly whitespace so trim is not necessary
public final static String LIST_SPLITTER = "\\s*,\\s*";
final List errors = new ArrayList();
final List warnings = new ArrayList();
final Set basicPlugins = new HashSet();
private final Set toBeClosed = new HashSet();
Set plugins;
boolean pedantic;
boolean trace;
private final Logger logger = LoggerFactory.getLogger(getClass().getName());
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;
List included;
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(file.getAbsolutePath());
sl.line(line);
sl.length(length);
}
}
public Processor() {
properties = new UTF8Properties();
}
public Processor(Properties parent) {
properties = new UTF8Properties(parent);
}
public Processor(Processor child) {
this(child.properties);
this.parent = child;
}
public Processor(Properties props, boolean copy) {
if (copy)
properties = new UTF8Properties(props);
else
properties = props;
}
public void setParent(Processor processor) {
this.parent = processor;
Properties ext = new UTF8Properties(processor.properties);
ext.putAll(this.properties);
this.properties = ext;
}
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 + 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 new RuntimeException(e);
}
}
/**
* A processor can mark itself current for a thread.
*/
private Processor current() {
Processor p = current.get();
if (p == null)
return this;
return p;
}
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 location(s);
}
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 location(s);
} finally {
p.signal();
}
}
public void progress(float progress, String format, Object... args) {
String message = formatArrays(format, args);
if (progress > 0)
System.err.printf("[%2d] %s%n", (int) progress, message);
else
System.err.printf("%s%n", 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", 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) {
StackTraceElement st[] = e.getStackTrace();
String previousPkg = null;
boolean shorted = false;
if (count < st.length) {
shorted = true;
count--;
}
for (int i = 0; i < count && i < st.length; i++) {
String cname = st[i].getClassName();
String file = st[i].getFileName();
String method = st[i].getMethodName();
int line = st[i].getLineNumber();
String pkg = Descriptors.getPackage(cname);
if (PACKAGES_IGNORED.matcher(pkg).matches())
continue;
String shortName = Descriptors.getShortName(cname);
if (pkg.equals(previousPkg))
pkg = "''";
else
pkg += "";
if (file.equals(shortName + ".java"))
file = "";
else
file = " (" + file + ")";
String l;
if (st[i].isNativeMethod())
l = "native";
else if (line > 0)
l = "" + line;
else
l = "";
out.printf(" %10s %-40s %s %s%n", l, shortName + "." + method, pkg, file);
previousPkg = pkg;
}
if (shorted)
out.println("...");
}
public void signal() {}
public List getWarnings() {
fixupMessages();
return warnings;
}
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);
}
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
*/
public List getPlugins(Class clazz) {
List l = new ArrayList();
Set all = getPlugins();
for (Object plugin : all) {
if (clazz.isInstance(plugin))
l.add(clazz.cast(plugin));
}
return l;
}
/**
* Returns the first plugin it can find of the given type.
*
* @param
* @param clazz
*/
public T getPlugin(Class clazz) {
Set all = getPlugins();
for (Object plugin : all) {
if (clazz.isInstance(plugin))
return clazz.cast(plugin);
}
return 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() {
synchronized (this) {
if (this.plugins != null)
return this.plugins;
plugins = new LinkedHashSet();
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 plugins;
// The owner of the plugin is always in there.
plugins.add(this);
setTypeSpecificPlugins(plugins);
if (parent != null)
plugins.addAll(parent.getPlugins());
//
// Look only local
//
spe = mergeLocalProperties(PLUGIN);
String pluginPath = mergeProperties(PLUGINPATH);
loadPlugins(plugins, spe, pluginPath);
addExtensions(plugins);
for (RegistryDonePlugin rdp : getPlugins(RegistryDonePlugin.class)) {
try {
rdp.done();
} catch (Exception e) {
error("Calling done on %s, gives an exception %s", rdp, e);
}
}
return this.plugins;
}
/**
* Is called when all plugins are loaded
*
* @param plugins
*/
protected void addExtensions(Set plugins) {
}
/**
* 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);
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.add(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();
trace("Trying pre-plugin %s", 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();
trace("Loading secondary plugin %s", 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);
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 {
trace("downloading %s to %s", 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
//
f.getParentFile().mkdirs();
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;
}
}
trace("Adding %s to loader for plugins", f);
try {
loader.add(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(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(Analyzer.FAIL_OK, null);
return v != null && v.equalsIgnoreCase("true");
}
public File getBase() {
return base;
}
public URI getBaseURI() {
return baseURI;
}
public void setBase(File base) {
this.base = base;
baseURI = (base == null) ? null : base.toURI();
}
public void clear() {
errors.clear();
warnings.clear();
locations.clear();
fixupMessages = false;
}
public Logger getLogger() {
return logger;
}
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);
}
public void close() throws IOException {
for (Closeable c : toBeClosed) {
try {
c.close();
} catch (IOException e) {
// Who cares?
}
}
if (pluginLoader != null)
pluginLoader.closex();
toBeClosed.clear();
}
public String _basedir(@SuppressWarnings("unused") String args[]) {
if (base == null)
throw new IllegalArgumentException("No base dir set");
return base.getAbsolutePath();
}
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 pf.getParentFile().getAbsolutePath();
}
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 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);
this.properties.putAll(properties);
mergeProperties(Constants.INIT); // execute macros in -init
this.properties.remove(Constants.INIT);
}
public void setProperties(File base, Properties properties) {
doIncludes(base, properties);
this.properties.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 synchronized void addIncluded(File file) {
if (included == null)
included = new ArrayList();
included.add(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).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);
File tmp = File.createTempFile("url", ext);
try {
IO.copy(url.openStream(), tmp);
doIncludeFile(tmp, overwrite, p);
} finally {
tmp.delete();
}
} 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 (included != null && included.contains(file)) {
error("Cyclic or multiple include of %s", file);
} else {
addIncluded(file);
updateModified(file.lastModified(), file.toString());
Properties sub;
if (file.getName().toLowerCase().endsWith(".mf")) {
try (InputStream in = new FileInputStream(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() {
plugins = null; // We always refresh our plugins
if (propertiesFile == null)
return false;
boolean changed = updateModified(propertiesFile.lastModified(), "properties file");
if (included != null) {
for (File file : included) {
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 = null;
properties.clear();
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
* @throws FileNotFoundException
* @throws IOException
*/
public void setProperties(File propertiesFile) throws IOException {
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 = null;
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) {
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);
}
@SuppressWarnings("resource")
Processor source = this;
return getLiteralProperty(key, deflt, source, true);
}
private String getWildcardProperty(String deflt, String separator, boolean inherit, Instruction ins) {
// Handle a wildcard key, make sure they're sorted
// for consistency
SortedList sortedList = SortedList
.fromIterator(inherit ? iterator() : getLocalKeys().iterator());
StringBuilder sb = new StringBuilder();
String del = "";
for (String k : sortedList) {
if (ins.matches(k)) {
String v = getLiteralProperty(k, null, this, inherit);
if (v != null) {
sb.append(del);
del = separator;
sb.append(v);
}
}
}
if (sb.length() == 0)
return deflt;
return sb.toString();
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
private Set getLocalKeys() {
Set keySet = properties.keySet();
return keySet;
}
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 {
while (source != null) {
Object raw = source.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);
}
break;
}
if (inherit)
source = source.getParent();
else
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);
InputStream in = new FileInputStream(file);
try {
UTF8Properties p = loadProperties0(in, file);
return p;
} finally {
in.close();
}
}
/**
* 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(InputStream in, File file) throws IOException {
String name = file.getAbsoluteFile().toURI().getPath();
int n = name.lastIndexOf('/');
if (n > 0)
name = name.substring(0, n);
if (name.length() == 0)
name = ".";
try {
UTF8Properties p = new UTF8Properties();
p.load(in, file, this);
return replaceAll0(p, "\\$\\{\\.\\}", name);
} catch (Exception e) {
error("Error during loading properties file: %s, error: %s", name, 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
*/
private static UTF8Properties replaceAll0(Properties p, String pattern, String replacement) {
UTF8Properties result = new UTF8Properties();
for (Iterator> i = p.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = i.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
value = value.replaceAll(pattern, replacement);
result.put(key, value);
}
return result;
}
public static Properties replaceAll(Properties p, String pattern, String replacement) {
return replaceAll0(p, pattern, replacement);
}
/**
* 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 {
for (Entry< ? , ? > entry : map.entrySet()) {
Object key = entry.getKey();
// Skip directives we do not recognize
if (key.equals(INTERNAL_SOURCE_DIRECTIVE) || key.equals(INTERNAL_EXPORTED_DIRECTIVE)
|| key.equals(NO_IMPORT_DIRECTIVE) || key.equals(PROVIDE_DIRECTIVE)
|| key.equals(SPLIT_PACKAGE_DIRECTIVE) || key.equals(FROM_DIRECTIVE))
continue;
String value = ((String) entry.getValue()).trim();
sb.append(";");
sb.append(key);
sb.append("=");
quote(sb, value);
}
}
/**
* @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
*/
public Set getPropertyKeys(boolean inherit) {
Set result;
if (parent == null || !inherit) {
result = Create.set();
} else
result = parent.getPropertyKeys(inherit);
for (Object o : properties.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(value)) {
value = 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 {
InputStreamReader ir = new InputStreamReader(in, "UTF8");
StringBuilder sb = new StringBuilder();
try {
char chars[] = new char[BUFFER_SIZE];
int size = ir.read(chars);
while (size > 0) {
sb.append(chars, 0, size);
size = ir.read(chars);
}
} finally {
ir.close();
}
return sb.toString();
}
/**
* Join a list.
*/
public static String join(Collection< ? > list, String delimeter) {
return join(delimeter, list);
}
public static String join(String delimeter, Collection< ? >... list) {
StringBuilder sb = new StringBuilder();
String del = "";
if (list != null) {
for (Collection< ? > l : list) {
for (Object item : l) {
sb.append(del);
sb.append(item);
del = delimeter;
}
}
}
return sb.toString();
}
public static String join(Object[] list, String delimeter) {
if (list == null)
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< ? >... list) {
return join(",", list);
}
public static String join(T list[]) {
return join(list, ",");
}
public static void split(String s, Collection set) {
String elements[] = s.trim().split(LIST_SPLITTER);
for (String element : elements) {
if (element.length() > 0)
set.add(element);
}
}
public static Collection split(String s) {
return split(s, LIST_SPLITTER);
}
public static Collection split(String s, String splitter) {
if (s != null)
s = s.trim();
if (s == null || s.trim().length() == 0)
return Collections.emptyList();
return Arrays.asList(s.split(splitter));
}
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 f
*/
public String normalize(String f) {
if (f.startsWith(base.getAbsolutePath() + "/"))
return f.substring(base.getAbsolutePath().length() + 1);
return f;
}
public String normalize(File f) {
return normalize(f.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());
}
void closex() {
Class clazz = URLClassLoader.class;
try {
//
// Java 7 is a good boy, it has a close method
//
clazz.getMethod("close").invoke(this);
return;
} catch (Exception e) {
// ignore
}
//
// On Java 6, we're screwed and have to much around
// This is best effort, likely fails on non-SUN vms
// :-(
//
try {
Field ucpField = clazz.getDeclaredField("ucp");
ucpField.setAccessible(true);
Object cp = ucpField.get(this);
Field loadersField = cp.getClass().getDeclaredField("loaders");
loadersField.setAccessible(true);
Collection< ? > loaders = (Collection< ? >) loadersField.get(cp);
for (Object loader : loaders) {
try {
Field loaderField = loader.getClass().getDeclaredField("jar");
loaderField.setAccessible(true);
JarFile jarFile = (JarFile) loaderField.get(loader);
jarFile.close();
} catch (Throwable t) {
// if we got this far, this is probably not a JAR loader
// so skip it
}
}
} catch (Throwable t) {
// probably not a SUN VM
}
}
void add(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(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();
}
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) {
trace("replacing %s with %s", 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.add(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) {
trace("begin %s", 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) {
trace("end");
current.set(previous);
}
public static Executor getExecutor() {
return executor;
}
public static ScheduledExecutorService getScheduledExecutor() {
return sheduledExecutor;
}
/**
* 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);
if (plugins != null)
plugins.add(plugin);
}
public synchronized void removeBasicPlugin(Object plugin) {
basicPlugins.remove(plugin);
if (plugins != null)
plugins.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);
}
@Override
public Iterator iterator() {
Set keys = keySet();
final Iterator it = keys.iterator();
return new Iterator() {
String current;
public boolean hasNext() {
return it.hasNext();
}
public String next() {
return current = it.next().toString();
}
public void remove() {
getProperties().remove(current);
}
};
}
public Set keySet() {
Set set;
if (parent == null)
set = Create.set();
else
set = parent.keySet();
for (Object o : properties.keySet())
set.add(o.toString());
return set;
}
/**
* 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;
}
public SetLocation file(String file) {
this.file = file;
return this;
}
public SetLocation header(String header) {
this.header = header;
return this;
}
public SetLocation context(String context) {
this.context = context;
return this;
}
public SetLocation method(String methodName) {
this.methodName = methodName;
return this;
}
public SetLocation line(int n) {
this.line = n;
return this;
}
public SetLocation reference(String reference) {
this.reference = reference;
return this;
}
public SetLocation details(Object details) {
this.details = details;
return this;
}
public Location location() {
return this;
}
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;
}
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?
List result = getIncluded();
if (result != null) {
ExtList reversed = new ExtList(result);
Collections.reverse(reversed);
for (File included : reversed) {
fl = findHeader(included, 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", properties.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 + "|" + key + ".*";
}
/**
* Get a Parameters from merged properties
*/
public Parameters getMergedParameters(String key) {
return new Parameters(mergeProperties(key));
}
/**
* 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);
Jar jar = new Jar(fileName(url.getPath()));
addClose(jar);
URLConnection connection = url.openConnection();
InputStream in = connection.getInputStream();
long lastModified = connection.getLastModified();
if (lastModified == 0)
// We assume the worst :-(
lastModified = System.currentTimeMillis();
EmbeddedResource.build(jar, in, lastModified);
in.close();
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 propertiesFile.getAbsolutePath().replaceAll("\\\\", "/");
}
/**
* 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);
}
}
}
}