blob: e30ddcd7a1be1e7b9f9152af2085cbdaab4aee60 [file] [log] [blame]
package aQute.bnd.osgi;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.jar.*;
import java.util.regex.*;
import aQute.bnd.header.*;
import aQute.bnd.service.*;
import aQute.lib.collections.*;
import aQute.lib.io.*;
import aQute.libg.generics.*;
import aQute.service.reporter.*;
public class Processor extends Domain implements Reporter, Registry, Constants, Closeable {
static ThreadLocal<Processor> current = new ThreadLocal<Processor>();
static ExecutorService executor = Executors.newCachedThreadPool();
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<String> errors = new ArrayList<String>();
final List<String> warnings = new ArrayList<String>();
final Set<Object> basicPlugins = new HashSet<Object>();
private final Set<Closeable> toBeClosed = new HashSet<Closeable>();
Set<Object> plugins;
boolean pedantic;
boolean trace;
boolean exceptions;
boolean fileMustExist = true;
private File base = new File("").getAbsoluteFile();
Properties properties;
String profile;
private Macro replacer;
private long lastModified;
private File propertiesFile;
private boolean fixup = true;
long modified;
Processor parent;
List<File> included;
CL pluginLoader;
Collection<String> filter;
HashSet<String> missingCommand;
public Processor() {
properties = new Properties();
}
public Processor(Properties parent) {
properties = new Properties(parent);
}
public Processor(Processor child) {
this(child.properties);
this.parent = child;
}
public void setParent(Processor processor) {
this.parent = processor;
Properties ext = new Properties(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 (isFailOk())
addAll(warnings, processor.getErrors(), prefix);
else
addAll(errors, processor.getErrors(), prefix);
addAll(warnings, processor.getWarnings(), prefix);
processor.getErrors().clear();
processor.getWarnings().clear();
}
public void getInfo(Reporter processor) {
getInfo(processor, "");
}
private <T> void addAll(List<String> to, List< ? extends T> from, String prefix) {
for (T x : from) {
to.add(prefix + x);
}
}
/**
* A processor can mark itself current for a thread.
*
* @return
*/
private Processor current() {
Processor p = current.get();
if (p == null)
return this;
return p;
}
public SetLocation warning(String string, Object... args) {
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) {
Processor p = current();
try {
if (p.isFailOk())
return p.warning(string, args);
String s = formatArrays(string, args == null ? new Object[0] : 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) {
format = String.format("[%2d] %s", (int) progress, format);
trace(format, args);
}
public void progress(String format, Object... args) {
progress(-1f, format, args);
}
public SetLocation exception(Throwable t, String format, Object... args) {
return error(format, t, args);
}
public SetLocation error(String string, Throwable t, Object... args) {
Processor p = current();
try {
if (p.exceptions)
t.printStackTrace();
if (p.isFailOk()) {
return p.warning(string + ": " + t, args);
}
p.errors.add("Exception: " + t.getMessage());
String s = formatArrays(string, args == null ? new Object[0] : args);
if (!p.errors.contains(s))
p.errors.add(s);
return location(s);
}
finally {
p.signal();
}
}
public void signal() {}
public List<String> getWarnings() {
return warnings;
}
public List<String> getErrors() {
return errors;
}
/**
* Standard OSGi header parser.
*
* @param value
* @return
*/
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());
setBase(reporter.getBase());
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 <T> List<T> getPlugins(Class<T> clazz) {
List<T> l = new ArrayList<T>();
Set<Object> 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 <T>
* @param clazz
* @return
*/
public <T> T getPlugin(Class<T> clazz) {
Set<Object> 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.
*
* @return
*/
protected synchronized Set<Object> getPlugins() {
if (this.plugins != null)
return this.plugins;
missingCommand = new HashSet<String>();
Set<Object> list = new LinkedHashSet<Object>();
// The owner of the plugin is always in there.
list.add(this);
setTypeSpecificPlugins(list);
if (parent != null)
list.addAll(parent.getPlugins());
// 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.
if (properties.containsKey(PLUGIN)) {
String spe = getProperty(PLUGIN);
if (spe.equals(NONE))
return new LinkedHashSet<Object>();
String pluginPath = getProperty(PLUGINPATH);
loadPlugins(list, spe, pluginPath);
}
return this.plugins = list;
}
/**
* @param list
* @param spe
*/
protected void loadPlugins(Set<Object> list, String spe, String pluginPath) {
Parameters plugins = new Parameters(spe);
CL loader = getLoader();
// First add the plugin-specific paths from their path: directives
for (Entry<String,Attrs> 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);
}
}
}
// Next add -pluginpath entries
if (pluginPath != null && pluginPath.length() > 0) {
StringTokenizer tokenizer = new StringTokenizer(pluginPath, ",");
while (tokenizer.hasMoreTokens()) {
String path = tokenizer.nextToken().trim();
try {
File f = getFile(path).getAbsoluteFile();
loader.add(f.toURI().toURL());
}
catch (Exception e) {
error("Problem adding path %s from global plugin path. Exception: %s", path, e);
}
}
}
// Load the plugins
for (Entry<String,Attrs> entry : plugins.entrySet()) {
String key = entry.getKey();
try {
trace("Using plugin %s", key);
// Plugins could use the same class with different
// parameters so we could have duplicate names Remove
// the ! added by the parser to make each name unique.
key = removeDuplicateMarker(key);
try {
Class< ? > c = loader.loadClass(key);
Object plugin = c.newInstance();
customize(plugin, entry.getValue());
if (plugin instanceof Closeable) {
addClose((Closeable) plugin);
}
list.add(plugin);
}
catch (Throwable t) {
// 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 = entry.getValue().get(COMMAND_DIRECTIVE);
if (commands == null)
error("Problem loading the plugin: %s exception: (%s)", key, t);
else {
Collection<String> cs = split(commands);
missingCommand.addAll(cs);
}
}
}
catch (Throwable e) {
error("Problem loading the plugin: %s exception: (%s)", key, e);
}
}
}
protected void setTypeSpecificPlugins(Set<Object> list) {
list.add(executor);
list.add(random);
list.addAll(basicPlugins);
}
/**
* @param plugin
* @param entry
*/
protected <T> T customize(T plugin, Attrs map) {
if (plugin instanceof Plugin) {
if (map != null)
((Plugin) plugin).setProperties(map);
((Plugin) plugin).setReporter(this);
}
if (plugin instanceof RegistryPlugin) {
((RegistryPlugin) plugin).setRegistry(this);
}
return plugin;
}
@Override
public boolean isFailOk() {
String v = getProperty(Analyzer.FAIL_OK, null);
return v != null && v.equalsIgnoreCase("true");
}
public File getBase() {
return base;
}
public void setBase(File base) {
this.base = base;
}
public void clear() {
errors.clear();
warnings.clear();
}
public void trace(String msg, Object... parms) {
Processor p = current();
if (p.trace) {
System.err.printf("# " + msg + "%n", parms);
}
}
public <T> List<T> newList() {
return new ArrayList<T>();
}
public <T> Set<T> newSet() {
return new TreeSet<T>();
}
public static <K, V> Map<K,V> newMap() {
return new LinkedHashMap<K,V>();
}
public static <K, V> Map<K,V> newHashMap() {
return new LinkedHashMap<K,V>();
}
public <T> List<T> newList(Collection<T> t) {
return new ArrayList<T>(t);
}
public <T> Set<T> newSet(Collection<T> t) {
return new TreeSet<T>(t);
}
public <K, V> Map<K,V> newMap(Map<K,V> t) {
return new LinkedHashMap<K,V>(t);
}
public void close() {
for (Closeable c : toBeClosed) {
try {
c.close();
}
catch (IOException e) {
// Who cares?
}
}
toBeClosed.clear();
}
public String _basedir(@SuppressWarnings("unused")
String args[]) {
if (base == null)
throw new IllegalArgumentException("No base dir set");
return base.getAbsolutePath();
}
/**
* Property handling ...
*
* @return
*/
public Properties getProperties() {
if (fixup) {
fixup = false;
begin();
}
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: " + file);
}
} else {
if (!file.exists())
error("Properties file does not exist: " + file);
else
error("Properties file must a file, not a directory: " + 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);
}
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<File>();
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<String> 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() && fileMustExist) {
error("Included file " + file + (file.exists() ? " does not exist" : " is directory"));
} else
doIncludeFile(file, overwrite, p);
}
catch (Exception e) {
if (fileMustExist)
error("Error in processing included file: " + value, e);
}
}
}
}
/**
* @param file
* @param parent
* @param done
* @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 parent
* @param done
* @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 " + file);
} else {
addIncluded(file);
updateModified(file.lastModified(), file.toString());
InputStream in = new FileInputStream(file);
try {
Properties sub;
if (file.getName().toLowerCase().endsWith(".mf")) {
sub = getManifestAsProperties(in);
} else
sub = loadProperties(in, file.getAbsolutePath());
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);
}
}
}
finally {
IO.close(in);
}
}
}
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;
}
/**
*
*/
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: " + propertiesFile);
}
}
}
catch (IOException e) {
error("Could not load properties " + propertiesFile);
}
}
protected void begin() {
if (isTrue(getProperty(PEDANTIC)))
setPedantic(true);
}
public static boolean isTrue(String value) {
if (value == null)
return false;
return !"false".equalsIgnoreCase(value);
}
/**
* Get a property without preprocessing it with a proper default
*
* @param headerName
* @param deflt
* @return
*/
public String getUnprocessedProperty(String key, String deflt) {
return getProperties().getProperty(key, deflt);
}
/**
* Get a property with preprocessing it with a proper default
*
* @param headerName
* @param deflt
* @return
*/
public String getProperty(String key, String deflt) {
String value = null;
Instruction ins = new Instruction(key);
if (!ins.isLiteral()) {
// Handle a wildcard key, make sure they're sorted
// for consistency
SortedList<String> sortedList = SortedList.fromIterator(iterator());
StringBuilder sb = new StringBuilder();
String del = "";
for (String k : sortedList) {
if (ins.matches(k)) {
String v = getProperty(k, null);
if (v != null) {
sb.append(del);
del = ",";
sb.append(v);
}
}
}
if (sb.length() == 0)
return deflt;
return sb.toString();
}
Processor source = this;
// Use the key as is first, if found ok
if (filter != null && filter.contains(key)) {
value = (String) getProperties().get(key);
} else {
while (source != null) {
value = (String) source.getProperties().get(key);
if (value != null)
break;
source = source.getParent();
}
}
// Check if we found a value, if not, try to prefix
// it with a profile if found and search again. profiles
// are a simple name that is prefixed like [profile]. This
// allows different variables to be used in different profiles.
if (value == null && profile != null) {
String pkey = "[" + profile + "]" + key;
if (filter != null && filter.contains(key)) {
value = (String) getProperties().get(pkey);
} else {
while (source != null) {
value = (String) source.getProperties().get(pkey);
if (value != null)
break;
source = source.getParent();
}
}
}
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
* @return
* @throws IOException
*/
public Properties loadProperties(File file) throws IOException {
updateModified(file.lastModified(), "Properties file: " + file);
InputStream in = new FileInputStream(file);
try {
Properties p = loadProperties(in, file.getAbsolutePath());
return p;
}
finally {
in.close();
}
}
Properties loadProperties(InputStream in, String name) throws IOException {
int n = name.lastIndexOf('/');
if (n > 0)
name = name.substring(0, n);
if (name.length() == 0)
name = ".";
try {
Properties p = new Properties();
p.load(in);
return replaceAll(p, "\\$\\{\\.\\}", name);
}
catch (Exception e) {
error("Error during loading properties file: " + name + ", error:" + e);
return new Properties();
}
}
/**
* 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) {
Properties result = new Properties();
for (Iterator<Map.Entry<Object,Object>> i = p.entrySet().iterator(); i.hasNext();) {
Map.Entry<Object,Object> entry = i.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
value = value.replaceAll(pattern, 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 {
for (Entry< ? , ? > entry : map.entrySet()) {
Object key = entry.getKey();
// Skip directives we do not recognize
if (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
* @return
* @throws IOException
*/
public static boolean quote(Appendable sb, String value) throws IOException {
boolean clean = (value.length() >= 2 && value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"')
|| Verifier.TOKEN.matcher(value).matches();
if (!clean)
sb.append("\"");
sb.append(value);
if (!clean)
sb.append("\"");
return clean;
}
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.
*
* @return
*/
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.
*
* @return
*/
public Properties getFlattenedProperties() {
return getReplacer().getFlattenedProperties();
}
/**
* Return all inherited property keys
*
* @return
*/
public Set<String> getPropertyKeys(boolean inherit) {
Set<String> 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
* @return
* @throws IOException
*/
public static Properties getManifestAsProperties(InputStream in) throws IOException {
Properties p = new Properties();
Manifest manifest = new Manifest(in);
for (Iterator<Object> 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[1000];
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.
*
* @param args
* @return
*/
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 <T> String join(T list[]) {
return join(list, ",");
}
public static void split(String s, Collection<String> set) {
String elements[] = s.trim().split(LIST_SPLITTER);
for (String element : elements) {
if (element.length() > 0)
set.add(element);
}
}
public static Collection<String> split(String s) {
return split(s, LIST_SPLITTER);
}
public static Collection<String> 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<String> result = new ArrayList<String>();
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
* @return
*/
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;
}
static class CL extends URLClassLoader {
CL() {
super(new URL[0], Processor.class.getClassLoader());
}
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 NoClassDefFoundError {
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 NoClassDefFoundError(sb.toString());
}
}
}
private CL getLoader() {
if (pluginLoader == null) {
pluginLoader = new CL();
}
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);
}
public boolean check(String... pattern) throws IOException {
Set<String> missed = Create.set();
if (pattern != null) {
for (String p : pattern) {
boolean match = false;
Pattern pat = Pattern.compile(p);
for (Iterator<String> i = errors.iterator(); i.hasNext();) {
if (pat.matcher(i.next()).find()) {
i.remove();
match = true;
}
}
for (Iterator<String> 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<String> 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
* @return
*/
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.
*
* <pre>
* &quot;/&quot; + &quot;abc/def/&quot; becomes &quot;abc/def&quot;
*
* &#064;param prefix
* &#064;param suffix
* &#064;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
* @return
*/
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 <key>=<name> ", 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
* @return
*/
public static String formatArrays(String string, Object... parms) {
Object[] parms2 = parms;
Object[] output = new Object[parms.length];
for (int i = 0; i < parms.length; i++) {
output[i] = makePrintable(parms[i]);
}
return String.format(string, parms2);
}
/**
* 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 object;
if (object.getClass().isArray()) {
Object[] array = (Object[]) object;
Object[] output = new Object[array.length];
for (int i = 0; i < array.length; i++) {
output[i] = makePrintable(array[i]);
}
return Arrays.toString(output);
}
return object;
}
public static String append(String... strings) {
List<String> 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);
}
/**
* Set the current command thread. This must be balanced with the
* {@link #end(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;
}
/**
* 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<File> 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<String> iterator() {
Set<String> keys = keySet();
final Iterator<String> it = keys.iterator();
return new Iterator<String>() {
String current;
public boolean hasNext() {
return it.hasNext();
}
public String next() {
return current = it.next().toString();
}
public void remove() {
getProperties().remove(current);
}
};
}
public Set<String> keySet() {
Set<String> 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
* @return
*/
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
*
* @param s
* @return
*/
List<Location> locations = new ArrayList<Location>();
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;
}
}
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;
}
}