felix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mccu...@apache.org
Subject svn commit: r1362033 [8/10] - in /felix/trunk/bundleplugin/src: main/java/aQute/bnd/build/ main/java/aQute/bnd/build/model/ main/java/aQute/bnd/build/model/clauses/ main/java/aQute/bnd/build/model/conversions/ main/java/aQute/bnd/compatibility/ main/ja...
Date Mon, 16 Jul 2012 13:43:44 GMT
Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java?rev=1362033&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java Mon Jul 16 13:43:38 2012
@@ -0,0 +1,1666 @@
+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;
+	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);
+			else {
+				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);
+			}
+			else {
+				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());
+					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;
+	}
+
+	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);
+			}
+		}
+
+		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;
+
+		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();
+			}
+		}
+
+		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);
+		}
+
+		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()
+	 */
+
+	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;
+	}
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java?rev=1362033&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java Mon Jul 16 13:43:38 2012
@@ -0,0 +1,17 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+
+public interface Resource {
+	InputStream openInputStream() throws Exception;
+
+	void write(OutputStream out) throws Exception;
+
+	long lastModified();
+
+	void setExtra(String extra);
+
+	String getExtra();
+
+	long size() throws Exception;
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java?rev=1362033&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java Mon Jul 16 13:43:38 2012
@@ -0,0 +1,30 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+
+import aQute.lib.tag.*;
+
+public class TagResource extends WriteResource {
+	final Tag	tag;
+
+	public TagResource(Tag tag) {
+		this.tag = tag;
+	}
+
+	public void write(OutputStream out) throws UnsupportedEncodingException {
+		OutputStreamWriter ow = new OutputStreamWriter(out, "UTF-8");
+		PrintWriter pw = new PrintWriter(ow);
+		pw.println("<?xml version='1.1'?>");
+		try {
+			tag.print(0, pw);
+		}
+		finally {
+			pw.flush();
+		}
+	}
+
+	public long lastModified() {
+		return 0;
+	}
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java?rev=1362033&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java Mon Jul 16 13:43:38 2012
@@ -0,0 +1,84 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.net.*;
+
+import aQute.lib.io.*;
+
+public class URLResource implements Resource {
+	URL		url;
+	String	extra;
+	long	size	= -1;
+
+	public URLResource(URL url) {
+		this.url = url;
+	}
+
+	public InputStream openInputStream() throws IOException {
+		return url.openStream();
+	}
+
+	public String toString() {
+		return ":" + url.getPath() + ":";
+	}
+
+	public void write(OutputStream out) throws Exception {
+		IO.copy(this.openInputStream(), out);
+	}
+
+	public long lastModified() {
+		return -1;
+	}
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+
+	public long size() throws Exception {
+		if (size >= 0)
+			return size;
+
+		try {
+			if (url.getProtocol().equals("file:")) {
+				File file = new File(url.getPath());
+				if (file.isFile())
+					return size = file.length();
+			} else {
+				URLConnection con = url.openConnection();
+				if (con instanceof HttpURLConnection) {
+					HttpURLConnection http = (HttpURLConnection) con;
+					http.setRequestMethod("HEAD");
+					http.connect();
+					String l = http.getHeaderField("Content-Length");
+					if (l != null) {
+						return size = Long.parseLong(l);
+					}
+				}
+			}
+		}
+		catch (Exception e) {
+			// Forget this exception, we do it the hard way
+		}
+		InputStream in = openInputStream();
+		DataInputStream din = null;
+		try {
+			din = new DataInputStream(in);
+			long result = din.skipBytes(Integer.MAX_VALUE);
+			while (in.read() >= 0) {
+				result += din.skipBytes(Integer.MAX_VALUE);
+			}
+			size = result;
+		}
+		finally {
+			if (din != null) {
+				din.close();
+			}
+		}
+		return size;
+	}
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java?rev=1362033&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java Mon Jul 16 13:43:38 2012
@@ -0,0 +1,913 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.bnd.header.*;
+import aQute.bnd.osgi.Descriptors.PackageRef;
+import aQute.bnd.osgi.Descriptors.TypeRef;
+import aQute.lib.base64.*;
+import aQute.lib.io.*;
+import aQute.libg.cryptography.*;
+import aQute.libg.qtokens.*;
+
+public class Verifier extends Processor {
+
+	private final Jar		dot;
+	private final Manifest	manifest;
+	private final Domain	main;
+
+	private boolean			r3;
+	private boolean			usesRequire;
+
+	final static Pattern	EENAME	= Pattern.compile("CDC-1\\.0/Foundation-1\\.0" + "|CDC-1\\.1/Foundation-1\\.1"
+											+ "|OSGi/Minimum-1\\.[1-9]" + "|JRE-1\\.1" + "|J2SE-1\\.2" + "|J2SE-1\\.3"
+											+ "|J2SE-1\\.4" + "|J2SE-1\\.5" + "|JavaSE-1\\.6" + "|JavaSE-1\\.7"
+											+ "|PersonalJava-1\\.1" + "|PersonalJava-1\\.2"
+											+ "|CDC-1\\.0/PersonalBasis-1\\.0" + "|CDC-1\\.0/PersonalJava-1\\.0");
+
+	final static int		V1_1	= 45;
+	final static int		V1_2	= 46;
+	final static int		V1_3	= 47;
+	final static int		V1_4	= 48;
+	final static int		V1_5	= 49;
+	final static int		V1_6	= 50;
+	final static int		V1_7	= 51;
+	final static int		V1_8	= 52;
+
+	static class EE {
+		String	name;
+		int		target;
+
+		EE(String name, @SuppressWarnings("unused") int source, int target) {
+			this.name = name;
+			this.target = target;
+		}
+
+		public String toString() {
+			return name + "(" + target + ")";
+		}
+	}
+
+	final static EE[]			ees								= {
+			new EE("CDC-1.0/Foundation-1.0", V1_3, V1_1),
+			new EE("CDC-1.1/Foundation-1.1", V1_3, V1_2),
+			new EE("OSGi/Minimum-1.0", V1_3, V1_1),
+			new EE("OSGi/Minimum-1.1", V1_3, V1_2),
+			new EE("JRE-1.1", V1_1, V1_1), //
+			new EE("J2SE-1.2", V1_2, V1_1), //
+			new EE("J2SE-1.3", V1_3, V1_1), //
+			new EE("J2SE-1.4", V1_3, V1_2), //
+			new EE("J2SE-1.5", V1_5, V1_5), //
+			new EE("JavaSE-1.6", V1_6, V1_6), //
+			new EE("PersonalJava-1.1", V1_1, V1_1), //
+			new EE("JavaSE-1.7", V1_7, V1_7), //
+			new EE("PersonalJava-1.1", V1_1, V1_1), //
+			new EE("PersonalJava-1.2", V1_1, V1_1), new EE("CDC-1.0/PersonalBasis-1.0", V1_3, V1_1),
+			new EE("CDC-1.0/PersonalJava-1.0", V1_3, V1_1), new EE("CDC-1.1/PersonalBasis-1.1", V1_3, V1_2),
+			new EE("CDC-1.1/PersonalJava-1.1", V1_3, V1_2)
+																};
+
+	final static Pattern		CARDINALITY_PATTERN				= Pattern.compile("single|multiple");
+	final static Pattern		RESOLUTION_PATTERN				= Pattern.compile("optional|mandatory");
+	final static Pattern		BUNDLEMANIFESTVERSION			= Pattern.compile("2");
+	public final static String	SYMBOLICNAME_STRING				= "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
+	public final static Pattern	SYMBOLICNAME					= Pattern.compile(SYMBOLICNAME_STRING);
+
+	public final static String	VERSION_STRING					= "[0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[0-9A-Za-z_-]+)?)?)?";
+	public final static Pattern	VERSION							= Pattern.compile(VERSION_STRING);
+	final static Pattern		FILTEROP						= Pattern.compile("=|<=|>=|~=");
+	public final static Pattern	VERSIONRANGE					= Pattern.compile("((\\(|\\[)"
+
+																+ VERSION_STRING + "," + VERSION_STRING + "(\\]|\\)))|"
+																		+ VERSION_STRING);
+	final static Pattern		FILE							= Pattern
+																		.compile("/?[^/\"\n\r\u0000]+(/[^/\"\n\r\u0000]+)*");
+	final static Pattern		WILDCARDPACKAGE					= Pattern
+																		.compile("((\\p{Alnum}|_)+(\\.(\\p{Alnum}|_)+)*(\\.\\*)?)|\\*");
+	public final static Pattern	ISO639							= Pattern.compile("[A-Z][A-Z]");
+	public final static Pattern	HEADER_PATTERN					= Pattern.compile("[A-Za-z0-9][-a-zA-Z0-9_]+");
+	public final static Pattern	TOKEN							= Pattern.compile("[-a-zA-Z0-9_]+");
+
+	public final static Pattern	NUMBERPATTERN					= Pattern.compile("\\d+");
+	public final static Pattern	PACKAGEPATTERN					= Pattern
+																		.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*");
+	public final static Pattern	PATHPATTERN						= Pattern.compile(".*");
+	public final static Pattern	FQNPATTERN						= Pattern.compile(".*");
+	public final static Pattern	URLPATTERN						= Pattern.compile(".*");
+	public final static Pattern	ANYPATTERN						= Pattern.compile(".*");
+	public final static Pattern	FILTERPATTERN					= Pattern.compile(".*");
+	public final static Pattern	TRUEORFALSEPATTERN				= Pattern.compile("true|false|TRUE|FALSE");
+	public static final Pattern	WILDCARDNAMEPATTERN				= Pattern.compile(".*");
+	public static final Pattern	BUNDLE_ACTIVATIONPOLICYPATTERN	= Pattern.compile("lazy");
+
+	public final static String	EES[]							= {
+			"CDC-1.0/Foundation-1.0", "CDC-1.1/Foundation-1.1", "OSGi/Minimum-1.0", "OSGi/Minimum-1.1",
+			"OSGi/Minimum-1.2", "JRE-1.1", "J2SE-1.2", "J2SE-1.3", "J2SE-1.4", "J2SE-1.5", "JavaSE-1.6", "JavaSE-1.7",
+			"PersonalJava-1.1", "PersonalJava-1.2", "CDC-1.0/PersonalBasis-1.0", "CDC-1.0/PersonalJava-1.0"
+																};
+
+	public final static String	OSNAMES[]						= {
+			"AIX", // IBM
+			"DigitalUnix", // Compaq
+			"Embos", // Segger Embedded Software Solutions
+			"Epoc32", // SymbianOS Symbian OS
+			"FreeBSD", // Free BSD
+			"HPUX", // hp-ux Hewlett Packard
+			"IRIX", // Silicon Graphics
+			"Linux", // Open source
+			"MacOS", // Apple
+			"NetBSD", // Open source
+			"Netware", // Novell
+			"OpenBSD", // Open source
+			"OS2", // OS/2 IBM
+			"QNX", // procnto QNX
+			"Solaris", // Sun (almost an alias of SunOS)
+			"SunOS", // Sun Microsystems
+			"VxWorks", // WindRiver Systems
+			"Windows95", "Win32", "Windows98", "WindowsNT", "WindowsCE", "Windows2000", // Win2000
+			"Windows2003", // Win2003
+			"WindowsXP", "WindowsVista",
+																};
+
+	public final static String	PROCESSORNAMES[]				= { //
+			//
+			"68k", // Motorola 68000
+			"ARM_LE", // Intel Strong ARM. Deprecated because it does not
+			// specify the endianness. See the following two rows.
+			"arm_le", // Intel Strong ARM Little Endian mode
+			"arm_be", // Intel String ARM Big Endian mode
+			"Alpha", //
+			"ia64n",// Hewlett Packard 32 bit
+			"ia64w",// Hewlett Packard 64 bit mode
+			"Ignite", // psc1k PTSC
+			"Mips", // SGI
+			"PArisc", // Hewlett Packard
+			"PowerPC", // power ppc Motorola/IBM Power PC
+			"Sh4", // Hitachi
+			"Sparc", // SUN
+			"Sparcv9", // SUN
+			"S390", // IBM Mainframe 31 bit
+			"S390x", // IBM Mainframe 64-bit
+			"V850E", // NEC V850E
+			"x86", // pentium i386
+			"i486", // i586 i686 Intel& AMD 32 bit
+			"x86-64",
+																};
+
+	final Analyzer				analyzer;
+	private Instructions		dynamicImports;
+
+	public Verifier(Jar jar) throws Exception {
+		this.analyzer = new Analyzer(this);
+		this.analyzer.use(this);
+		addClose(analyzer);
+		this.analyzer.setJar(jar);
+		this.manifest = this.analyzer.calcManifest();
+		this.main = Domain.domain(manifest);
+		this.dot = jar;
+		getInfo(analyzer);
+	}
+
+	public Verifier(Analyzer analyzer) throws Exception {
+		this.analyzer = analyzer;
+		this.dot = analyzer.getJar();
+		this.manifest = dot.getManifest();
+		this.main = Domain.domain(manifest);
+	}
+
+	private void verifyHeaders() {
+		for (String h : main) {
+			if (!HEADER_PATTERN.matcher(h).matches())
+				error("Invalid Manifest header: " + h + ", pattern=" + HEADER_PATTERN);
+		}
+	}
+
+	/*
+	 * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
+	 * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
+	 * optional ::= ’*’
+	 */
+	public void verifyNative() {
+		String nc = get("Bundle-NativeCode");
+		doNative(nc);
+	}
+
+	public void doNative(String nc) {
+		if (nc != null) {
+			QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
+			char del;
+			do {
+				do {
+					String name = qt.nextToken();
+					if (name == null) {
+						error("Can not parse name from bundle native code header: " + nc);
+						return;
+					}
+					del = qt.getSeparator();
+					if (del == ';') {
+						if (dot != null && !dot.exists(name)) {
+							error("Native library not found in JAR: " + name);
+						}
+					} else {
+						String value = null;
+						if (del == '=')
+							value = qt.nextToken();
+
+						String key = name.toLowerCase();
+						if (key.equals("osname")) {
+							// ...
+						} else if (key.equals("osversion")) {
+							// verify version range
+							verify(value, VERSIONRANGE);
+						} else if (key.equals("language")) {
+							verify(value, ISO639);
+						} else if (key.equals("processor")) {
+							// verify(value, PROCESSORS);
+						} else if (key.equals("selection-filter")) {
+							// verify syntax filter
+							verifyFilter(value);
+						} else if (name.equals("*") && value == null) {
+							// Wildcard must be at end.
+							if (qt.nextToken() != null)
+								error("Bundle-Native code header may only END in wildcard: nc");
+						} else {
+							warning("Unknown attribute in native code: " + name + "=" + value);
+						}
+						del = qt.getSeparator();
+					}
+				} while (del == ';');
+			} while (del == ',');
+		}
+	}
+
+	public boolean verifyFilter(String value) {
+		String s = validateFilter(value);
+		if (s == null)
+			return true;
+
+		error(s);
+		return false;
+	}
+
+	public static String validateFilter(String value) {
+		try {
+			verifyFilter(value, 0);
+			return null;
+		}
+		catch (Exception e) {
+			return "Not a valid filter: " + value + e.getMessage();
+		}
+	}
+
+	private void verifyActivator() throws Exception {
+		String bactivator = main.get("Bundle-Activator");
+		if (bactivator != null) {
+			TypeRef ref = analyzer.getTypeRefFromFQN(bactivator);
+			if (analyzer.getClassspace().containsKey(ref))
+				return;
+
+			PackageRef packageRef = ref.getPackageRef();
+			if (packageRef.isDefaultPackage())
+				error("The Bundle Activator is not in the bundle and it is in the default package ");
+			else if (!analyzer.isImported(packageRef)) {
+				error("Bundle-Activator not found on the bundle class path nor in imports: " + bactivator);
+			}
+		}
+	}
+
+	private void verifyComponent() {
+		String serviceComponent = main.get("Service-Component");
+		if (serviceComponent != null) {
+			Parameters map = parseHeader(serviceComponent);
+			for (String component : map.keySet()) {
+				if (component.indexOf("*") < 0 && !dot.exists(component)) {
+					error("Service-Component entry can not be located in JAR: " + component);
+				} else {
+					// validate component ...
+				}
+			}
+		}
+	}
+
+	/**
+	 * Check for unresolved imports. These are referrals that are not imported
+	 * by the manifest and that are not part of our bundle class path. The are
+	 * calculated by removing all the imported packages and contained from the
+	 * referred packages.
+	 */
+	private void verifyUnresolvedReferences() {
+		Set<PackageRef> unresolvedReferences = new TreeSet<PackageRef>(analyzer.getReferred().keySet());
+		unresolvedReferences.removeAll(analyzer.getImports().keySet());
+		unresolvedReferences.removeAll(analyzer.getContained().keySet());
+
+		// Remove any java.** packages.
+		for (Iterator<PackageRef> p = unresolvedReferences.iterator(); p.hasNext();) {
+			PackageRef pack = p.next();
+			if (pack.isJava())
+				p.remove();
+			else {
+				// Remove any dynamic imports
+				if (isDynamicImport(pack))
+					p.remove();
+			}
+		}
+
+		if (!unresolvedReferences.isEmpty()) {
+			// Now we want to know the
+			// classes that are the culprits
+			Set<String> culprits = new HashSet<String>();
+			for (Clazz clazz : analyzer.getClassspace().values()) {
+				if (hasOverlap(unresolvedReferences, clazz.getReferred()))
+					culprits.add(clazz.getAbsolutePath());
+			}
+
+			error("Unresolved references to %s by class(es) %s on the Bundle-Classpath: %s", unresolvedReferences,
+					culprits, analyzer.getBundleClasspath().keySet());
+		}
+	}
+
+	/**
+	 * @param p
+	 * @param pack
+	 */
+	private boolean isDynamicImport(PackageRef pack) {
+		if (dynamicImports == null)
+			dynamicImports = new Instructions(main.getDynamicImportPackage());
+
+		return dynamicImports.matches(pack.getFQN());
+	}
+
+	private boolean hasOverlap(Set< ? > a, Set< ? > b) {
+		for (Iterator< ? > i = a.iterator(); i.hasNext();) {
+			if (b.contains(i.next()))
+				return true;
+		}
+		return false;
+	}
+
+	public void verify() throws Exception {
+		verifyHeaders();
+		verifyDirectives("Export-Package", "uses:|mandatory:|include:|exclude:|" + IMPORT_DIRECTIVE, PACKAGEPATTERN,
+				"package");
+		verifyDirectives("Import-Package", "resolution:", PACKAGEPATTERN, "package");
+		verifyDirectives("Require-Bundle", "visibility:|resolution:", SYMBOLICNAME, "bsn");
+		verifyDirectives("Fragment-Host", "extension:", SYMBOLICNAME, "bsn");
+		verifyDirectives("Provide-Capability", "effective:|uses:", null, null);
+		verifyDirectives("Require-Capability", "effective:|resolution:|filter:", null, null);
+		verifyDirectives("Bundle-SymbolicName", "singleton:|fragment-attachment:|mandatory:", SYMBOLICNAME, "bsn");
+
+		verifyManifestFirst();
+		verifyActivator();
+		verifyActivationPolicy();
+		verifyComponent();
+		verifyNative();
+		verifyUnresolvedReferences();
+		verifySymbolicName();
+		verifyListHeader("Bundle-RequiredExecutionEnvironment", EENAME, false);
+		verifyHeader("Bundle-ManifestVersion", BUNDLEMANIFESTVERSION, false);
+		verifyHeader("Bundle-Version", VERSION, true);
+		verifyListHeader("Bundle-Classpath", FILE, false);
+		verifyDynamicImportPackage();
+		verifyBundleClasspath();
+		verifyUses();
+		if (usesRequire) {
+			if (!getErrors().isEmpty()) {
+				getWarnings()
+						.add(0,
+								"Bundle uses Require Bundle, this can generate false errors because then not enough information is available without the required bundles");
+			}
+		}
+
+		verifyRequirements();
+		verifyCapabilities();
+	}
+
+	private void verifyRequirements() {
+		Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.REQUIRE_CAPABILITY));
+		for (String key : map.keySet()) {
+			Attrs attrs = map.get(key);
+			verify(attrs, "filter:", FILTERPATTERN, false, "Requirement %s filter not correct", key);
+			verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
+			verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
+
+			if (key.equals("osgi.extender")) {
+				// No requirements on extender
+			} else if (key.equals("osgi.serviceloader")) {
+				verify(attrs, "register:", PACKAGEPATTERN, false,
+						"Service Loader extender register: directive not a fully qualified Java name");
+			} else if (key.equals("osgi.contract")) {
+
+			} else if (key.equals("osgi.service")) {
+
+			} else if (key.equals("osgi.ee")) {
+
+			} else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
+				error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
+			}
+
+			verifyAttrs(attrs);
+
+			if (attrs.containsKey("mandatory:"))
+				error("mandatory: directive is intended for Capabilities, not Requirement %s", key);
+
+			if (attrs.containsKey("uses:"))
+				error("uses: directive is intended for Capabilities, not Requirement %s", key);
+		}
+	}
+
+	/**
+	 * @param attrs
+	 */
+	void verifyAttrs(Attrs attrs) {
+		for (String a : attrs.keySet()) {
+			String v = attrs.get(a);
+
+			if (!a.endsWith(":")) {
+				Attrs.Type t = attrs.getType(a);
+				if ("version".equals(a)) {
+					if (t != Attrs.Type.VERSION)
+						error("Version attributes should always be of type version, it is %s", t);
+				} else
+					verifyType(t, v);
+			}
+		}
+	}
+
+	private void verifyCapabilities() {
+		Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.PROVIDE_CAPABILITY));
+		for (String key : map.keySet()) {
+			Attrs attrs = map.get(key);
+			verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
+			verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
+
+			if (key.equals("osgi.extender")) {
+				verify(attrs, "osgi.extender", SYMBOLICNAME, true,
+						"Extender %s must always have the osgi.extender attribute set", key);
+				verify(attrs, "version", VERSION, true, "Extender %s must always have a version", key);
+			} else if (key.equals("osgi.serviceloader")) {
+				verify(attrs, "register:", PACKAGEPATTERN, false,
+						"Service Loader extender register: directive not a fully qualified Java name");
+			} else if (key.equals("osgi.contract")) {
+				verify(attrs, "osgi.contract", SYMBOLICNAME, true,
+						"Contracts %s must always have the osgi.contract attribute set", key);
+
+			} else if (key.equals("osgi.service")) {
+				verify(attrs, "objectClass", PACKAGEPATTERN, true,
+						"osgi.service %s must have the objectClass attribute set", key);
+
+			} else if (key.equals("osgi.ee")) {
+				// TODO
+			} else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
+				error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
+			}
+
+			verifyAttrs(attrs);
+
+			if (attrs.containsKey("filter:"))
+				error("filter: directive is intended for Requirements, not Capability %s", key);
+			if (attrs.containsKey("cardinality:"))
+				error("cardinality: directive is intended for Requirements, not Capability %s", key);
+			if (attrs.containsKey("resolution:"))
+				error("resolution: directive is intended for Requirements, not Capability %s", key);
+		}
+	}
+
+	private void verify(Attrs attrs, String ad, Pattern pattern, boolean mandatory, String msg, String... args) {
+		String v = attrs.get(ad);
+		if (v == null) {
+			if (mandatory)
+				error("Missing required attribute/directive %s", ad);
+		} else {
+			Matcher m = pattern.matcher(v);
+			if (!m.matches())
+				error(msg, (Object[]) args);
+		}
+	}
+
+	private void verifyType(@SuppressWarnings("unused") Attrs.Type type, @SuppressWarnings("unused") String string) {
+
+	}
+
+	/**
+	 * Verify if the header does not contain any other directives
+	 * 
+	 * @param header
+	 * @param directives
+	 */
+	private void verifyDirectives(String header, String directives, Pattern namePattern, String type) {
+		Pattern pattern = Pattern.compile(directives);
+		Parameters map = parseHeader(manifest.getMainAttributes().getValue(header));
+		for (Entry<String,Attrs> entry : map.entrySet()) {
+			String pname = removeDuplicateMarker(entry.getKey());
+
+			if (namePattern != null) {
+				if (!namePattern.matcher(pname).matches())
+					if (isPedantic())
+						error("Invalid %s name: '%s'", type, pname);
+					else
+						warning("Invalid %s name: '%s'", type, pname);
+			}
+
+			for (String key : entry.getValue().keySet()) {
+				if (key.endsWith(":")) {
+					if (!key.startsWith("x-")) {
+						Matcher m = pattern.matcher(key);
+						if (m.matches())
+							continue;
+
+						warning("Unknown directive %s in %s, allowed directives are %s, and 'x-*'.", key, header,
+								directives.replace('|', ','));
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Verify the use clauses
+	 */
+	private void verifyUses() {
+		// Set<String> uses = Create.set();
+		// for ( Map<String,String> attrs : analyzer.getExports().values()) {
+		// if ( attrs.containsKey(Constants.USES_DIRECTIVE)) {
+		// String s = attrs.get(Constants.USES_DIRECTIVE);
+		// uses.addAll( split(s));
+		// }
+		// }
+		// uses.removeAll(analyzer.getExports().keySet());
+		// uses.removeAll(analyzer.getImports().keySet());
+		// if ( !uses.isEmpty())
+		// warning("Export-Package uses: directive contains packages that are not imported nor exported: %s",
+		// uses);
+	}
+
+	public boolean verifyActivationPolicy() {
+		String policy = main.get(Constants.BUNDLE_ACTIVATIONPOLICY);
+		if (policy == null)
+			return true;
+
+		return verifyActivationPolicy(policy);
+	}
+
+	public boolean verifyActivationPolicy(String policy) {
+		Parameters map = parseHeader(policy);
+		if (map.size() == 0)
+			warning("Bundle-ActivationPolicy is set but has no argument %s", policy);
+		else if (map.size() > 1)
+			warning("Bundle-ActivationPolicy has too many arguments %s", policy);
+		else {
+			Map<String,String> s = map.get("lazy");
+			if (s == null)
+				warning("Bundle-ActivationPolicy set but is not set to lazy: %s", policy);
+			else
+				return true;
+		}
+
+		return false;
+	}
+
+	public void verifyBundleClasspath() {
+		Parameters bcp = main.getBundleClassPath();
+		if (bcp.isEmpty() || bcp.containsKey("."))
+			return;
+
+		for (String path : bcp.keySet()) {
+			if (path.endsWith("/"))
+				error("A Bundle-ClassPath entry must not end with '/': %s", path);
+
+			if (dot.getDirectories().containsKey(path))
+				// We assume that any classes are in a directory
+				// and therefore do not care when the bundle is included
+				return;
+		}
+
+		for (String path : dot.getResources().keySet()) {
+			if (path.endsWith(".class")) {
+				warning("The Bundle-Classpath does not contain the actual bundle JAR (as specified with '.' in the Bundle-Classpath) but the JAR does contain classes. Is this intentional?");
+				return;
+			}
+		}
+	}
+
+	/**
+	 * <pre>
+	 *          DynamicImport-Package ::= dynamic-description
+	 *              ( ',' dynamic-description )*
+	 *              
+	 *          dynamic-description::= wildcard-names ( ';' parameter )*
+	 *          wildcard-names ::= wildcard-name ( ';' wildcard-name )*
+	 *          wildcard-name ::= package-name 
+	 *                         | ( package-name '.*' ) // See 1.4.2
+	 *                         | '*'
+	 * </pre>
+	 */
+	private void verifyDynamicImportPackage() {
+		verifyListHeader("DynamicImport-Package", WILDCARDPACKAGE, true);
+		String dynamicImportPackage = get("DynamicImport-Package");
+		if (dynamicImportPackage == null)
+			return;
+
+		Parameters map = main.getDynamicImportPackage();
+		for (String name : map.keySet()) {
+			name = name.trim();
+			if (!verify(name, WILDCARDPACKAGE))
+				error("DynamicImport-Package header contains an invalid package name: " + name);
+
+			Map<String,String> sub = map.get(name);
+			if (r3 && sub.size() != 0) {
+				error("DynamicPackage-Import has attributes on import: " + name
+						+ ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
+			}
+		}
+	}
+
+	private void verifyManifestFirst() {
+		if (!dot.isManifestFirst()) {
+			error("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
+		}
+	}
+
+	private void verifySymbolicName() {
+		Parameters bsn = parseHeader(main.get(Analyzer.BUNDLE_SYMBOLICNAME));
+		if (!bsn.isEmpty()) {
+			if (bsn.size() > 1)
+				error("More than one BSN specified " + bsn);
+
+			String name = bsn.keySet().iterator().next();
+			if (!isBsn(name)) {
+				error("Symbolic Name has invalid format: " + name);
+			}
+		}
+	}
+
+	/**
+	 * @param name
+	 * @return
+	 */
+	public static boolean isBsn(String name) {
+		return SYMBOLICNAME.matcher(name).matches();
+	}
+
+	/**
+	 * <pre>
+	 *         filter ::= ’(’ filter-comp ’)’
+	 *         filter-comp ::= and | or | not | operation
+	 *         and ::= ’&amp;’ filter-list
+	 *         or ::= ’|’ filter-list
+	 *         not ::= ’!’ filter
+	 *         filter-list ::= filter | filter filter-list
+	 *         operation ::= simple | present | substring
+	 *         simple ::= attr filter-type value
+	 *         filter-type ::= equal | approx | greater | less
+	 *         equal ::= ’=’
+	 *         approx ::= ’&tilde;=’
+	 *         greater ::= ’&gt;=’
+	 *         less ::= ’&lt;=’
+	 *         present ::= attr ’=*’
+	 *         substring ::= attr ’=’ initial any final
+	 *         inital ::= () | value
+	 *         any ::= ’*’ star-value
+	 *         star-value ::= () | value ’*’ star-value
+	 *         final ::= () | value
+	 *         value ::= &lt;see text&gt;
+	 * </pre>
+	 * 
+	 * @param expr
+	 * @param index
+	 * @return
+	 */
+
+	public static int verifyFilter(String expr, int index) {
+		try {
+			while (Character.isWhitespace(expr.charAt(index)))
+				index++;
+
+			if (expr.charAt(index) != '(')
+				throw new IllegalArgumentException("Filter mismatch: expected ( at position " + index + " : " + expr);
+
+			index++; // skip (
+
+			while (Character.isWhitespace(expr.charAt(index)))
+				index++;
+
+			switch (expr.charAt(index)) {
+				case '!' :
+					index++; // skip !
+					while (Character.isWhitespace(expr.charAt(index)))
+						index++;
+
+					if (expr.charAt(index) != '(')
+						throw new IllegalArgumentException("Filter mismatch: ! (not) must have one sub expression "
+								+ index + " : " + expr);
+					while (Character.isWhitespace(expr.charAt(index)))
+						index++;
+
+					index = verifyFilter(expr, index);
+					while (Character.isWhitespace(expr.charAt(index)))
+						index++;
+					if (expr.charAt(index) != ')')
+						throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
+								+ expr);
+					return index + 1;
+
+				case '&' :
+				case '|' :
+					index++; // skip operator
+					while (Character.isWhitespace(expr.charAt(index)))
+						index++;
+					while (expr.charAt(index) == '(') {
+						index = verifyFilter(expr, index);
+						while (Character.isWhitespace(expr.charAt(index)))
+							index++;
+					}
+
+					if (expr.charAt(index) != ')')
+						throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
+								+ expr);
+					return index + 1; // skip )
+
+				default :
+					index = verifyFilterOperation(expr, index);
+					if (expr.charAt(index) != ')')
+						throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
+								+ expr);
+					return index + 1;
+			}
+		}
+		catch (IndexOutOfBoundsException e) {
+			throw new IllegalArgumentException("Filter mismatch: early EOF from " + index);
+		}
+	}
+
+	static private int verifyFilterOperation(String expr, int index) {
+		StringBuilder sb = new StringBuilder();
+		while ("=><~()".indexOf(expr.charAt(index)) < 0) {
+			sb.append(expr.charAt(index++));
+		}
+		String attr = sb.toString().trim();
+		if (attr.length() == 0)
+			throw new IllegalArgumentException("Filter mismatch: attr at index " + index + " is 0");
+		sb = new StringBuilder();
+		while ("=><~".indexOf(expr.charAt(index)) >= 0) {
+			sb.append(expr.charAt(index++));
+		}
+		String operator = sb.toString();
+		if (!verify(operator, FILTEROP))
+			throw new IllegalArgumentException("Filter error, illegal operator " + operator + " at index " + index);
+
+		sb = new StringBuilder();
+		while (")".indexOf(expr.charAt(index)) < 0) {
+			switch (expr.charAt(index)) {
+				case '\\' :
+					if ("\\)(*".indexOf(expr.charAt(index + 1)) >= 0)
+						index++;
+					else
+						throw new IllegalArgumentException("Filter error, illegal use of backslash at index " + index
+								+ ". Backslash may only be used before * or () or \\");
+			}
+			sb.append(expr.charAt(index++));
+		}
+		return index;
+	}
+
+	private boolean verifyHeader(String name, Pattern regex, boolean error) {
+		String value = manifest.getMainAttributes().getValue(name);
+		if (value == null)
+			return false;
+
+		QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
+		for (Iterator<String> i = st.getTokenSet().iterator(); i.hasNext();) {
+			if (!verify(i.next(), regex)) {
+				String msg = "Invalid value for " + name + ", " + value + " does not match " + regex.pattern();
+				if (error)
+					error(msg);
+				else
+					warning(msg);
+			}
+		}
+		return true;
+	}
+
+	static private boolean verify(String value, Pattern regex) {
+		return regex.matcher(value).matches();
+	}
+
+	private boolean verifyListHeader(String name, Pattern regex, boolean error) {
+		String value = manifest.getMainAttributes().getValue(name);
+		if (value == null)
+			return false;
+
+		Parameters map = parseHeader(value);
+		for (String header : map.keySet()) {
+			if (!regex.matcher(header).matches()) {
+				String msg = "Invalid value for " + name + ", " + value + " does not match " + regex.pattern();
+				if (error)
+					error(msg);
+				else
+					warning(msg);
+			}
+		}
+		return true;
+	}
+
+	public String getProperty(String key, String deflt) {
+		if (properties == null)
+			return deflt;
+		return properties.getProperty(key, deflt);
+	}
+
+	public static boolean isVersion(String version) {
+		return VERSION.matcher(version).matches();
+	}
+
+	public static boolean isIdentifier(String value) {
+		if (value.length() < 1)
+			return false;
+
+		if (!Character.isJavaIdentifierStart(value.charAt(0)))
+			return false;
+
+		for (int i = 1; i < value.length(); i++) {
+			if (!Character.isJavaIdentifierPart(value.charAt(i)))
+				return false;
+		}
+		return true;
+	}
+
+	public static boolean isMember(String value, String[] matches) {
+		for (String match : matches) {
+			if (match.equals(value))
+				return true;
+		}
+		return false;
+	}
+
+	public static boolean isFQN(String name) {
+		if (name.length() == 0)
+			return false;
+		if (!Character.isJavaIdentifierStart(name.charAt(0)))
+			return false;
+
+		for (int i = 1; i < name.length(); i++) {
+			char c = name.charAt(i);
+			if (Character.isJavaIdentifierPart(c) || c == '$' || c == '.')
+				continue;
+
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Verify checksums
+	 */
+	/**
+	 * Verify the checksums from the manifest against the real thing.
+	 * 
+	 * @param all
+	 *            if each resources must be digested
+	 * @return true if ok
+	 * @throws Exception
+	 */
+
+	public void verifyChecksums(boolean all) throws Exception {
+		Manifest m = dot.getManifest();
+		if (m == null || m.getEntries().isEmpty()) {
+			if (all)
+				error("Verify checksums with all but no digests");
+			return;
+		}
+
+		List<String> missingDigest = new ArrayList<String>();
+
+		for (String path : dot.getResources().keySet()) {
+			if (path.equals("META-INF/MANIFEST.MF"))
+				continue;
+
+			Attributes a = m.getAttributes(path);
+			String digest = a.getValue("SHA1-Digest");
+			if (digest == null) {
+				if (!path.matches(""))
+					missingDigest.add(path);
+			} else {
+				byte[] d = Base64.decodeBase64(digest);
+				SHA1 expected = new SHA1(d);
+				Digester<SHA1> digester = SHA1.getDigester();
+				InputStream in = dot.getResource(path).openInputStream();
+				IO.copy(in, digester);
+				digester.digest();
+				if (!expected.equals(digester.digest())) {
+					error("Checksum mismatch %s, expected %s, got %s", path, expected, digester.digest());
+				}
+			}
+		}
+		if (missingDigest.size() > 0) {
+			error("Entries in the manifest are missing digests: %s", missingDigest);
+		}
+	}
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java?rev=1362033&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java Mon Jul 16 13:43:38 2012
@@ -0,0 +1,164 @@
+package aQute.bnd.osgi;
+
+import java.util.regex.*;
+
+public class Version implements Comparable<Version> {
+	final int					major;
+	final int					minor;
+	final int					micro;
+	final String				qualifier;
+	public final static String	VERSION_STRING	= "(\\d+)(\\.(\\d+)(\\.(\\d+)(\\.([-_\\da-zA-Z]+))?)?)?";
+	public final static Pattern	VERSION			= Pattern.compile(VERSION_STRING);
+	public final static Version	LOWEST			= new Version();
+	public final static Version	HIGHEST			= new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+														"\uFFFF");
+
+	public static final Version	emptyVersion	= LOWEST;
+	public static final Version	ONE				= new Version(1, 0, 0);
+
+	public Version() {
+		this(0);
+	}
+
+	public Version(int major, int minor, int micro, String qualifier) {
+		this.major = major;
+		this.minor = minor;
+		this.micro = micro;
+		this.qualifier = qualifier;
+	}
+
+	public Version(int major, int minor, int micro) {
+		this(major, minor, micro, null);
+	}
+
+	public Version(int major, int minor) {
+		this(major, minor, 0, null);
+	}
+
+	public Version(int major) {
+		this(major, 0, 0, null);
+	}
+
+	public Version(String version) {
+		version = version.trim();
+		Matcher m = VERSION.matcher(version);
+		if (!m.matches())
+			throw new IllegalArgumentException("Invalid syntax for version: " + version);
+
+		major = Integer.parseInt(m.group(1));
+		if (m.group(3) != null)
+			minor = Integer.parseInt(m.group(3));
+		else
+			minor = 0;
+
+		if (m.group(5) != null)
+			micro = Integer.parseInt(m.group(5));
+		else
+			micro = 0;
+
+		qualifier = m.group(7);
+	}
+
+	public int getMajor() {
+		return major;
+	}
+
+	public int getMinor() {
+		return minor;
+	}
+
+	public int getMicro() {
+		return micro;
+	}
+
+	public String getQualifier() {
+		return qualifier;
+	}
+
+	public int compareTo(Version other) {
+		if (other == this)
+			return 0;
+
+		Version o = other;
+		if (major != o.major)
+			return major - o.major;
+
+		if (minor != o.minor)
+			return minor - o.minor;
+
+		if (micro != o.micro)
+			return micro - o.micro;
+
+		int c = 0;
+		if (qualifier != null)
+			c = 1;
+		if (o.qualifier != null)
+			c += 2;
+
+		switch (c) {
+			case 0 :
+				return 0;
+			case 1 :
+				return 1;
+			case 2 :
+				return -1;
+		}
+		return qualifier.compareTo(o.qualifier);
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(major);
+		sb.append(".");
+		sb.append(minor);
+		sb.append(".");
+		sb.append(micro);
+		if (qualifier != null) {
+			sb.append(".");
+			sb.append(qualifier);
+		}
+		return sb.toString();
+	}
+
+	public boolean equals(Object ot) {
+		if (!(ot instanceof Version))
+			return false;
+
+		return compareTo((Version) ot) == 0;
+	}
+
+	public int hashCode() {
+		return major * 97 ^ minor * 13 ^ micro + (qualifier == null ? 97 : qualifier.hashCode());
+	}
+
+	public int get(int i) {
+		switch (i) {
+			case 0 :
+				return major;
+			case 1 :
+				return minor;
+			case 2 :
+				return micro;
+			default :
+				throw new IllegalArgumentException("Version can only get 0 (major), 1 (minor), or 2 (micro)");
+		}
+	}
+
+	public static Version parseVersion(String version) {
+		if (version == null) {
+			return LOWEST;
+		}
+
+		version = version.trim();
+		if (version.length() == 0) {
+			return LOWEST;
+		}
+
+		return new Version(version);
+
+	}
+
+	public Version getWithoutQualifier() {
+		return new Version(major, minor, micro);
+	}
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java?rev=1362033&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java Mon Jul 16 13:43:38 2012
@@ -0,0 +1,93 @@
+package aQute.bnd.osgi;
+
+import java.util.*;
+import java.util.regex.*;
+
+public class VersionRange {
+	Version			high;
+	Version			low;
+	char			start	= '[';
+	char			end		= ']';
+
+	static Pattern	RANGE	= Pattern.compile("(\\(|\\[)\\s*(" + Version.VERSION_STRING + ")\\s*,\\s*("
+									+ Version.VERSION_STRING + ")\\s*(\\)|\\])");
+
+	public VersionRange(String string) {
+		string = string.trim();
+		Matcher m = RANGE.matcher(string);
+		if (m.matches()) {
+			start = m.group(1).charAt(0);
+			String v1 = m.group(2);
+			String v2 = m.group(10);
+			low = new Version(v1);
+			high = new Version(v2);
+			end = m.group(18).charAt(0);
+			if (low.compareTo(high) > 0)
+				throw new IllegalArgumentException("Low Range is higher than High Range: " + low + "-" + high);
+
+		} else
+			high = low = new Version(string);
+	}
+
+	public boolean isRange() {
+		return high != low;
+	}
+
+	public boolean includeLow() {
+		return start == '[';
+	}
+
+	public boolean includeHigh() {
+		return end == ']';
+	}
+
+	public String toString() {
+		if (high == low)
+			return high.toString();
+
+		StringBuilder sb = new StringBuilder();
+		sb.append(start);
+		sb.append(low);
+		sb.append(',');
+		sb.append(high);
+		sb.append(end);
+		return sb.toString();
+	}
+
+	public Version getLow() {
+		return low;
+	}
+
+	public Version getHigh() {
+		return high;
+	}
+
+	public boolean includes(Version v) {
+		if (!isRange()) {
+			return low.compareTo(v) <= 0;
+		}
+		if (includeLow()) {
+			if (v.compareTo(low) < 0)
+				return false;
+		} else if (v.compareTo(low) <= 0)
+			return false;
+
+		if (includeHigh()) {
+			if (v.compareTo(high) > 0)
+				return false;
+		} else if (v.compareTo(high) >= 0)
+			return false;
+
+		return true;
+	}
+
+	public Iterable<Version> filter(final Iterable<Version> versions) {
+		List<Version> list = new ArrayList<Version>();
+		for (Version v : versions) {
+			if (includes(v))
+				list.add(v);
+		}
+		return list;
+	}
+
+}
\ No newline at end of file

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/WriteResource.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/WriteResource.java?rev=1362033&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/WriteResource.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/osgi/WriteResource.java Mon Jul 16 13:43:38 2012
@@ -0,0 +1,74 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+
+public abstract class WriteResource implements Resource {
+	String			extra;
+	volatile long	size	= -1;
+
+	public InputStream openInputStream() throws Exception {
+		PipedInputStream pin = new PipedInputStream();
+		final PipedOutputStream pout = new PipedOutputStream(pin);
+		Thread t = new Thread() {
+			public void run() {
+				try {
+					write(pout);
+					pout.flush();
+				}
+				catch (Exception e) {
+					e.printStackTrace();
+				}
+				finally {
+					try {
+						pout.close();
+					}
+					catch (IOException e) {
+						// Ignore
+					}
+				}
+			}
+		};
+		t.start();
+		return pin;
+	}
+
+	public abstract void write(OutputStream out) throws IOException, Exception;
+
+	public abstract long lastModified();
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+
+	static class CountingOutputStream extends OutputStream {
+		long	size;
+
+		@Override
+		public void write(int var0) throws IOException {
+			size++;
+		}
+
+		@Override
+		public void write(byte[] buffer) throws IOException {
+			size += buffer.length;
+		}
+
+		@Override
+		public void write(byte[] buffer, int start, int length) throws IOException {
+			size += length;
+		}
+	}
+
+	public long size() throws IOException, Exception {
+		if (size == -1) {
+			CountingOutputStream cout = new CountingOutputStream();
+			write(cout);
+			size = cout.size;
+		}
+		return size;
+	}
+}



Mime
View raw message