FELIX-661: temporarily add BND Builder source so we can apply a minor patch
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@683306 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/NOTICE b/bundleplugin/NOTICE
index b12ce4c..5ecb6de 100644
--- a/bundleplugin/NOTICE
+++ b/bundleplugin/NOTICE
@@ -13,6 +13,11 @@
Copyright 2006-2008 The OSGi Alliance.
Licensed under the Apache License 2.0.
+This product includes software developed by Peter Kriens
+(http://www.aqute.biz/Code/Bnd)
+Copyright 2006-2008 aQute, All rights reserved
+Licensed under the Apache License 2.0.
+
II. Used Software
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
new file mode 100644
index 0000000..1b0f0ad
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
@@ -0,0 +1,769 @@
+/* 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.*;
+
+/**
+ * Include-Resource: ( [name '=' ] file )+
+ *
+ * Private-Package: package-decl ( ',' package-decl )*
+ *
+ * Export-Package: package-decl ( ',' package-decl )*
+ *
+ * Import-Package: package-decl ( ',' package-decl )*
+ *
+ * @version $Revision: 1.4 $
+ */
+public class Builder extends Analyzer {
+ private static final int SPLIT_MERGE_LAST = 1;
+ private static final int SPLIT_MERGE_FIRST = 2;
+ private static final int SPLIT_ERROR = 3;
+ private static final int SPLIT_FIRST = 4;
+ private static final int SPLIT_DEFAULT = 0;
+
+ boolean sources = false;
+ File[] sourcePath;
+ Pattern NAME_URL = Pattern
+ .compile("(.*)(http://.*)");
+
+ public Jar build() throws Exception {
+ begin();
+
+ dot = new Jar("dot");
+ doPrebuild(dot, parseHeader(getProperty("-prebuild")));
+ doExpand(dot);
+ doIncludeResources(dot);
+
+ doConditional(dot);
+
+ dot.setManifest(calcManifest());
+ // This must happen after we analyzed so
+ // we know what it is on the classpath
+ addSources(dot);
+ if (getProperty(POM) != null)
+ doPom(dot);
+
+ doVerify(dot);
+ if (dot.getResources().isEmpty())
+ error("The JAR is empty");
+
+ dot.updateModified(lastModified());
+ return dot;
+ }
+
+ void begin() {
+ super.begin();
+ if (getProperty(IMPORT_PACKAGE) == null)
+ setProperty(IMPORT_PACKAGE, "*");
+
+ sources = getProperty(SOURCES) != null;
+ }
+
+ private void doConditional(Jar dot) throws IOException {
+ Map conditionals = getHeader(CONDITIONAL_PACKAGE);
+ if (conditionals != null && conditionals.size() > 0) {
+ int size;
+ do {
+ size = dot.getDirectories().size();
+ analyze();
+ analyzed = false;
+ Map imports = getImports();
+
+ Map filtered = merge(CONDITIONAL_PACKAGE, conditionals,
+ imports, new HashSet());
+
+ // remove existing packages to prevent merge errors
+ filtered.keySet().removeAll(dot.getPackages());
+ filtered = replaceWithPattern(filtered);
+ doExpand(dot, CONDITIONAL_PACKAGE, filtered);
+ } while (dot.getDirectories().size() > size);
+ }
+ }
+
+ /**
+ * Intercept the call to analyze and cleanup versions after we have analyzed
+ * the setup. We do not want to cleanup if we are going to verify.
+ */
+
+ public void analyze() throws IOException {
+ super.analyze();
+ cleanupVersion(imports);
+ cleanupVersion(exports);
+ String version = getProperty(BUNDLE_VERSION);
+ if (version != null)
+ setProperty(BUNDLE_VERSION, cleanupVersion(version));
+ }
+
+ public void cleanupVersion(Map mapOfMap) {
+ for (Iterator e = mapOfMap.entrySet().iterator(); e.hasNext();) {
+ Map.Entry entry = (Map.Entry) e.next();
+ Map attributes = (Map) entry.getValue();
+ if (attributes.containsKey("version")) {
+ attributes.put("version", cleanupVersion((String) attributes
+ .get("version")));
+ }
+ }
+ }
+
+ /**
+ * Clean up version parameters. Other builders use more fuzzy definitions of
+ * the version syntax. This method cleans up such a version to match an OSGi
+ * version.
+ *
+ * @param version
+ * @return
+ */
+ static Pattern fuzzyVersion = Pattern
+ .compile(
+ "(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
+ Pattern.DOTALL);
+ static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)",
+ Pattern.DOTALL);
+
+ static Pattern nummeric = Pattern.compile("\\d*");
+
+ static public String cleanupVersion(String version) {
+ Matcher m = fuzzyVersion.matcher(version);
+ if (m.matches()) {
+ StringBuffer result = new StringBuffer();
+ String d1 = m.group(1);
+ String d2 = m.group(3);
+ String d3 = m.group(5);
+ String qualifier = m.group(7);
+
+ if (d1 != null) {
+ result.append(d1);
+ if (d2 != null) {
+ result.append(".");
+ result.append(d2);
+ if (d3 != null) {
+ result.append(".");
+ result.append(d3);
+ if (qualifier != null) {
+ result.append(".");
+ cleanupModifier(result, qualifier);
+ }
+ } else if (qualifier != null) {
+ result.append(".0.");
+ cleanupModifier(result, qualifier);
+ }
+ } else if (qualifier != null) {
+ result.append(".0.0.");
+ cleanupModifier(result, qualifier);
+ }
+ return result.toString();
+ }
+ }
+ return version;
+ }
+
+ static void cleanupModifier(StringBuffer result, String modifier) {
+ Matcher m = fuzzyModifier.matcher(modifier);
+ if (m.matches())
+ modifier = m.group(2);
+
+ for (int i = 0; i < modifier.length(); i++) {
+ char c = modifier.charAt(i);
+ if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
+ result.append(c);
+ }
+ }
+
+ /**
+ *
+ */
+ private void addSources(Jar dot) {
+ if (!sources)
+ return;
+
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ getProperties().store(out, "Generated by BND, at " + new Date());
+ dot.putResource("OSGI-OPT/bnd.bnd", new EmbeddedResource(out
+ .toByteArray(), 0));
+ out.close();
+ } catch (Exception e) {
+ error("Can not embed bnd file in JAR: " + e);
+ }
+
+ for (Iterator cpe = classspace.keySet().iterator(); cpe.hasNext();) {
+ String path = (String) cpe.next();
+ path = path.substring(0, path.length() - ".class".length())
+ + ".java";
+
+ for (int i = 0; i < sourcePath.length; i++) {
+ File root = sourcePath[i];
+ File f = getFile(root, path);
+ if (f.exists()) {
+ dot
+ .putResource("OSGI-OPT/src/" + path,
+ new FileResource(f));
+ }
+ }
+ }
+ }
+
+ private void doVerify(Jar dot) throws Exception {
+ Verifier verifier = new Verifier(dot, getProperties());
+ verifier.setPedantic(isPedantic());
+ verifier.verify();
+ errors.addAll(verifier.getErrors());
+ warnings.addAll(verifier.getWarnings());
+ }
+
+ private void doExpand(Jar jar) throws IOException {
+ if (classpath.size() == 0
+ && (getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
+ warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
+
+ Map prive = replaceWithPattern(getHeader(Analyzer.PRIVATE_PACKAGE));
+ Map export = replaceWithPattern(getHeader(Analyzer.EXPORT_PACKAGE));
+ if (prive.isEmpty() && export.isEmpty()) {
+ warnings
+ .add("Neither Export-Package nor Private-Package is set, therefore no packages will be included");
+ }
+ doExpand(jar, EXPORT_PACKAGE, export);
+ doExpand(jar, PRIVATE_PACKAGE, prive);
+ }
+
+ private void doExpand(Jar jar, String name, Map instructions) {
+ Set superfluous = new HashSet(instructions.keySet());
+ for (Iterator c = classpath.iterator(); c.hasNext();) {
+ Jar now = (Jar) c.next();
+ doExpand(jar, instructions, now, superfluous);
+ }
+ if (superfluous.size() > 0) {
+ StringBuffer sb = new StringBuffer();
+ String del = "Instructions for " + name + " that are never used: ";
+ for (Iterator i = superfluous.iterator(); i.hasNext();) {
+ Instruction p = (Instruction) i.next();
+ sb.append(del);
+ sb.append(p.getPattern());
+ del = ", ";
+ }
+ warning(sb.toString());
+ }
+ }
+
+ /**
+ * Iterate over each directory in the class path entry and check if that
+ * directory is a desired package.
+ *
+ * @param included
+ * @param classpathEntry
+ */
+ private void doExpand(Jar jar, Map included, Jar classpathEntry,
+ Set superfluous) {
+
+ loop: for (Iterator p = classpathEntry.getDirectories().entrySet()
+ .iterator(); p.hasNext();) {
+ Map.Entry directory = (Map.Entry) p.next();
+ String path = (String) directory.getKey();
+
+ if (doNotCopy.matcher(getName(path)).matches())
+ continue;
+
+ if (directory.getValue() == null)
+ continue;
+
+ String pack = path.replace('/', '.');
+ Instruction instr = matches(included, pack, superfluous);
+ if (instr != null) {
+ // System.out.println("Pattern match: " + pack + " " +
+ // instr.getPattern() + " " + instr.isNegated());
+ if (!instr.isNegated()) {
+ Map contents = (Map) directory.getValue();
+
+ // What to do with split packages? Well if this
+ // directory already exists, we will check the strategy
+ // and react accordingly.
+ boolean overwriteResource = true;
+ if (jar.hasDirectory(path)) {
+ Map directives = (Map) included.get(instr);
+
+ switch (getSplitStrategy((String) directives
+ .get(SPLIT_PACKAGE_DIRECTIVE))) {
+ case SPLIT_MERGE_LAST:
+ overwriteResource = true;
+ break;
+
+ case SPLIT_MERGE_FIRST:
+ overwriteResource = false;
+ break;
+
+ case SPLIT_ERROR:
+ error("Split package generates error: " + pack);
+ continue loop;
+
+ case SPLIT_FIRST:
+ continue loop;
+
+ default:
+ // Default is like merge-first, but with a warning
+ warning("There are split packages, use directive -split-package:=(merge-first|merge-last) on instruction to get rid of this warning: "
+ + pack
+ + ", classpath: "
+ + classpath
+ + " from: " + classpathEntry.source);
+ overwriteResource = false;
+ break;
+ }
+ }
+ jar.addDirectory(contents, overwriteResource);
+ }
+ }
+ }
+ }
+
+ private int getSplitStrategy(String type) {
+ if (type == null)
+ return SPLIT_DEFAULT;
+
+ if (type.equals("merge-last"))
+ return SPLIT_MERGE_LAST;
+
+ if (type.equals("merge-first"))
+ return SPLIT_MERGE_FIRST;
+
+ if (type.equals("error"))
+ return SPLIT_ERROR;
+
+ if (type.equals("first"))
+ return SPLIT_FIRST;
+
+ error("Invalid strategy for split-package: " + type);
+ return SPLIT_DEFAULT;
+ }
+
+ private Map replaceWithPattern(Map header) {
+ Map map = new LinkedHashMap();
+ for (Iterator e = header.entrySet().iterator(); e.hasNext();) {
+ Map.Entry entry = (Map.Entry) e.next();
+ String pattern = (String) entry.getKey();
+ Instruction instr = Instruction.getPattern(pattern);
+ map.put(instr, entry.getValue());
+ }
+ return map;
+ }
+
+ private Instruction matches(Map instructions, String pack,
+ Set superfluousPatterns) {
+ for (Iterator i = instructions.keySet().iterator(); i.hasNext();) {
+ Instruction pattern = (Instruction) i.next();
+ if (pattern.matches(pack)) {
+ superfluousPatterns.remove(pattern);
+ return pattern;
+ }
+ }
+ return null;
+ }
+
+ private Map getHeader(String string) {
+ if (string == null)
+ return new LinkedHashMap();
+ return parseHeader(getProperty(string));
+ }
+
+ /**
+ * Parse the Bundle-Includes header. Files in the bundles Include header are
+ * included in the jar. The source can be a directory or a file.
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+ private void doIncludeResources(Jar jar) throws Exception {
+ Macro macro = new Macro(getProperties(), this);
+
+ String includes = getProperty("Bundle-Includes");
+ if (includes == null)
+ includes = getProperty("Include-Resource");
+ else
+ warnings
+ .add("Please use Include-Resource instead of Bundle-Includes");
+
+ if (includes == null)
+ return;
+
+ for (Iterator i = getClauses(includes).iterator(); i.hasNext();) {
+ boolean preprocess = false;
+ String clause = (String) i.next();
+ if (clause.startsWith("{") && clause.endsWith("}")) {
+ preprocess = true;
+ clause = clause.substring(1, clause.length() - 1).trim();
+ }
+
+ Map extra = new HashMap();
+ int n = clause.indexOf(';');
+ if (n > 0) {
+ String attributes = clause.substring(n + 1);
+ String parts[] = attributes.split("\\s*;\\s*");
+ for (int j = 0; j < parts.length; j++) {
+ String assignment[] = parts[j].split("\\s*=\\s*");
+ if (assignment.length == 2)
+ extra.put(assignment[0], assignment[1]);
+ else
+ error("Invalid attribute on Include-Resource: "
+ + clause);
+ }
+ clause = clause.substring(0, n);
+ }
+
+ if (clause.startsWith("@")) {
+ extractFromJar(jar, clause.substring(1));
+ } else {
+ String parts[] = clause.split("\\s*=\\s*");
+
+ String source;
+ File sourceFile;
+ String destinationPath;
+
+ if (parts.length == 1) {
+ // Just a copy, destination path defined by
+ // source path.
+ source = parts[0];
+ sourceFile = getFile(base, source);
+ // Directories should be copied to the root
+ // but files to their file name ...
+ if (sourceFile.isDirectory())
+ destinationPath = "";
+ else
+ destinationPath = sourceFile.getName();
+ } else {
+ source = parts[1];
+ sourceFile = getFile(base, source);
+ destinationPath = parts[0];
+ }
+
+ // Some people insist on ending a directory with
+ // a slash ... it now also works if you do /=dir
+ if (destinationPath.endsWith("/"))
+ destinationPath = destinationPath.substring(0,
+ destinationPath.length() - 1);
+
+ if (!sourceFile.exists()) {
+ Jar src = getJarFromName(source, "Include-Resource "
+ + source);
+ if (src != null) {
+ JarResource jarResource = new JarResource(src);
+ jar.putResource(destinationPath, jarResource);
+ } else {
+ error("Input file does not exist: " + source);
+ }
+ } else
+ copy(jar, destinationPath, sourceFile, preprocess ? macro
+ : null, extra);
+ }
+ }
+ }
+
+ /**
+ * Extra resources from a Jar and add them to the given jar. The clause is
+ * the
+ *
+ * @param jar
+ * @param clauses
+ * @param i
+ * @throws ZipException
+ * @throws IOException
+ */
+ private void extractFromJar(Jar jar, String name) throws ZipException,
+ IOException {
+ // Inline all resources and classes from another jar
+ // optionally appended with a modified regular expression
+ // like @zip.jar!/META-INF/MANIFEST.MF
+ int n = name.lastIndexOf("!/");
+ Pattern filter = null;
+ if (n > 0) {
+ String fstring = name.substring(n + 2);
+ name = name.substring(0, n);
+ filter = wildcard(fstring);
+ }
+ Jar sub = getJarFromName(name, "extract from jar");
+ if (sub == null)
+ error("Can not find JAR file " + name);
+ else
+ jar.addAll(sub, filter);
+ }
+
+ private Pattern wildcard(String spec) {
+ StringBuffer sb = new StringBuffer();
+ for (int j = 0; j < spec.length(); j++) {
+ char c = spec.charAt(j);
+ switch (c) {
+ case '.':
+ sb.append("\\.");
+ break;
+
+ case '*':
+ // test for ** (all directories)
+ if (j < spec.length() - 1 && spec.charAt(j + 1) == '*') {
+ sb.append(".*");
+ j++;
+ } else
+ sb.append("[^/]*");
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ String s = sb.toString();
+ try {
+ return Pattern.compile(s);
+ } catch (Exception e) {
+ error("Invalid regular expression on wildcarding: " + spec
+ + " used *");
+ }
+ return null;
+ }
+
+ private void copy(Jar jar, String path, File from, Macro macro, Map extra)
+ throws Exception {
+ if (doNotCopy.matcher(from.getName()).matches())
+ return;
+
+ if (from.isDirectory()) {
+ String next = path;
+ if (next.length() != 0)
+ next += '/';
+
+ File files[] = from.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ copy(jar, next + files[i].getName(), files[i], macro, extra);
+ }
+ } else {
+ if (from.exists()) {
+ if (macro != null) {
+ String content = read(from);
+ content = macro.process(content);
+ Resource resource = new EmbeddedResource(content
+ .getBytes("UTF-8"), from.lastModified());
+
+ String x = (String) extra.get("extra");
+ if (x != null)
+ resource.setExtra(x);
+ jar.putResource(path, resource);
+ } else
+ jar.putResource(path, new FileResource(from));
+ } else {
+ error("Input file does not exist: " + from);
+ }
+ }
+ }
+
+ private String read(File from) throws Exception {
+ long size = from.length();
+ byte[] buffer = new byte[(int) size];
+ FileInputStream in = new FileInputStream(from);
+ in.read(buffer);
+ in.close();
+ return new String(buffer, "UTF-8");
+ }
+
+ private String getName(String where) {
+ int n = where.lastIndexOf('/');
+ if (n < 0)
+ return where;
+
+ return where.substring(n + 1);
+ }
+
+ public void setSourcepath(File[] files) {
+ sourcePath = files;
+ }
+
+ /**
+ * Create a POM reseource for Maven containing as much information as
+ * possible from the manifest.
+ *
+ * @param output
+ * @param builder
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void doPom(Jar dot) throws FileNotFoundException, IOException {
+ {
+ Manifest manifest = dot.getManifest();
+ String name = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_NAME);
+ String description = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_DESCRIPTION);
+ String docUrl = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_DOCURL);
+ String version = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_VERSION);
+ String bundleVendor = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_VENDOR);
+ ByteArrayOutputStream s = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(s);
+ String bsn = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_SYMBOLICNAME);
+ String licenses = manifest.getMainAttributes().getValue(
+ BUNDLE_LICENSE);
+
+ if (bsn == null) {
+ errors
+ .add("Can not create POM unless Bundle-SymbolicName is set");
+ return;
+ }
+
+ bsn = bsn.trim();
+ int n = bsn.lastIndexOf('.');
+ if (n <= 0) {
+ errors
+ .add("Can not create POM unless Bundle-SymbolicName contains a .");
+ ps.close();
+ s.close();
+ return;
+ }
+ String groupId = bsn.substring(0, n);
+ String artifactId = bsn.substring(n + 1);
+ ps
+ .println("<project xmlns='http://maven.apache.org/POM/4.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd'>");
+ ps.println(" <modelVersion>4.0.0</modelVersion>");
+ ps.println(" <groupId>" + groupId + "</groupId>");
+
+ n = artifactId.indexOf(';');
+ if (n > 0)
+ artifactId = artifactId.substring(0, n).trim();
+
+ ps.println(" <artifactId>" + artifactId + "</artifactId>");
+ ps.println(" <version>" + version + "</version>");
+ if (description != null) {
+ ps.println(" <description>");
+ ps.print(" ");
+ ps.println(description);
+ ps.println(" </description>");
+ }
+ if (name != null) {
+ ps.print(" <name>");
+ ps.print(name);
+ ps.println("</name>");
+ }
+ if (docUrl != null) {
+ ps.print(" <url>");
+ ps.print(docUrl);
+ ps.println("</url>");
+ }
+
+ if (bundleVendor != null) {
+ Matcher m = NAME_URL.matcher(bundleVendor);
+ String namePart = bundleVendor;
+ String urlPart = null;
+ if (m.matches()) {
+ namePart = m.group(1);
+ urlPart = m.group(2);
+ }
+ ps.println(" <organization>");
+ ps.print(" <name>");
+ ps.print(namePart.trim());
+ ps.println("</name>");
+ if (urlPart != null) {
+ ps.print(" <url>");
+ ps.print(urlPart.trim());
+ ps.println("</url>");
+ }
+ ps.println(" </organization>");
+ }
+ if (licenses != null) {
+ ps.println(" <licenses>");
+ Map map = parseHeader(licenses);
+ for (Iterator e = map.entrySet().iterator(); e.hasNext();) {
+ Map.Entry entry = (Map.Entry) e.next();
+ ps.println(" <license>");
+ Map values = (Map) entry.getValue();
+ print(ps, values, "name", "name", (String) values
+ .get("url"));
+ print(ps, values, "url", "url", null);
+ print(ps, values, "distribution", "distribution", "repo");
+ ps.println(" </license>");
+ }
+ ps.println(" </licenses>");
+ }
+ ps.println("</project>");
+ ps.close();
+ s.close();
+ dot
+ .putResource("pom.xml", new EmbeddedResource(s
+ .toByteArray(), 0));
+ }
+ }
+
+ /**
+ * NEW
+ *
+ */
+ void doPrebuild(Jar dot, Map clauses) {
+ for (Iterator i = clauses.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = (Map.Entry) i.next();
+ String name = (String) entry.getKey();
+ Map args = (Map) entry.getValue();
+ File file = getFile(base, name);
+ File dir = file.getParentFile();
+ if (!dir.exists())
+ error("No directory for prebuild: " + file.getAbsolutePath());
+ else {
+ Instruction instr = Instruction.getPattern(file.getName());
+ File[] children = dir.listFiles();
+ for (int c = 0; c < children.length; c++) {
+ File child = children[c];
+ if (instr.matches(child.getName()) && !instr.isNegated()) {
+ try {
+ progress("Prebuilding "+child);
+ doPrebuild(dot, child, args);
+ } catch (Exception e) {
+ error("Can not build: "+child);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void doPrebuild(Jar dot, File f, Map args) throws Exception {
+ Builder builder = new Builder();
+ builder.setBase(base);
+ builder.setProperties(f);
+
+ Properties p = new Properties();
+ p.putAll(getProperties());
+ p.keySet().removeAll(builder.getProperties().keySet());
+ builder.getProperties().putAll(p);
+
+ Jar jar = builder.build();
+ String path = (String) args.get("path");
+ if (path != null)
+ path = f.getName();
+
+ dot.putResource(path, new JarResource(jar));
+ }
+
+ /**
+ * Utility function to print a tag from a map
+ *
+ * @param ps
+ * @param values
+ * @param string
+ * @param tag
+ * @param object
+ */
+ private void print(PrintStream ps, Map values, String string, String tag,
+ String object) {
+ String value = (String) values.get(string);
+ if (value == null)
+ value = object;
+ if (value == null)
+ return;
+ ps.println(" <" + tag + ">" + value.trim() + "</" + tag + ">");
+ }
+
+ public void close() {
+ super.close();
+ }
+}