bndlib: A Swiss Army Knife for OSGi providing comprehensive bundle manipulation and analysis capabilities
—
Semantic versioning analysis based on API changes between bundle versions, enabling automated compatibility assessment and version recommendation.
Implementation for comparing APIs between JAR versions and detecting breaking changes.
/**
* Implementation for comparing APIs between JAR versions
*/
public class DiffImpl implements Diff {
/** Compare two JAR files and generate diff */
public Diff diff(Jar newer, Jar older) throws Exception;
/** Create API tree from JAR */
public Tree tree(Jar jar) throws Exception;
/** Get diff instructions for filtering */
public Instructions getDiffInstructions();
/** Set diff instructions */
public void setDiffInstructions(Instructions instructions);
/** Check if change is ignored */
public boolean isIgnored(Element element);
/** Get diff reporter */
public Reporter getReporter();
/** Set diff reporter */
public void setReporter(Reporter reporter);
}
/**
* Represents a difference between API elements
*/
public interface Diff {
/** Get diff type */
public Delta getDelta();
/** Get element type */
public Element getType();
/** Get element name */
public String getName();
/** Get children diffs */
public Collection<? extends Diff> getChildren();
/** Get newer element */
public Element getNewer();
/** Get older element */
public Element getOlder();
}
/**
* Types of changes detected
*/
public enum Delta {
IGNORE, // Change should be ignored
UNCHANGED, // No change
MINOR, // Minor change (backward compatible)
MAJOR, // Major change (breaking)
MICRO, // Micro change (implementation only)
ADDED, // New element added
REMOVED // Element removed
}Usage Examples:
import aQute.bnd.differ.DiffImpl;
import aQute.bnd.differ.Diff;
import aQute.bnd.differ.Delta;
// Compare two versions of a bundle
Jar newer = new Jar(new File("mybundle-2.0.0.jar"));
Jar older = new Jar(new File("mybundle-1.0.0.jar"));
DiffImpl differ = new DiffImpl();
Diff diff = differ.diff(newer, older);
// Analyze changes
analyzeDiff(diff, 0);
// Helper method to analyze diff recursively
private void analyzeDiff(Diff diff, int indent) {
String prefix = " ".repeat(indent);
Delta delta = diff.getDelta();
System.out.println(prefix + delta + ": " + diff.getType() + " " + diff.getName());
// Process child diffs
for (Diff child : diff.getChildren()) {
analyzeDiff(child, indent + 1);
}
}Performs semantic versioning analysis based on API changes and recommends appropriate version increments.
/**
* Performs semantic versioning analysis based on API changes
*/
public class Baseline {
/** Create baseline analyzer */
public Baseline(Reporter reporter, Instructions diffignore);
/** Perform baseline analysis */
public Set<Info> baseline(Jar jar, Jar baseline, Instructions instructions) throws Exception;
/** Get suggested version */
public Version getSuggestedVersion();
/** Check if baseline passed */
public boolean isOk();
/** Get baseline errors */
public List<String> getErrors();
/** Get baseline warnings */
public List<String> getWarnings();
}
/**
* Baseline information for a package
*/
public class Info implements Comparable<Info> {
/** Get package name */
public String packageName;
/** Get suggested version */
public Version suggestedVersion;
/** Get newer version */
public Version newerVersion;
/** Get older version */
public Version olderVersion;
/** Get version change type */
public Delta delta;
/** Get warnings */
public List<String> warnings;
/** Check if version is too low */
public boolean mismatch;
/** Get attributes */
public Attrs attributes;
}Usage Examples:
import aQute.bnd.differ.Baseline;
import aQute.bnd.differ.Baseline.Info;
import aQute.bnd.build.Instructions;
// Perform baseline analysis
Jar current = new Jar(new File("current.jar"));
Jar previous = new Jar(new File("previous.jar"));
Reporter reporter = new ConsoleReporter();
Instructions diffIgnore = new Instructions();
diffIgnore.put("*impl*", new Attrs()); // Ignore implementation packages
Baseline baseline = new Baseline(reporter, diffIgnore);
Set<Info> results = baseline.baseline(current, previous, new Instructions());
// Analyze results
for (Info info : results) {
System.out.println("Package: " + info.packageName);
System.out.println(" Current version: " + info.newerVersion);
System.out.println(" Previous version: " + info.olderVersion);
System.out.println(" Suggested version: " + info.suggestedVersion);
System.out.println(" Change type: " + info.delta);
if (info.mismatch) {
System.out.println(" WARNING: Version too low for changes detected");
}
for (String warning : info.warnings) {
System.out.println(" WARNING: " + warning);
}
}
// Check overall result
if (baseline.isOk()) {
System.out.println("Baseline analysis passed");
System.out.println("Suggested bundle version: " + baseline.getSuggestedVersion());
} else {
System.err.println("Baseline analysis failed");
for (String error : baseline.getErrors()) {
System.err.println("ERROR: " + error);
}
}Classes representing different elements in the API tree for diff analysis.
/**
* Base class for API elements
*/
public abstract class Element {
/** Get element type */
public abstract Type getType();
/** Get element name */
public abstract String getName();
/** Get element key for comparison */
public String getKey();
/** Get element version */
public Version getVersion();
/** Get element attributes */
public Map<String, String> getAttributes();
/** Compare with another element */
public Delta compare(Element other);
}
/**
* Types of API elements
*/
public enum Type {
ROOT,
BUNDLE,
PACKAGE,
CLASS,
INTERFACE,
ANNOTATION,
ENUM,
METHOD,
CONSTRUCTOR,
FIELD,
CONSTANT
}
/**
* Java-specific element for class analysis
*/
public class JavaElement extends Element {
/** Get Java access modifiers */
public int getAccess();
/** Check if element is public */
public boolean isPublic();
/** Check if element is protected */
public boolean isProtected();
/** Check if element is static */
public boolean isStatic();
/** Check if element is final */
public boolean isFinal();
/** Check if element is abstract */
public boolean isAbstract();
/** Get method signature */
public String getSignature();
/** Get return type */
public String getReturnType();
/** Get parameter types */
public String[] getParameterTypes();
/** Get exception types */
public String[] getExceptionTypes();
}Configuration for controlling diff analysis and ignoring specific changes.
/**
* Instructions for controlling diff analysis
*/
public class Instructions extends LinkedHashMap<Instruction, Attrs> {
/** Create empty instructions */
public Instructions();
/** Create from properties */
public Instructions(Properties properties);
/** Check if element matches any instruction */
public boolean matches(String name);
/** Get instruction that matches name */
public Instruction getMatching(String name);
/** Add instruction */
public Instruction put(String pattern, Attrs attributes);
/** Get all patterns */
public Set<String> getPatterns();
}
/**
* Single instruction with pattern and attributes
*/
public class Instruction {
/** Get instruction pattern */
public String getPattern();
/** Get instruction attributes */
public Attrs getAttributes();
/** Check if pattern matches string */
public boolean matches(String value);
/** Check if instruction is literal (no wildcards) */
public boolean isLiteral();
/** Check if instruction is negated */
public boolean isNegated();
}Utilities for comparing repository contents and tracking changes over time.
/**
* Repository difference analyzer
*/
public class RepositoryDiff {
/** Compare two repositories */
public static Diff compareRepositories(Repository newer, Repository older) throws Exception;
/** Find changed bundles between repositories */
public static Map<String, Diff> findChangedBundles(Repository newer, Repository older) throws Exception;
/** Generate compatibility report */
public static CompatibilityReport generateReport(Repository newer, Repository older) throws Exception;
}
/**
* Compatibility report between repository versions
*/
public class CompatibilityReport {
/** Get added bundles */
public Set<String> getAddedBundles();
/** Get removed bundles */
public Set<String> getRemovedBundles();
/** Get changed bundles */
public Map<String, BundleChange> getChangedBundles();
/** Get compatibility summary */
public CompatibilitySummary getSummary();
}
/**
* Change information for a bundle
*/
public class BundleChange {
public String bundleSymbolicName;
public Version oldVersion;
public Version newVersion;
public Delta overallChange;
public Map<String, Delta> packageChanges;
public List<String> breakingChanges;
public List<String> warnings;
}Complete API Comparison Example:
import aQute.bnd.differ.*;
import aQute.bnd.build.Instructions;
// Complete API comparison and baseline workflow
public class APIComparisonWorkflow {
public void performAPIAnalysis(File currentBundle, File previousBundle) throws Exception {
try (Jar current = new Jar(currentBundle);
Jar previous = new Jar(previousBundle)) {
// Step 1: Perform detailed diff analysis
DiffImpl differ = new DiffImpl();
Diff diff = differ.diff(current, previous);
System.out.println("=== API Diff Analysis ===");
printDiffTree(diff, 0);
// Step 2: Perform baseline analysis for version recommendations
Reporter reporter = new Reporter() {
public void error(String msg, Object... args) {
System.err.println("ERROR: " + String.format(msg, args));
}
public void warning(String msg, Object... args) {
System.err.println("WARNING: " + String.format(msg, args));
}
public void progress(float progress, String msg, Object... args) {
System.out.println("PROGRESS: " + String.format(msg, args));
}
};
// Configure what to ignore in baseline
Instructions diffIgnore = new Instructions();
diffIgnore.put("*impl*", new Attrs()); // Ignore impl packages
diffIgnore.put("*.internal.*", new Attrs()); // Ignore internal packages
Baseline baseline = new Baseline(reporter, diffIgnore);
Set<Baseline.Info> baselineResults = baseline.baseline(current, previous, new Instructions());
System.out.println("\n=== Baseline Analysis ===");
for (Baseline.Info info : baselineResults) {
System.out.println("Package: " + info.packageName);
System.out.println(" Previous: " + info.olderVersion + " -> Current: " + info.newerVersion);
System.out.println(" Suggested: " + info.suggestedVersion);
System.out.println(" Change: " + info.delta);
if (info.mismatch) {
System.out.println(" ❌ Version mismatch - version too low for detected changes");
}
for (String warning : info.warnings) {
System.out.println(" ⚠️ " + warning);
}
}
// Step 3: Generate summary and recommendations
System.out.println("\n=== Summary ===");
if (baseline.isOk()) {
System.out.println("✅ Baseline analysis passed");
Version suggested = baseline.getSuggestedVersion();
if (suggested != null) {
System.out.println("📦 Recommended bundle version: " + suggested);
}
} else {
System.out.println("❌ Baseline analysis failed");
for (String error : baseline.getErrors()) {
System.out.println(" " + error);
}
}
// Step 4: Generate compatibility report
generateCompatibilityReport(diff, baselineResults);
}
}
private void printDiffTree(Diff diff, int level) {
String indent = " ".repeat(level);
Delta delta = diff.getDelta();
String icon = getChangeIcon(delta);
System.out.println(indent + icon + " " + diff.getType() + " " + diff.getName());
for (Diff child : diff.getChildren()) {
printDiffTree(child, level + 1);
}
}
private String getChangeIcon(Delta delta) {
switch (delta) {
case ADDED: return "➕";
case REMOVED: return "❌";
case MAJOR: return "💥";
case MINOR: return "🔄";
case MICRO: return "🔧";
case UNCHANGED: return "✅";
default: return "❓";
}
}
private void generateCompatibilityReport(Diff diff, Set<Baseline.Info> baselineResults) {
System.out.println("\n=== Compatibility Report ===");
// Count changes by type
Map<Delta, Integer> changeCounts = new HashMap<>();
countChanges(diff, changeCounts);
System.out.println("Change Summary:");
for (Map.Entry<Delta, Integer> entry : changeCounts.entrySet()) {
if (entry.getValue() > 0) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
}
// Compatibility assessment
boolean hasBreaking = changeCounts.getOrDefault(Delta.MAJOR, 0) > 0 ||
changeCounts.getOrDefault(Delta.REMOVED, 0) > 0;
if (hasBreaking) {
System.out.println("⚠️ BREAKING CHANGES DETECTED");
System.out.println(" This release contains breaking changes that may affect consumers");
} else {
System.out.println("✅ BACKWARD COMPATIBLE");
System.out.println(" This release maintains backward compatibility");
}
}
private void countChanges(Diff diff, Map<Delta, Integer> counts) {
Delta delta = diff.getDelta();
counts.put(delta, counts.getOrDefault(delta, 0) + 1);
for (Diff child : diff.getChildren()) {
countChanges(child, counts);
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-biz-a-qute-bnd--biz-a-qute-bndlib