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 2009/07/13 12:06:50 UTC

svn commit: r793527 [5/7] - in /felix/trunk/bundleplugin: ./ src/main/java/aQute/ src/main/java/aQute/bnd/ src/main/java/aQute/bnd/build/ src/main/java/aQute/bnd/help/ src/main/java/aQute/bnd/make/ src/main/java/aQute/bnd/service/ src/main/java/aQute/b...

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java?rev=793527&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java Mon Jul 13 10:06:47 2009
@@ -0,0 +1,86 @@
+/* Copyright 2006 aQute SARL 
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.regex.Pattern;
+
+public class FileResource implements Resource {
+	File	file;
+	String	extra;
+	
+	public FileResource(File file) {
+		this.file = file;
+	}
+
+	public InputStream openInputStream() throws FileNotFoundException {
+		return new FileInputStream(file);
+	}
+
+	public static void build(Jar jar, File directory, Pattern doNotCopy) {
+		traverse(
+				jar,
+				directory.getAbsolutePath().length(),
+				directory,
+				doNotCopy);
+	}
+
+	public String toString() {
+		return ":" + file.getName() + ":";
+	}
+
+	public void write(OutputStream out) throws IOException {
+		copy(this, out);
+	}
+
+	static synchronized void copy(Resource resource, OutputStream out)
+			throws IOException {
+		InputStream in = resource.openInputStream();
+		try {
+			byte buffer[] = new byte[20000];
+			int size = in.read(buffer);
+			while (size > 0) {
+				out.write(buffer, 0, size);
+				size = in.read(buffer);
+			}
+		}
+		finally {
+			in.close();
+		}
+	}
+
+	static void traverse(Jar jar, int rootlength, File directory,
+			Pattern doNotCopy) {
+		if (doNotCopy.matcher(directory.getName()).matches())
+			return;
+
+		File files[] = directory.listFiles();
+		for (int i = 0; i < files.length; i++) {
+			if (files[i].isDirectory())
+				traverse(jar, rootlength, files[i], doNotCopy);
+			else {
+				String path = files[i].getAbsolutePath().substring(
+						rootlength + 1);
+				if (File.separatorChar != '/')
+					path = path.replace(File.separatorChar, '/');
+				jar.putResource(path, new FileResource(files[i]), true);
+			}
+		}
+	}
+
+	public long lastModified() {
+		return file.lastModified();
+	}
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+	
+	public long size() {
+	    return (int) file.length();
+	}
+}

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

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java?rev=793527&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java Mon Jul 13 10:06:47 2009
@@ -0,0 +1,113 @@
+/*
+ * $Header: /cvsroot/bnd/aQute.bnd/src/aQute/lib/osgi/Instruction.java,v 1.1 2009/01/19 14:08:30 pkriens Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aQute.lib.osgi;
+
+import java.util.regex.*;
+
+public class Instruction {
+    Pattern pattern;
+    String  instruction;
+    boolean negated;
+    boolean optional;
+    
+    public Instruction(String instruction, boolean negated) {
+        this.instruction = instruction;
+        this.negated = negated;
+    }
+
+    public boolean matches(String value) {
+        return getMatcher(value).matches();
+    }
+
+    public boolean isNegated() {
+        return negated;
+    }
+
+    public String getPattern() {
+        return instruction;
+    }
+
+    /**
+     * Convert a string based pattern to a regular expression based pattern.
+     * This is called an instruction, this object makes it easier to handle the
+     * different cases
+     * 
+     * @param string
+     * @return
+     */
+    public static Instruction getPattern(String string) {
+        boolean negated = false;
+        if (string.startsWith("!")) {
+            negated = true;
+            string = string.substring(1);
+        }
+        StringBuffer sb = new StringBuffer();
+        for (int c = 0; c < string.length(); c++) {
+            switch (string.charAt(c)) {
+            case '.':
+                sb.append("\\.");
+                break;
+            case '*':
+                sb.append(".*");
+                break;
+            case '?':
+                sb.append(".?");
+                break;
+            default:
+                sb.append(string.charAt(c));
+                break;
+            }
+        }
+        string = sb.toString();
+        if (string.endsWith("\\..*")) {
+            sb.append("|");
+            sb.append(string.substring(0, string.length() - 4));
+        }
+        return new Instruction(sb.toString(), negated);
+    }
+
+    public String toString() {
+        return getPattern();
+    }
+
+    public Matcher getMatcher(String value) {
+        if (pattern == null) {
+            pattern = Pattern.compile(instruction);
+        }
+        return pattern.matcher(value);
+    }
+
+    public int hashCode() {
+        return instruction.hashCode();
+    }
+
+    public boolean equals(Object other) {
+        return other != null && (other instanceof Instruction)
+                && instruction.equals(((Instruction) other).instruction);
+    }
+
+    public void setOptional() {
+        optional = true;
+    }
+    
+    public boolean isOptional() {
+        return optional;
+    }
+
+}

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

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/InstructionFilter.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/InstructionFilter.java?rev=793527&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/InstructionFilter.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/InstructionFilter.java Mon Jul 13 10:06:47 2009
@@ -0,0 +1,34 @@
+/* Copyright 2009 aQute SARL 
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.File;
+import java.io.FileFilter;
+
+public class InstructionFilter implements FileFilter {
+
+	private Instruction instruction;
+	private boolean recursive;
+	
+	public InstructionFilter (Instruction instruction, boolean recursive) {
+		this.instruction = instruction;
+		this.recursive = recursive;
+	}
+	public boolean isRecursive() {
+		return recursive;
+	}
+	public boolean accept(File pathname) {
+		if (Analyzer.doNotCopy.matcher(pathname.getName()).matches()) {
+			return false;
+		}
+
+		if (pathname.isDirectory() && isRecursive()) {
+			return true;
+		}
+		
+		if (instruction == null) {
+			return true;
+		}
+		return !instruction.isNegated() & instruction.matches(pathname.getName());
+	}
+}

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

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java?rev=793527&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java Mon Jul 13 10:06:47 2009
@@ -0,0 +1,429 @@
+/* Copyright 2006 aQute SARL 
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.libg.reporter.*;
+
+public class Jar implements Closeable {
+    public static final Object[]       EMPTY_ARRAY = new Jar[0];
+    Map<String, Resource>              resources   = new TreeMap<String, Resource>();
+    Map<String, Map<String, Resource>> directories = new TreeMap<String, Map<String, Resource>>();
+    Manifest                           manifest;
+    boolean                            manifestFirst;
+    String                             name;
+    File                               source;
+    ZipFile                            zipFile;
+    long                               lastModified;
+    String                             lastModifiedReason;
+    Reporter                           reporter;
+    boolean                            doNotTouchManifest;
+    boolean                            nomanifest;
+
+    public Jar(String name) {
+        this.name = name;
+    }
+
+    public Jar(String name, File dirOrFile) throws ZipException, IOException {
+        this(name);
+        source = dirOrFile;
+        if (dirOrFile.isDirectory())
+            FileResource.build(this, dirOrFile, Analyzer.doNotCopy);
+        else {
+            zipFile = ZipResource.build(this, dirOrFile);
+        }
+    }
+
+    public Jar(String name, InputStream in, long lastModified)
+            throws IOException {
+        this(name);
+        EmbeddedResource.build(this, in, lastModified);
+    }
+
+    public Jar(String name, String path) throws IOException {
+        this(name);
+        File f = new File(path);
+        InputStream in = new FileInputStream(f);
+        EmbeddedResource.build(this, in, f.lastModified());
+        in.close();
+    }
+
+    public Jar(File jar) throws IOException {
+        this(getName(jar), jar);
+    }
+
+    /**
+     * Make the JAR file name the project name if we get a src or bin directory.
+     * 
+     * @param f
+     * @return
+     */
+    private static String getName(File f) {
+        f = f.getAbsoluteFile();
+        String name = f.getName();
+        if (name.equals("bin") || name.equals("src"))
+            return f.getParentFile().getName();
+        else {
+            if (name.endsWith(".jar"))
+                name = name.substring(0, name.length() - 4);
+            return name;
+        }
+    }
+
+    public Jar(String string, InputStream resourceAsStream) throws IOException {
+        this(string, resourceAsStream, 0);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String toString() {
+        return "Jar:" + name;
+    }
+
+    public boolean putResource(String path, Resource resource) {
+        return putResource(path, resource, true);
+    }
+
+    public boolean putResource(String path, Resource resource, boolean overwrite) {
+        updateModified(resource.lastModified(), path);
+
+        if (path.equals("META-INF/MANIFEST.MF")) {
+            manifest = null;
+            if (resources.isEmpty())
+                manifestFirst = true;
+        }
+        String dir = getDirectory(path);
+        Map<String, Resource> s = directories.get(dir);
+        if (s == null) {
+            s = new TreeMap<String, Resource>();
+            directories.put(dir, s);
+            int n = dir.lastIndexOf('/');
+            while (n > 0) {
+                String dd = dir.substring(0, n);
+                if (directories.containsKey(dd))
+                    break;
+                directories.put(dd, null);
+                n = dd.lastIndexOf('/');
+            }
+        }
+        boolean duplicate = s.containsKey(path);
+        if (!duplicate || overwrite) {
+            resources.put(path, resource);
+            s.put(path, resource);
+        }
+        return duplicate;
+    }
+
+    public Resource getResource(String path) {
+        return resources.get(path);
+    }
+
+    private String getDirectory(String path) {
+        int n = path.lastIndexOf('/');
+        if (n < 0)
+            return "";
+
+        return path.substring(0, n);
+    }
+
+    public Map<String, Map<String, Resource>> getDirectories() {
+        return directories;
+    }
+
+    public Map<String, Resource> getResources() {
+        return resources;
+    }
+
+    public boolean addDirectory(Map<String, Resource> directory,
+            boolean overwrite) {
+        boolean duplicates = false;
+        if (directory == null)
+            return false;
+
+        for (Map.Entry<String, Resource> entry : directory.entrySet()) {
+            String key = entry.getKey();
+            if (!key.endsWith(".java")) {
+                duplicates |= putResource(key, (Resource) entry.getValue(),
+                        overwrite);
+            }
+        }
+        return duplicates;
+    }
+
+    public Manifest getManifest() throws IOException {
+        if (manifest == null) {
+            Resource manifestResource = getResource("META-INF/MANIFEST.MF");
+            if (manifestResource != null) {
+                InputStream in = manifestResource.openInputStream();
+                manifest = new Manifest(in);
+                in.close();
+            }
+        }
+        return manifest;
+    }
+
+    public boolean exists(String path) {
+        return resources.containsKey(path);
+    }
+
+    public void setManifest(Manifest manifest) {
+        manifestFirst = true;
+        this.manifest = manifest;
+    }
+
+    public void write(File file) throws Exception {
+        try {
+            OutputStream out = new FileOutputStream(file);
+            write(out);
+            out.close();
+            return;
+
+        } catch (Exception t) {
+            file.delete();
+            throw t;
+        }
+    }
+
+    public void write(String file) throws Exception {
+        write(new File(file));
+    }
+
+    public void write(OutputStream out) throws IOException {
+        ZipOutputStream jout = nomanifest ? new ZipOutputStream(out) : new JarOutputStream(out);
+        Set<String> done = new HashSet<String>();
+
+        Set<String> directories = new HashSet<String>();
+        if (doNotTouchManifest) {
+            writeResource(jout, directories, "META-INF/MANIFEST.MF",
+                    getResource("META-INF/MANIFEST.MF"));
+            done.add("META-INF/MANIFEST.MF");
+        } else if (!nomanifest)
+            doManifest(done, jout);
+
+        for (Map.Entry<String, Resource> entry : getResources().entrySet()) {
+            // Skip metainf contents
+            if (!done.contains(entry.getKey()))
+                writeResource(jout, directories, (String) entry.getKey(),
+                        (Resource) entry.getValue());
+        }
+        jout.finish();
+    }
+
+    private void doManifest(Set<String> done, ZipOutputStream jout)
+            throws IOException {
+        if ( nomanifest )
+            return;
+        
+        JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
+        jout.putNextEntry(ze);
+        writeManifest(jout);
+        jout.closeEntry();
+        done.add(ze.getName());
+    }
+
+    /**
+     * Cleanup the manifest for writing. Cleaning up consists of adding a space
+     * after any \n to prevent the manifest to see this newline as a delimiter.
+     * 
+     * @param out
+     *            Output
+     * @throws IOException
+     */
+
+    public void writeManifest(OutputStream out) throws IOException {
+        writeManifest(getManifest(), out);
+    }
+
+    public static void writeManifest(Manifest manifest, OutputStream out)
+            throws IOException {
+        
+        manifest = clean(manifest);
+        manifest.write(out);
+    }
+
+    private static Manifest clean(Manifest org) {
+
+        Manifest result = new Manifest();
+        for (Map.Entry<?, ?> entry : org.getMainAttributes().entrySet()) {
+            String nice = clean((String) entry.getValue());
+            result.getMainAttributes().put(entry.getKey(), nice);
+        }
+        for (String name : org.getEntries().keySet()) {
+            Attributes attrs = result.getAttributes(name);
+            if (attrs == null) {
+                attrs = new Attributes();
+                result.getEntries().put(name, attrs);
+            }
+
+            for (Map.Entry<?, ?> entry : org.getAttributes(name).entrySet()) {
+                String nice = clean((String) entry.getValue());
+                attrs.put((Attributes.Name) entry.getKey(), nice);
+            }
+        }
+        return result;
+    }
+
+    private static String clean(String s) {
+        if (s.indexOf('\n') < 0)
+            return s;
+
+        StringBuffer sb = new StringBuffer(s);
+        for (int i = 0; i < sb.length(); i++) {
+            if (sb.charAt(i) == '\n')
+                sb.insert(++i, ' ');
+        }
+        return sb.toString();
+    }
+
+    private void writeResource(ZipOutputStream jout, Set<String> directories,
+            String path, Resource resource) throws IOException {
+        if (resource == null)
+            return;
+
+        createDirectories(directories, jout, path);
+        ZipEntry ze = new ZipEntry(path);
+        ze.setMethod(ZipEntry.DEFLATED);
+        long lastModified = resource.lastModified();
+        if (lastModified == 0L) {
+            lastModified = System.currentTimeMillis();
+        }
+        ze.setTime(lastModified);
+        if (resource.getExtra() != null)
+            ze.setExtra(resource.getExtra().getBytes());
+        jout.putNextEntry(ze);
+        try {
+            resource.write(jout);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Cannot write resource: " + path
+                    + " " + e);
+        }
+        jout.closeEntry();
+    }
+
+    void createDirectories(Set<String> directories, ZipOutputStream zip,
+            String name) throws IOException {
+        int index = name.lastIndexOf('/');
+        if (index > 0) {
+            String path = name.substring(0, index);
+            if (directories.contains(path))
+                return;
+            createDirectories(directories, zip, path);
+            ZipEntry ze = new ZipEntry(path + '/');
+            zip.putNextEntry(ze);
+            zip.closeEntry();
+            directories.add(path);
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Add all the resources in the given jar that match the given filter.
+     * 
+     * @param sub
+     *            the jar
+     * @param filter
+     *            a pattern that should match the resoures in sub to be added
+     */
+    public boolean addAll(Jar sub, Pattern filter) {
+        boolean dupl = false;
+        for (String name : sub.getResources().keySet()) {
+            if ("META-INF/MANIFEST.MF".equals(name))
+                continue;
+
+            if (filter == null || filter.matcher(name).matches())
+                dupl |= putResource(name, sub.getResource(name), true);
+        }
+        return dupl;
+    }
+
+    public void close() {
+        if (zipFile != null)
+            try {
+                zipFile.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        resources = null;
+        directories = null;
+        manifest = null;
+        source = null;
+    }
+
+    public long lastModified() {
+        return lastModified;
+    }
+
+    public void updateModified(long time, String reason) {
+        if (time > lastModified) {
+            lastModified = time;
+            lastModifiedReason = reason;
+        }
+    }
+
+    public void setReporter(Reporter reporter) {
+        this.reporter = reporter;
+    }
+
+    public boolean hasDirectory(String path) {
+        return directories.get(path) != null;
+    }
+
+    public List<String> getPackages() {
+        List<String> list = new ArrayList<String>(directories.size());
+
+        for (Iterator<String> i = directories.keySet().iterator(); i.hasNext();) {
+            String path = i.next();
+            String pack = path.replace('/', '.');
+            list.add(pack);
+        }
+        return list;
+    }
+
+    public File getSource() {
+        return source;
+    }
+
+    public boolean addAll(Jar src) {
+        return addAll(src, null);
+    }
+
+    public boolean rename(String oldPath, String newPath) {
+        Resource resource = remove(oldPath);
+        if (resource == null)
+            return false;
+
+        return putResource(newPath, resource);
+    }
+
+    public Resource remove(String path) {
+        Resource resource = resources.remove(path);
+        String dir = getDirectory(path);
+        Map<String, Resource> mdir = directories.get(dir);
+        // must be != null
+        mdir.remove(path);
+        return resource;
+    }
+
+    /**
+     * Make sure nobody touches the manifest! If the bundle is signed, we do not
+     * want anybody to touch the manifest after the digests have been
+     * calculated.
+     */
+    public void setDoNotTouchManifest() {
+        doNotTouchManifest = true;
+    }
+
+    public void setNoManifest(boolean b) {
+        nomanifest = b;
+    }
+}

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

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java?rev=793527&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java Mon Jul 13 10:06:47 2009
@@ -0,0 +1,42 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public class JarResource implements Resource {
+	Jar		jar;
+	String extra;
+	
+	public JarResource(Jar jar ) {
+		this.jar = jar;
+	}
+	
+	public String getExtra() {
+		return extra;
+	}
+
+	public long lastModified() {
+		return jar.lastModified();
+	}
+
+
+	public void write(OutputStream out) throws IOException {
+		jar.write(out);
+	}
+	
+	public InputStream openInputStream() throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		write(out);
+		out.close();
+		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+		return in;
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+	
+	public Jar getJar() { 
+	    return jar;
+	}
+
+}

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

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java?rev=793527&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java Mon Jul 13 10:06:47 2009
@@ -0,0 +1,841 @@
+/* Copyright 2006 aQute SARL 
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.text.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.libg.sed.*;
+import aQute.libg.version.*;
+
+/**
+ * Provide a macro processor. This processor can replace variables in strings
+ * based on a properties and a domain. The domain can implement functions that
+ * start with a "_" and take args[], the names of these functions are available
+ * as functions in the macro processor (without the _). Macros can nest to any
+ * depth but may not contain loops.
+ * 
+ */
+public class Macro implements Replacer {
+    Properties properties;
+    Processor  domain;
+    Object     targets[];
+    boolean    flattening;
+
+    public Macro(Properties properties, Processor domain, Object... targets) {
+        this.properties = properties;
+        this.domain = domain;
+        this.targets = targets;
+        if (targets != null) {
+            for (Object o : targets) {
+                assert o != null;
+            }
+        }
+    }
+
+    public Macro(Processor processor) {
+        this(new Properties(), processor);
+    }
+
+    public String process(String line) {
+        return process(line, null);
+    }
+
+    String process(String line, Link link) {
+        StringBuffer sb = new StringBuffer();
+        process(line, 0, '\u0000', '\u0000', sb, link);
+        return sb.toString();
+    }
+
+    int process(CharSequence org, int index, char begin, char end,
+            StringBuffer result, Link link) {
+        StringBuilder line = new StringBuilder(org);
+        int nesting = 1;
+
+        StringBuffer variable = new StringBuffer();
+        outer: while (index < line.length()) {
+            char c1 = line.charAt(index++);
+            if (c1 == end) {
+                if (--nesting == 0) {
+                    result.append(replace(variable.toString(), link));
+                    return index;
+                }
+            } else if (c1 == begin)
+                nesting++;
+            else if (c1 == '\\' && index < line.length() - 1
+                    && line.charAt(index) == '$') {
+                // remove the escape backslash and interpret the dollar as a
+                // literal
+                index++;
+                variable.append('$');
+                continue outer;
+            } else if (c1 == '$' && index < line.length() - 2) {
+                char c2 = line.charAt(index);
+                char terminator = getTerminator(c2);
+                if (terminator != 0) {
+                    index = process(line, index + 1, c2, terminator, variable,
+                            link);
+                    continue outer;
+                }
+            }
+            variable.append(c1);
+        }
+        result.append(variable);
+        return index;
+    }
+
+    public static char getTerminator(char c) {
+        switch (c) {
+        case '(':
+            return ')';
+        case '[':
+            return ']';
+        case '{':
+            return '}';
+        case '<':
+            return '>';
+        case '\u00ab': // Guillemet double << >>
+            return '\u00bb';
+        case '\u2039': // Guillemet single
+            return '\u203a';
+        }
+        return 0;
+    }
+
+    protected String replace(String key, Link link) {
+        if (link != null && link.contains(key))
+            return "${infinite:" + link.toString() + "}";
+
+        if (key != null) {
+            key = key.trim();
+            if (key.length() > 0) {
+                String value = (String) properties.getProperty(key);
+                if (value != null)
+                    return process(value, new Link(link, key));
+
+                value = doCommands(key);
+                if (value != null)
+                    return process(value, new Link(link, key));
+
+                if (key != null && key.trim().length() > 0) {
+                    value = System.getProperty(key);
+                    if (value != null)
+                        return value;
+                }
+                if (!flattening)
+                    domain.warning("No translation found for macro: " + key);
+            } else {
+                domain.warning("Found empty macro key");
+            }
+        } else {
+            domain.warning("Found null macro key");
+        }
+        return "${" + key + "}";
+    }
+
+    /**
+     * Parse the key as a command. A command consist of parameters separated by
+     * ':'.
+     * 
+     * @param key
+     * @return
+     */
+    static Pattern commands = Pattern.compile("(?<!\\\\);");
+
+    private String doCommands(String key) {
+        String[] args = commands.split(key);
+        if (args == null || args.length == 0)
+            return null;
+
+        for (int i = 0; i < args.length; i++)
+            if (args[i].indexOf('\\') >= 0)
+                args[i] = args[i].replaceAll("\\\\;", ";");
+
+        Processor rover = domain;
+        while (rover != null) {
+            String result = doCommand(rover, args[0], args);
+            if (result != null)
+                return result;
+
+            rover = rover.getParent();
+        }
+
+        for (int i = 0; targets != null && i < targets.length; i++) {
+            String result = doCommand(targets[i], args[0], args);
+            if (result != null)
+                return result;
+        }
+
+        return doCommand(this, args[0], args);
+    }
+
+    private String doCommand(Object target, String method, String[] args) {
+        if (target == null)
+            ; // System.out.println("Huh? Target should never be null " +
+                // domain);
+        else {
+            String cname = "_" + method.replaceAll("-", "_");
+            try {
+                Method m = target.getClass().getMethod(cname,
+                        new Class[] { String[].class });
+                return (String) m.invoke(target, new Object[] { args });
+            } catch (NoSuchMethodException e) {
+                // Ignore
+            } catch (InvocationTargetException e) {
+                domain.warning("Exception in replace: " + e.getCause());
+                e.printStackTrace();
+            } catch (Exception e) {
+                domain.warning("Exception in replace: " + e + " method="
+                        + method);
+                e.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return a unique list where the duplicates are removed.
+     * 
+     * @param args
+     * @return
+     */
+    static String _uniqHelp = "${uniq;<list> ...}";
+
+    public String _uniq(String args[]) {
+        verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
+        Set<String> set = new LinkedHashSet<String>();
+        for (int i = 1; i < args.length; i++) {
+            Processor.split(args[i], set);
+        }
+        return Processor.join(set, ",");
+    }
+
+    public String _filter(String args[]) {
+        return filter(args, false);
+    }
+
+    public String _filterout(String args[]) {
+        return filter(args, true);
+
+    }
+
+    static String _filterHelp = "${%s;<list>;<regex>}";
+
+    String filter(String[] args, boolean include) {
+        verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
+
+        Collection<String> list = new ArrayList<String>(Processor
+                .split(args[1]));
+        Pattern pattern = Pattern.compile(args[2]);
+
+        for (Iterator<String> i = list.iterator(); i.hasNext();) {
+            if (pattern.matcher(i.next()).matches() == include)
+                i.remove();
+        }
+        return Processor.join(list);
+    }
+
+    static String _sortHelp = "${sort;<list>...}";
+
+    public String _sort(String args[]) {
+        verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
+
+        List<String> result = new ArrayList<String>();
+        for (int i = 1; i < args.length; i++) {
+            Processor.split(args[i], result);
+        }
+        Collections.sort(result);
+        return Processor.join(result);
+    }
+
+    static String _joinHelp = "${join;<list>...}";
+
+    public String _join(String args[]) {
+
+        verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
+
+        List<String> result = new ArrayList<String>();
+        for (int i = 1; i < args.length; i++) {
+            Processor.split(args[i], result);
+        }
+        return Processor.join(result);
+    }
+
+    static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";
+
+    public String _if(String args[]) {
+        verifyCommand(args, _ifHelp, null, 3, 4);
+        String condition = args[1].trim();
+        if (condition.length() != 0)
+            return args[2];
+        if (args.length > 3)
+            return args[3];
+        else
+            return "";
+    }
+
+    public String _now(String args[]) {
+        return new Date().toString();
+    }
+
+    public static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
+
+    public String _fmodified(String args[]) throws Exception {
+        verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
+
+        long time = 0;
+        Collection<String> names = new ArrayList<String>();
+        for (int i = 1; i < args.length; i++) {
+            Processor.split(args[i], names);
+        }
+        for (String name : names) {
+            File f = new File(name);
+            if (f.exists() && f.lastModified() > time)
+                time = f.lastModified();
+        }
+        return "" + time;
+    }
+
+    public String _long2date(String args[]) {
+        try {
+            return new Date(Long.parseLong(args[1])).toString();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "not a valid long";
+    }
+
+    public String _literal(String args[]) {
+        if (args.length != 2)
+            throw new RuntimeException(
+                    "Need a value for the ${literal;<value>} macro");
+        return "${" + args[1] + "}";
+    }
+
+    public String _def(String args[]) {
+        if (args.length != 2)
+            throw new RuntimeException(
+                    "Need a value for the ${def;<value>} macro");
+
+        String value = properties.getProperty(args[1]);
+        if (value == null)
+            return "";
+        else
+            return value;
+    }
+
+    /**
+     * 
+     * replace ; <list> ; regex ; replace
+     * 
+     * @param args
+     * @return
+     */
+    public String _replace(String args[]) {
+        if (args.length != 4) {
+            domain.warning("Invalid nr of arguments to replace "
+                    + Arrays.asList(args));
+            return null;
+        }
+
+        String list[] = args[1].split("\\s*,\\s*");
+        StringBuffer sb = new StringBuffer();
+        String del = "";
+        for (int i = 0; i < list.length; i++) {
+            String element = list[i].trim();
+            if (!element.equals("")) {
+                sb.append(del);
+                sb.append(element.replaceAll(args[2], args[3]));
+                del = ", ";
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public String _warning(String args[]) {
+        for (int i = 1; i < args.length; i++) {
+            domain.warning(process(args[i]));
+        }
+        return "";
+    }
+
+    public String _error(String args[]) {
+        for (int i = 1; i < args.length; i++) {
+            domain.error(process(args[i]));
+        }
+        return "";
+    }
+
+    /**
+     * toclassname ; <path>.class ( , <path>.class ) *
+     * 
+     * @param args
+     * @return
+     */
+    static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
+
+    public String _toclassname(String args[]) {
+        verifyCommand(args, _toclassnameHelp, null, 2, 2);
+        Collection<String> paths = Processor.split(args[1]);
+
+        List<String> names = new ArrayList<String>(paths.size());
+        for (String path : paths) {
+            if (path.endsWith(".class")) {
+                String name = path.substring(0, path.length() - 6).replace('/',
+                        '.');
+                names.add(name);
+            } else if (path.endsWith(".java")) {
+                String name = path.substring(0, path.length() - 5).replace('/',
+                        '.');
+                names.add(name);
+            } else {
+                domain
+                        .warning("in toclassname, "
+                                + args[1]
+                                + " is not a class path because it does not end in .class");
+            }
+        }
+        return Processor.join(names, ",");
+    }
+
+    /**
+     * toclassname ; <path>.class ( , <path>.class ) *
+     * 
+     * @param args
+     * @return
+     */
+
+    static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
+
+    public String _toclasspath(String args[]) {
+        verifyCommand(args, _toclasspathHelp, null, 2, 3);
+        boolean cl= true;
+        if (args.length>2)
+            cl = new Boolean(args[2]);
+        
+        Collection<String> names = Processor.split(args[1]);
+        Collection<String> paths = new ArrayList<String>(names.size());
+        for (String name : names) {
+            String path = name.replace('.', '/') + (cl ? ".class" : "");
+            paths.add(path);
+        }
+        return Processor.join(paths, ",");
+    }
+
+    public String _dir(String args[]) {
+        if (args.length < 2) {
+            domain.warning("Need at least one file name for ${dir;...}");
+            return null;
+        } else {
+            String del = "";
+            StringBuffer sb = new StringBuffer();
+            for (int i = 1; i < args.length; i++) {
+                File f = new File(args[i]).getAbsoluteFile();
+                if (f.exists() && f.getParentFile().exists()) {
+                    sb.append(del);
+                    sb.append(f.getParentFile().getAbsolutePath());
+                    del = ",";
+                }
+            }
+            return sb.toString();
+        }
+
+    }
+
+    public String _basename(String args[]) {
+        if (args.length < 2) {
+            domain.warning("Need at least one file name for ${basename;...}");
+            return null;
+        } else {
+            String del = "";
+            StringBuffer sb = new StringBuffer();
+            for (int i = 1; i < args.length; i++) {
+                File f = new File(args[i]).getAbsoluteFile();
+                if (f.exists() && f.getParentFile().exists()) {
+                    sb.append(del);
+                    sb.append(f.getName());
+                    del = ",";
+                }
+            }
+            return sb.toString();
+        }
+
+    }
+
+    public String _isfile(String args[]) {
+        if (args.length < 2) {
+            domain.warning("Need at least one file name for ${isfile;...}");
+            return null;
+        } else {
+            boolean isfile = true;
+            for (int i = 1; i < args.length; i++) {
+                File f = new File(args[i]).getAbsoluteFile();
+                isfile &= f.isFile();
+            }
+            return isfile ? "true" : "false";
+        }
+
+    }
+
+    public String _isdir(String args[]) {
+        if (args.length < 2) {
+            domain.warning("Need at least one file name for ${isdir;...}");
+            return null;
+        } else {
+            boolean isdir = true;
+            for (int i = 1; i < args.length; i++) {
+                File f = new File(args[i]).getAbsoluteFile();
+                isdir &= f.isDirectory();
+            }
+            return isdir ? "true" : "false";
+        }
+
+    }
+
+    public String _tstamp(String args[]) {
+        String format = "yyyyMMddHHmm";
+        long now = System.currentTimeMillis();
+
+        if (args.length > 1) {
+            format = args[1];
+            if (args.length > 2) {
+                now = Long.parseLong(args[2]);
+                if (args.length > 3) {
+                    domain.warning("Too many arguments for tstamp: "
+                            + Arrays.toString(args));
+                }
+            }
+        }
+        SimpleDateFormat sdf = new SimpleDateFormat(format);
+        return sdf.format(new Date(now));
+    }
+
+    /**
+     * Wildcard a directory. The lists can contain Instruction that are matched
+     * against the given directory
+     * 
+     * ${wc;<dir>;<list>(;<list>)*}
+     * 
+     * @author aqute
+     * 
+     */
+
+    public String _lsr(String args[]) {
+        return ls(args, true);
+    }
+
+    public String _lsa(String args[]) {
+        return ls(args, false);
+    }
+
+    String ls(String args[], boolean relative) {
+        if (args.length < 2)
+            throw new IllegalArgumentException(
+                    "the ${ls} macro must at least have a directory as parameter");
+
+        File dir = new File(args[1]);
+        if (!dir.isAbsolute())
+            throw new IllegalArgumentException(
+                    "the ${ls} macro directory parameter is not absolute: "
+                            + dir);
+
+        if (!dir.exists())
+            throw new IllegalArgumentException(
+                    "the ${ls} macro directory parameter does not exist: "
+                            + dir);
+
+        if (!dir.isDirectory())
+            throw new IllegalArgumentException(
+                    "the ${ls} macro directory parameter points to a file instead of a directory: "
+                            + dir);
+
+        String[] files = dir.list();
+        List<String> result;
+
+        if (args.length < 3) {
+            result = Arrays.asList(files);
+        } else
+            result = new ArrayList<String>();
+
+        for (int i = 2; i < args.length; i++) {
+            String parts[] = args[i].split("\\s*,\\s*");
+            for (String pattern : parts) {
+                // So make it in to an instruction
+                Instruction instr = Instruction.getPattern(pattern);
+
+                // For each project, match it against the instruction
+                for (int f = 0; f < files.length; f++) {
+                    if (files[f] != null) {
+                        if (instr.matches(files[f])) {
+                            if (!instr.isNegated()) {
+                                if (relative)
+                                    result.add(files[f]);
+                                else
+                                    result.add(new File(dir, files[f])
+                                            .getAbsolutePath());
+                            }
+                            files[f] = null;
+                        }
+                    }
+                }
+            }
+        }
+        return Processor.join(result, ",");
+    }
+
+    public String _currenttime(String args[]) {
+        return Long.toString(System.currentTimeMillis());
+    }
+
+    /**
+     * Modify a version to set a version policy. Thed policy is a mask that is
+     * mapped to a version.
+     * 
+     * <pre>
+     * +           increment
+     * -           decrement
+     * =           maintain
+     * &tilde;           discard
+     * 
+     * ==+      = maintain major, minor, increment micro, discard qualifier
+     * &tilde;&tilde;&tilde;=     = just get the qualifier
+     * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
+     * </pre>
+     * 
+     * 
+     * 
+     * 
+     * @param args
+     * @return
+     */
+    static String  _versionHelp      = "${version;<mask>;<version>}, modify a version\n"
+                                             + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
+                                             + "M ::= '+' | '-' | MQ\n"
+                                             + "MQ ::= '~' | '='";
+    static Pattern _versionPattern[] = new Pattern[] { null, null,
+            Pattern.compile("[-+=~]{0,3}[=~]?"), Verifier.VERSION };
+
+    public String _version(String args[]) {
+        verifyCommand(args, _versionHelp, null, 3, 3);
+
+        String mask = args[1];
+
+        Version version = new Version(args[2]);
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+
+        for (int i = 0; i < mask.length(); i++) {
+            char c = mask.charAt(i);
+            String result = null;
+            if (c != '~') {
+                if (i == 3) {
+                    result = version.getQualifier();
+                } else if (Character.isDigit(c)) {
+                	// Handle masks like +00, =+0
+                	result = String.valueOf(c);
+                } else {
+                    int x = version.get(i);
+                    switch (c) {
+                    case '+':
+                        x++;
+                        break;
+                    case '-':
+                        x--;
+                        break;
+                    case '=':
+                        break;
+                    }
+                    result = Integer.toString(x);
+                }
+                if (result != null) {
+                    sb.append(del);
+                    del = ".";
+                    sb.append(result);
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * System command. Execute a command and insert the result.
+     * 
+     * @param args
+     * @param help
+     * @param patterns
+     * @param low
+     * @param high
+     */
+    public String _system(String args[]) throws Exception {
+        verifyCommand(args,
+                "${system;<command>[;<in>]}, execute a system command", null,
+                2, 3);
+        String command = args[1];
+        String input = null;
+
+        if (args.length > 2) {
+            input = args[2];
+        }
+
+        Process process = Runtime.getRuntime().exec(command, null,
+                domain.getBase());
+        if (input != null) {
+            process.getOutputStream().write(input.getBytes("UTF-8"));
+        }
+        process.getOutputStream().close();
+
+        String s = getString(process.getInputStream());
+        process.getInputStream().close();
+        int exitValue = process.waitFor();
+        if (exitValue != 0) {
+            domain.error("System command " + command + " failed with "
+                    + exitValue);
+        }
+        return s.trim();
+    }
+
+    /**
+     * Get the contents of a file.
+     * 
+     * @param in
+     * @return
+     * @throws IOException
+     */
+
+    public String _cat(String args[]) throws IOException {
+        verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2,
+                2);
+        File f = domain.getFile(args[1]);
+        if (f.isFile()) {
+            InputStream in = new FileInputStream(f);
+            return getString(in);
+        } else if (f.isDirectory()) {
+            return Arrays.toString(f.list());
+        } else {
+            try {
+                URL url = new URL(args[1]);
+                InputStream in = url.openStream();
+                return getString(in);
+            } catch (MalformedURLException mfue) {
+                // Ignore here
+            }
+            return null;
+        }
+    }
+
+    public static String getString(InputStream in) throws IOException {
+        try {
+            StringBuilder sb = new StringBuilder();
+            BufferedReader rdr = new BufferedReader(new InputStreamReader(in));
+            String line = null;
+            while ((line = rdr.readLine()) != null) {
+                sb.append(line);
+                sb.append("\n");
+            }
+            return sb.toString();
+        } finally {
+            in.close();
+        }
+    }
+
+    public static void verifyCommand(String args[], String help,
+            Pattern[] patterns, int low, int high) {
+        String message = "";
+        if (args.length > high) {
+            message = "too many arguments";
+        } else if (args.length < low) {
+            message = "too few arguments";
+        } else {
+            for (int i = 0; patterns != null && i < patterns.length
+                    && i < args.length - 1; i++) {
+                if (patterns[i] != null
+                        && !patterns[i].matcher(args[i + 1]).matches()) {
+                    message += String.format(
+                            "Argument %s (%s) does not match %s\n", i, args[i],
+                            patterns[i].pattern());
+                }
+            }
+        }
+        if (message.length() != 0) {
+            StringBuilder sb = new StringBuilder();
+            String del = "${";
+            for (String arg : args) {
+                sb.append(del);
+                sb.append(arg);
+                del = ";";
+            }
+            sb.append("}, is not understood. ");
+            sb.append(message);
+            throw new IllegalArgumentException(sb.toString());
+        }
+    }
+
+    // Helper class to track expansion of variables
+    // on the stack.
+    static class Link {
+        Link   previous;
+        String key;
+
+        public Link(Link previous, String key) {
+            this.previous = previous;
+            this.key = key;
+        }
+
+        public boolean contains(String key) {
+            if (this.key.equals(key))
+                return true;
+
+            if (previous == null)
+                return false;
+
+            return previous.contains(key);
+        }
+
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            String del = "[";
+            for (Link r = this; r != null; r = r.previous) {
+                sb.append(del);
+                sb.append(r.key);
+                del = ",";
+            }
+            sb.append("]");
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Take all the properties and translate them to actual values. This method
+     * takes the set properties and traverse them over all entries, including
+     * the default properties for that properties. The values no longer contain
+     * macros.
+     * 
+     * @return A new Properties with the flattened values
+     */
+    public Properties getFlattenedProperties() {
+        // Some macros only work in a lower processor, so we
+        // do not report unknown macros while flattening
+        flattening = true;
+        try {
+            Properties flattened = new Properties();
+            for (Enumeration<?> e = properties.propertyNames(); e
+                    .hasMoreElements();) {
+                String key = (String) e.nextElement();
+                if (!key.startsWith("_"))
+                    if ( key.startsWith("-"))
+                        flattened.put(key, properties.getProperty(key));
+                    else
+                        flattened.put(key, process(properties.getProperty(key)));
+            }
+            return flattened;
+        } finally {
+            flattening = false;
+        }
+    };
+
+}

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