/* 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.regex.*;
import aQute.qtokens.QuotedTokenizer;
public class Processor {
public final static String DEFAULT_BND_EXTENSION = ".bnd";
public final static String DEFAULT_JAR_EXTENSION = ".jar";
public final static String DEFAULT_BAR_EXTENSION = ".bar";
List errors = new ArrayList();
List warnings = new ArrayList();
public void getInfo(Processor processor) {
errors.addAll(processor.errors);
warnings.addAll(processor.warnings);
}
public void warning(String string) {
warnings.add(string);
}
public void error(String string) {
errors.add(string);
}
public List getWarnings() {
return warnings;
}
public List getErrors() {
return errors;
}
public Map parseHeader(String value) {
return parseHeader(value, this);
}
/**
* Standard OSGi header parser. This parser can handle the format clauses ::=
* clause ( ',' clause ) + clause ::= name ( ';' name ) (';' key '=' value )
*
* This is mapped to a Map { name => Map { attr|directive => value } }
*
* @param value
* @return
* @throws MojoExecutionException
*/
static public Map parseHeader(String value,
Processor logger) {
if (value == null || value.trim().length() == 0) return new HashMap();
Map result = new LinkedHashMap();
QuotedTokenizer qt = new QuotedTokenizer(value, ";=,");
char del;
do {
boolean hadAttribute = false;
Map clause = new HashMap();
List aliases = new ArrayList();
aliases.add(qt.nextToken());
del = qt.getSeparator();
while (del == ';') {
String adname = qt.nextToken();
if ((del = qt.getSeparator()) != '=') {
if (hadAttribute) throw new IllegalArgumentException(
"Header contains name field after attribute or directive: "
+ adname + " from " + value);
aliases.add(adname);
} else {
String advalue = qt.nextToken();
clause.put(adname, advalue);
del = qt.getSeparator();
hadAttribute = true;
}
}
for (Iterator i = aliases.iterator(); i.hasNext();) {
String packageName = (String) i.next();
if (result.containsKey(packageName)) {
if (logger != null) logger
.warning("Duplicate package name in header: "
+ packageName
+ ". Multiple package names in one clause not supported in Bnd.");
} else result.put(packageName, clause);
}
} while (del == ',');
return result;
}
public Map analyzeBundleClasspath(Jar dot, Map bundleClasspath,
Map contained, Map referred, Map uses)
throws IOException {
Map classSpace = new HashMap();
if (bundleClasspath.isEmpty()) {
analyzeJar(dot, "", classSpace, contained, referred,
uses);
} else {
for (Iterator j = bundleClasspath.keySet().iterator(); j
.hasNext();) {
String path = (String) j.next();
if ( path.equals(".") ) {
analyzeJar(dot, "", classSpace, contained,
referred, uses);
continue;
}
//
// There are 3 cases:
// - embedded JAR file
// - directory
// - error
//
Resource resource = dot.getResource(path);
if (resource != null) {
try {
Jar jar = new Jar(path);
EmbeddedResource.build(jar, resource
.openInputStream());
analyzeJar(jar, "", classSpace, contained,
referred, uses);
} catch (Exception e) {
warning("Invalid bundle classpath entry: "
+ path + " " + e);
}
} else {
if (dot.getDirectories().containsKey(path)) {
analyzeJar(dot, path, classSpace, contained,
referred, uses);
} else {
warning("No sub JAR or directory " + path);
}
}
}
}
return classSpace;
}
/**
* We traverse through al the classes that we can find and calculate the
* contained and referred set and uses. This method ignores the Bundle
* classpath.
*
* @param jar
* @param contained
* @param referred
* @param uses
* @throws IOException
*/
private void analyzeJar(Jar jar, String prefix,
Map classSpace, Map contained, Map referred, Map uses)
throws IOException {
next: for (Iterator r = jar.getResources().keySet()
.iterator(); r.hasNext();) {
String path = (String) r.next();
if (path.startsWith(prefix)) {
String relativePath = path.substring(prefix
.length());
String pack = getPackage(relativePath);
if (!contained.containsKey(pack)) {
if (!(pack.equals(".") || pack.equals("META-INF"))) {
Map map = new HashMap();
contained.put(pack, map);
Resource pinfo = jar.getResource(prefix
+ pack.replace('.', '/') + "/packageinfo");
if (pinfo != null) {
String version = parsePackageInfo(pinfo
.openInputStream());
if (version != null) map.put("version",
version);
}
}
}
if (path.endsWith(".class")) {
Resource resource = jar.getResource(path);
Clazz clazz;
try {
clazz = new Clazz(relativePath, resource
.openInputStream());
} catch (Throwable e) {
errors.add("Invalid class file: "
+ relativePath + " " + e.getMessage());
continue next;
}
classSpace.put(relativePath, clazz);
referred.putAll(clazz.getReferred());
// Add all the used packages
// to this package
Set t = (Set) uses.get(pack);
if (t == null) uses.put(pack, t = new HashSet());
t.addAll(clazz.getReferred().keySet());
t.remove(pack);
}
}
}
}
public String getPackage(String clazz) {
int n = clazz.lastIndexOf('/');
if (n < 0) return ".";
return clazz.substring(0, n).replace('/', '.');
}
static Pattern packageinfo = Pattern
.compile("version\\s+([0-9.]+).*");
static String parsePackageInfo(InputStream jar)
throws IOException {
try {
byte[] buf = EmbeddedResource.collect(jar, 0);
String line = new String(buf).trim();
Matcher m = packageinfo.matcher(line);
if (m.matches()) {
return m.group(1);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
static Map removeKeys(Map source, String prefix) {
Map temp = new TreeMap(source);
for (Iterator p = temp.keySet().iterator(); p.hasNext();) {
String pack = (String) p.next();
if (pack.startsWith(prefix)) p.remove();
}
return temp;
}
}