package aQute.bnd.main;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.jar.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import aQute.bnd.build.*;
import aQute.bnd.differ.*;
import aQute.bnd.differ.Baseline.BundleInfo;
import aQute.bnd.differ.Baseline.Info;
import aQute.bnd.header.*;
import aQute.bnd.osgi.*;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.service.diff.*;
import aQute.bnd.version.*;
import aQute.lib.collections.*;
import aQute.lib.getopt.*;
import aQute.lib.tag.*;
/**
* Implements commands to maintain the Package versions db.
*/
public class BaselineCommands {
static TransformerFactory transformerFactory = TransformerFactory.newInstance();
final bnd bnd;
final Baseline baseline;
final DiffPluginImpl differ = new DiffPluginImpl();
final Collection<String> SKIP_HEADERS = Arrays.asList(Constants.CREATED_BY, Constants.BND_LASTMODIFIED,
Constants.BUNDLE_MANIFESTVERSION, "Manifest-Version",
Constants.TOOL);
BaselineCommands(bnd bnd) throws IOException {
this.bnd = bnd;
this.baseline = new Baseline(bnd, differ);
}
@Description("Compare a newer bundle to a baselined bundle and provide versioning advice")
@Arguments(arg = {
"[newer jar]", "[older jar]"
})
interface baseLineOptions extends Options {
@Description("Output file with fixup info")
String fixup();
@Description("Show any differences")
boolean diff();
@Description("Be quiet, only report errors")
boolean quiet();
@Description("Show all, also unchanged")
boolean all();
}
/**
* Compare
*/
@Description("Compare a newer bundle to a baselined bundle and provide versioning advice. If no parameters are given, and there "
+ "is a local project, then we use the projects current build and the baseline jar in the release repo.")
public void _baseline(baseLineOptions opts) throws Exception {
List<String> args = opts._();
if (args.size() == 0) {
Project project = bnd.getProject();
if (project != null) {
for (Builder b : project.getSubBuilders()) {
ProjectBuilder pb = (ProjectBuilder) b;
Jar older = pb.getBaselineJar();
if ( older == null) {
bnd.error("No baseline JAR available. Did you set " + Constants.BASELINE);
return;
}
try {
pb.setProperty(Constants.BASELINE, ""); // do not do baselining in build
// make sure disabling is after getting the baseline jar
Jar newer = pb.build();
try {
differ.setIgnore(pb.getProperty(Constants.DIFFIGNORE));
baseline(opts, newer, older);
bnd.getInfo(b);
}
finally {
newer.close();
}
}
finally {
older.close();
}
}
bnd.getInfo(project);
return;
}
}
if (args.size() != 2) {
throw new IllegalArgumentException("Accepts only two argument (<jar>)");
}
File newer = bnd.getFile(args.remove(0));
if (!newer.isFile())
throw new IllegalArgumentException("Not a valid newer input file: " + newer);
File older = bnd.getFile(args.remove(0));
if (!older.isFile())
throw new IllegalArgumentException("Not a valid older input file: " + older);
Jar nj = new Jar(newer);
Jar oj = new Jar(older);
baseline(opts, nj, oj);
}
private void baseline(baseLineOptions opts, Jar newer, Jar older) throws FileNotFoundException,
UnsupportedEncodingException, IOException, Exception {
PrintStream out = null;
if (opts.fixup() != null) {
out = new PrintStream(bnd.getFile(opts.fixup()), "UTF-8");
}
Set<Info> infos = baseline.baseline(newer, older, null);
BundleInfo bundleInfo = baseline.getBundleInfo();
Info[] sorted = infos.toArray(new Info[infos.size()]);
Arrays.sort(sorted, new Comparator<Info>() {
public int compare(Info o1, Info o2) {
return o1.packageName.compareTo(o2.packageName);
}
});
if (!opts.quiet()) {
bnd.out.printf("===============================================================%n%s %s %s-%s", bundleInfo.mismatch ? '*' : ' ', bundleInfo.bsn, newer.getVersion(), older.getVersion());
if (bundleInfo.mismatch && bundleInfo.suggestedVersion != null)
bnd.out.printf(" suggests %s", bundleInfo.suggestedVersion);
bnd.out.printf("%n===============================================================%n");
boolean hadHeader = false;
for (Info info : sorted) {
if (info.packageDiff.getDelta() != Delta.UNCHANGED || opts.all()) {
if (!hadHeader) {
bnd.out.printf(" %-50s %-10s %-10s %-10s %-10s %-10s%n", "Package", "Delta", "New", "Old",
"Suggest", "If Prov.");
hadHeader = true;
}
bnd.out.printf("%s %-50s %-10s %-10s %-10s %-10s %-10s%n", info.mismatch ? '*' : ' ',
info.packageName, //
info.packageDiff.getDelta(), //
info.newerVersion, //
info.olderVersion.equals(Version.LOWEST) ? "-": info.olderVersion,//
info.suggestedVersion.compareTo(info.newerVersion) <= 0 ? "ok" : info.suggestedVersion, //
info.suggestedIfProviders == null ? "-" : info.suggestedIfProviders);
}
}
}
if (out != null) {
// Create a fixup file
Manifest manifest = newer.getManifest();
if (manifest == null)
manifest = new Manifest();
for (Map.Entry<Object,Object> e : manifest.getMainAttributes().entrySet()) {
String key = e.getKey().toString();
if (!SKIP_HEADERS.contains(key)) {
if (!Constants.EXPORT_PACKAGE.equals(key)) {
out.printf("%-40s = ", key);
String value = (String) e.getValue();
out.append(value);
}
out.println();
}
}
doExportPackage(sorted, out);
out.close();
}
}
/**
* Print out the packages from spec jars and check in which ees they appear.
* Example
*
* <pre>
* package overview -ee j2se-1.6.0 -ee j2se-1.5.0 -ee j2ee-1.4.0 javax.activation-1.1.jar
* </pre>
*/
@Description("Print out the packages from spec jars and check in which ees they appear. Very specific. For example, schema ee.j2se-1.6.0 ee.j2se-1.5.0 ee.j2ee-1.4.0")
interface schemaOptions extends Options {
@Description("Output file")
String output(String deflt);
@Description("Specify an XSL file for pretty printing")
String xsl();
}
class PSpec implements Comparable<PSpec> {
String packageName;
Version version;
int id;
public Attrs attrs;
public Tree tree;
public Attrs uses = new Attrs();
public int compareTo(PSpec o) {
return version.compareTo(o.version);
}
}
/**
* Create a schema of a set of jars outling the packages and their versions.
* This will create a list of packages with multiple versions, link to their
* specifications, and the deltas between versions.
*
* <pre>
* bnd package schema <file.jar>*
* </pre>
*
* @param opts
* @throws Exception
*/
public void _schema(schemaOptions opts) throws Exception {
MultiMap<String,PSpec> map = new MultiMap<String,PSpec>();
Tag top = new Tag("jschema");
int n = 1000;
for (String spec : opts._()) {
File f = bnd.getFile(spec);
if (!f.isFile()) {
bnd.messages.NoSuchFile_(f);
} else {
// For each specification jar we found
bnd.trace("spec %s", f);
Jar jar = new Jar(f); // spec
Manifest m = jar.getManifest();
Attributes main = m.getMainAttributes();
Tag specTag = new Tag(top, "specification");
specTag.addAttribute("jar", spec);
specTag.addAttribute("name", main.getValue("Specification-Name"));
specTag.addAttribute("title", main.getValue("Specification-Title"));
specTag.addAttribute("jsr", main.getValue("Specification-JSR"));
specTag.addAttribute("url", main.getValue("Specification-URL"));
specTag.addAttribute("version", main.getValue("Specification-Version"));
specTag.addAttribute("vendor", main.getValue("Specification-Vendor"));
specTag.addAttribute("id", n);
specTag.addContent(main.getValue(Constants.BUNDLE_DESCRIPTION));
Parameters exports = OSGiHeader.parseHeader(m.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
// Create a map with versions. Ensure import ranges overwrite
// the
// exported versions
Parameters versions = new Parameters();
versions.putAll(exports);
versions.putAll(OSGiHeader.parseHeader(m.getMainAttributes().getValue(Constants.IMPORT_PACKAGE)));
Analyzer analyzer = new Analyzer();
analyzer.setJar(jar);
analyzer.analyze();
Tree tree = differ.tree(analyzer);
for (Entry<String,Attrs> entry : exports.entrySet()) {
// For each exported package in the specification JAR
Attrs attrs = entry.getValue();
String packageName = entry.getKey();
PackageRef packageRef = analyzer.getPackageRef(packageName);
String version = attrs.get(Constants.VERSION_ATTRIBUTE);
PSpec pspec = new PSpec();
pspec.packageName = packageName;
pspec.version = new Version(version);
pspec.id = n;
pspec.attrs = attrs;
pspec.tree = tree;
Collection<PackageRef> uses = analyzer.getUses().get(packageRef);
if (uses != null) {
for (PackageRef x : uses) {
if (x.isJava())
continue;
String imp = x.getFQN();
if (imp.equals(packageName))
continue;
String v = null;
if (versions.containsKey(imp))
v = versions.get(imp).get(Constants.VERSION_ATTRIBUTE);
pspec.uses.put(imp, v);
}
}
map.add(packageName, pspec);
}
jar.close();
n++;
}
}
// We now gather all the information about all packages in the map.
// Next phase is generating the XML. Sorting the packages is
// important because XSLT is brain dead.
SortedList<String> names = new SortedList<String>(map.keySet());
Tag packagesTag = new Tag(top, "packages");
Tag baselineTag = new Tag(top, "baseline");
for (String pname : names) {
// For each distinct package name
SortedList<PSpec> specs = new SortedList<PSpec>(map.get(pname));
PSpec older = null;
Parameters olderExport = null;
for (PSpec newer : specs) {
// For each package in the total set
Tag pack = new Tag(packagesTag, "package");
pack.addAttribute("name", newer.packageName);
pack.addAttribute("version", newer.version);
pack.addAttribute("spec", newer.id);
Parameters newerExport = new Parameters();
newerExport.put(pname, newer.attrs);
if (older != null) {
String compareId = newer.packageName + "-" + newer.id + "-" + older.id;
pack.addAttribute("delta", compareId);
bnd.trace(" newer=%s older=%s", newerExport, olderExport);
Set<Info> infos = baseline.baseline(newer.tree, newerExport, older.tree, olderExport,
new Instructions(pname));
for (Info info : infos) {
Tag tag = getTag(info);
tag.addAttribute("id", compareId);
tag.addAttribute("newerSpec", newer.id);
tag.addAttribute("olderSpec", older.id);
baselineTag.addContent(tag);
}
older.tree = null;
older.attrs = null;
older = newer;
}
//
// XRef, show the used packages for this package
//
for (Entry<String,String> uses : newer.uses.entrySet()) {
Tag reference = new Tag(pack, "import");
reference.addAttribute("name", uses.getKey());
reference.addAttribute("version", uses.getValue());
}
older = newer;
olderExport = newerExport;
}
}
String o = opts.output("schema.xml");
File of = bnd.getFile(o);
File pof = of.getParentFile();
if (!pof.exists() && !pof.mkdirs()) {
throw new IOException("Could not create directory " + pof);
}
OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(of), "UTF-8");
try {
PrintWriter pw = new PrintWriter(fw);
try {
pw.println("<?xml version='1.0'?>");
top.print(0, pw);
}
finally {
pw.close();
}
}
finally {
fw.close();
}
if (opts.xsl() != null) {
URL home = bnd.getBase().toURI().toURL();
URL xslt = new URL(home, opts.xsl());
String path = of.getAbsolutePath();
if (path.endsWith(".xml"))
path = path.substring(0, path.length() - 4);
path = path + ".html";
File html = new File(path);
bnd.trace("xslt %s %s %s %s", xslt, of, html, html.exists());
FileOutputStream out = new FileOutputStream(html);
try {
Transformer transformer = transformerFactory.newTransformer(new StreamSource(xslt.openStream()));
transformer.transform(new StreamSource(of), new StreamResult(out));
}
finally {
out.close();
}
}
}
private Tag getTag(Info info) {
Tag tag = new Tag("info");
tag.addAttribute("name", info.packageName);
tag.addAttribute("newerVersion", info.newerVersion);
tag.addAttribute("olderVersion", info.olderVersion);
tag.addAttribute("suggestedVersion", info.suggestedVersion);
tag.addAttribute("suggestedIfProviders", info.suggestedIfProviders);
tag.addAttribute("mismatch", info.mismatch);
tag.addAttribute("warning", info.warning);
StringBuilder sb = new StringBuilder();
if (info.packageDiff.getDelta() == Delta.UNCHANGED)
tag.addAttribute("equals", "true");
else {
traverseTag(sb, info.packageDiff, "");
String s = sb.toString().trim();
if (s.length() != 0) {
Tag d = new Tag(tag, "diff", s);
d.setCDATA();
}
}
if (info.providers != null)
for (String provider : info.providers) {
Tag p = new Tag(tag, "provider");
p.addAttribute("provider", provider);
}
return tag;
}
private void traverseTag(StringBuilder sb, Diff diff, String indent) {
sb.append(indent);
sb.append(diff.toString().trim().replace('\n', ' '));
sb.append("\n");
if (diff.getDelta() == Delta.ADDED || diff.getDelta() == Delta.REMOVED)
return;
for (Diff child : diff.getChildren()) {
if (child.getDelta() != Delta.UNCHANGED && child.getDelta() != Delta.IGNORED)
traverseTag(sb, child, indent + " ");
}
}
/**
* @param exports
* @param out
* @throws IOException
*/
public void doExportPackage(Info[] infos, PrintStream out) throws IOException {
out.printf("# Suggested versions\n%-40s = ", Constants.EXPORT_PACKAGE);
String del = "";
for (Info info : infos) {
out.append(del);
out.printf("\\\n ");
out.append(info.packageName);
info.attributes.put(Constants.VERSION_ATTRIBUTE, info.suggestedVersion.toString());
for (Map.Entry<String,String> clause : info.attributes.entrySet()) {
if (clause.getKey().equals(Constants.USES_DIRECTIVE))
continue;
out.append(";\\\n ");
out.append(clause.getKey());
out.append("=");
Processor.quote(out, clause.getValue());
}
if (info.providers != null && !info.providers.isEmpty()) {
out.append(";\\\n " + Constants.PROVIDER_TYPE_DIRECTIVE + "=\"");
String del2 = "";
for (String part : info.providers) {
out.append(del2);
out.append("\\\n ");
out.append(part);
del2 = ",";
}
out.append("\"");
}
del = ",";
}
}
}