All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.bnd.osgi.Processor Maven / Gradle / Ivy

Go to download

The bndlib project is a general library to be used with OSGi bundles. It contains lots of cool functionality that calculates dependencies, etc.

There is a newer version: 2.4.0
Show newest version
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.bnd.version.*;
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	current			= new ThreadLocal();
	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				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;
	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						included;

	CL								pluginLoader;
	Collection				filter;
	HashSet					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  void addAll(List 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 getWarnings() {
		return warnings;
	}

	public List 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  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
	 * @return
	 */
	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.
	 * 
	 * @return
	 */
	protected synchronized Set getPlugins() {
		if (this.plugins != null)
			return this.plugins;

		missingCommand = new HashSet();
		Set list = new LinkedHashSet();

		// 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();

			String pluginPath = getProperty(PLUGINPATH);
			loadPlugins(list, spe, pluginPath);
		}

		return this.plugins = list;
	}

	/**
	 * @param list
	 * @param spe
	 */
	protected void loadPlugins(Set 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 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 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 cs = split(commands);
						missingCommand.addAll(cs);
					}
				}
			}
			catch (Throwable e) {
				error("Problem loading the plugin: %s exception: (%s)", key, e);
			}
		}
	}

	protected void setTypeSpecificPlugins(Set list) {
		list.add(executor);
		list.add(random);
		list.addAll(basicPlugins);
	}

	/**
	 * @param plugin
	 * @param entry
	 */
	protected  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  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() {
		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();
		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() && 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 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> 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;
	}

	/**
	 * 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 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
	 * @return
	 * @throws IOException
	 */
	public static Properties getManifestAsProperties(InputStream in) throws IOException {
		Properties p = new Properties();
		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[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  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
	 * @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 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
	 * @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.
	 * 
	 * 
	 *  "/" + "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
	 * @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 = ", 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 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<String>="x86-64,amd64,em64t,x86_64"
	 * 
* * @param args * Ignored; reserved for future use. */ public String _native_capability(String[] args) { StringBuilder builder = new StringBuilder().append("osgi.native"); try { // Operating System name and version String osnames; Version osversion; String sysPropOsName = System.getProperty("os.name"); String sysPropOsVersion = System.getProperty("os.version"); if (sysPropOsName.startsWith("Windows")) { if (sysPropOsVersion.startsWith("6.2")) { osversion = new Version(6,2,0); osnames = "Windows8,Windows 8,Win32"; } else if (sysPropOsVersion.startsWith("6.1")) { osversion = new Version(6, 1, 0); osnames = "Windows7,Windows 7,Win32"; } else if (sysPropOsName.startsWith("6.0")) { osversion = new Version(6, 0, 0); osnames = "WindowsVista,WinVista,Windows Vista,Win32"; } else if (sysPropOsName.startsWith("5.1")) { osversion = new Version(5, 1, 0); osnames = "WindowsXP,WinXP,Windows XP,Win32"; } else { throw new IllegalArgumentException(String.format("Unrecognised or unsupported Windows version while processing ${native} macro: %s version %s. Supported: XP, Vista, Win7, Win8.", sysPropOsName, sysPropOsVersion)); } } else if (sysPropOsName.startsWith("Mac OS X")) { osnames = "MacOSX,Mac OS X"; osversion = new Version(sysPropOsVersion); } else if (sysPropOsName.toLowerCase().startsWith("linux")) { osnames = "Linux"; osversion = new Version(sysPropOsVersion); } else if (sysPropOsName.startsWith("Solaris")) { osnames = "Solaris"; osversion = new Version(sysPropOsVersion); } else if (sysPropOsName.startsWith("AIX")) { osnames = "AIX"; osversion = new Version(sysPropOsVersion); } else if (sysPropOsName.startsWith("HP-UX")) { osnames = "HPUX,hp-ux"; osversion = new Version(sysPropOsVersion); } else { throw new IllegalArgumentException(String.format("Unrecognised or unsupported OS while processing ${native} macro: %s version %s. Supported: Windows, Mac OS X, Linux, Solaris, AIX, HP-UX.", sysPropOsName, sysPropOsVersion)); } builder.append(";osgi.native.osname:List=\"").append(osnames).append('"'); builder.append(";osgi.native.osversion:Version=").append(osversion.toString()); // Processor String processorNames; String arch = System.getProperty("os.arch"); if ("x86_64".equals(arch)) processorNames = "x86-64,amd64,em64t,x86_64"; else if ("x86".equals(arch)) processorNames = "x86,pentium,i386,i486,i586,i686"; else throw new IllegalArgumentException(String.format("Unrecognised/unsupported processor name '%s' in ${native} macro.", arch)); builder.append(";osgi.native.processor:List=\"").append(processorNames).append('"'); } catch (SecurityException e) { throw new IllegalArgumentException("Security error retrieving system properties while processing ${native} macro."); } return builder.toString(); } /** * 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 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 * @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 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; } } 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; } }