You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by mc...@apache.org on 2012/07/16 15:43:44 UTC
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...
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>
+ * "/" + "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 <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 ::= â&â 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 ::= â˜=â
+ * greater ::= â>=â
+ * less ::= â<=â
+ * present ::= attr â=*â
+ * substring ::= attr â=â initial any final
+ * inital ::= () | value
+ * any ::= â*â star-value
+ * star-value ::= () | value â*â star-value
+ * final ::= () | value
+ * value ::= <see text>
+ * </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;
+ }
+}