blob: e3a130885d91acb63ce94e14ac9a89c0e6b68fc4 [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.bnd.differ;
2
3import static aQute.bnd.service.diff.Delta.*;
4
5import java.io.*;
6import java.util.*;
7import java.util.jar.*;
8
9import aQute.bnd.service.diff.*;
10import aQute.bnd.service.diff.Tree.Data;
11import aQute.lib.hex.*;
12import aQute.lib.io.*;
13import aQute.lib.osgi.*;
14import aQute.libg.cryptography.*;
15import aQute.libg.header.*;
16
17
18/**
19 * This Diff Plugin Implementation will compare JARs for their API (based on the
20 * Bundle Class Path and exported packages), the Manifest, and the resources.
21 * The Differences are represented in a {@link Diff} tree.
22 */
23public class DiffPluginImpl implements Differ {
24
25 /**
26 * Headers that are considered major enough to parse according to spec and
27 * compare their constituents
28 */
29 final static Set<String> MAJOR_HEADERS = new TreeSet<String>(
30 String.CASE_INSENSITIVE_ORDER);
31
32 /**
33 * Headers that are considered not major enough to be considered
34 */
35 final static Set<String> IGNORE_HEADERS = new TreeSet<String>(
36 String.CASE_INSENSITIVE_ORDER);
37
38 static {
39 MAJOR_HEADERS.add(Constants.EXPORT_PACKAGE);
40 MAJOR_HEADERS.add(Constants.IMPORT_PACKAGE);
41 MAJOR_HEADERS.add(Constants.REQUIRE_BUNDLE);
42 MAJOR_HEADERS.add(Constants.FRAGMENT_HOST);
43 MAJOR_HEADERS.add(Constants.BUNDLE_SYMBOLICNAME);
44 MAJOR_HEADERS.add(Constants.BUNDLE_LICENSE);
45 MAJOR_HEADERS.add(Constants.BUNDLE_NATIVECODE);
46 MAJOR_HEADERS.add(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
47 MAJOR_HEADERS.add(Constants.DYNAMICIMPORT_PACKAGE);
48
49 IGNORE_HEADERS.add(Constants.TOOL);
50 IGNORE_HEADERS.add(Constants.BND_LASTMODIFIED);
51 IGNORE_HEADERS.add(Constants.CREATED_BY);
52 }
53
54 /**
55 *
56 * @see aQute.bnd.service.diff.Differ#diff(aQute.lib.resource.Jar,
57 * aQute.lib.resource.Jar)
58 */
59 public Tree tree(File newer) throws Exception {
60 Jar jnewer = new Jar(newer);
61 try {
62 return tree(jnewer);
63 } finally {
64 jnewer.close();
65 }
66 }
67
68 /**
69 *
70 * @see aQute.bnd.service.diff.Differ#diff(aQute.lib.resource.Jar,
71 * aQute.lib.resource.Jar)
72 */
73 public Tree tree(Jar newer) throws Exception {
74 Analyzer anewer = new Analyzer();
75 try {
76 anewer.setJar(newer);
77 return tree(anewer);
78 } finally {
79 anewer.setJar((Jar) null);
80 anewer.close();
81 }
82 }
83
84 public Tree tree(Analyzer newer) throws Exception {
85 return bundleElement(newer);
86 }
87
88 /**
89 * Create an element representing a bundle from the Jar.
90 *
91 * @param infos
92 * @param jar
93 * The Jar to be analyzed
94 * @return the elements that should be compared
95 * @throws Exception
96 */
97 private Element bundleElement(Analyzer analyzer) throws Exception {
98 List<Element> result = new ArrayList<Element>();
99
100 Manifest manifest = analyzer.getJar().getManifest();
101
102 if (manifest != null) {
103 result.add(JavaElement.getAPI(analyzer));
104 result.add(manifestElement(manifest));
105 }
106 result.add(resourcesElement(analyzer.getJar()));
107 return new Element(Type.BUNDLE, analyzer.getJar().getName(), result, CHANGED, CHANGED,
108 null);
109 }
110
111 /**
112 * Create an element representing all resources in the JAR
113 *
114 * @param jar
115 * @return
116 * @throws Exception
117 */
118 private Element resourcesElement(Jar jar) throws Exception {
119 List<Element> resources = new ArrayList<Element>();
120 for (Map.Entry<String, Resource> entry : jar.getResources().entrySet()) {
121
122 InputStream in = entry.getValue().openInputStream();
123 try {
124 Digester<SHA1> digester = SHA1.getDigester();
125 IO.copy(in, digester);
126 String value = Hex.toHexString(digester.digest().digest());
127 resources
128 .add(new Element(Type.RESOURCE, entry.getKey()+"="+value, null, CHANGED, CHANGED, null));
129 } finally {
130 in.close();
131 }
132 }
133 return new Element(Type.RESOURCES, "<resources>", resources, CHANGED, CHANGED, null);
134 }
135
136
137
138
139 /**
140 * Create an element for each manifest header. There are
141 * {@link #IGNORE_HEADERS} and {@link #MAJOR_HEADERS} that will be treated
142 * differently.
143 *
144 * @param manifest
145 * @return
146 */
147
148 private Element manifestElement(Manifest manifest) {
149 List<Element> result = new ArrayList<Element>();
150
151 for (Object key : manifest.getMainAttributes().keySet()) {
152 String header = key.toString();
153 String value = manifest.getMainAttributes().getValue(header);
154 if (IGNORE_HEADERS.contains(header))
155 continue;
156
157 if (MAJOR_HEADERS.contains(header)) {
158 Parameters clauses = OSGiHeader.parseHeader(value);
159 Collection<Element> clausesDef = new ArrayList<Element>();
160 for (Map.Entry<String, Attrs> clause : clauses.entrySet()) {
161 Collection<Element> parameterDef = new ArrayList<Element>();
162 for (Map.Entry<String, String> parameter : clause.getValue().entrySet()) {
163 parameterDef.add(new Element(Type.PARAMETER, parameter.getKey() + ":" + parameter
164 .getValue(), null, CHANGED, CHANGED, null));
165 }
166 clausesDef.add(new Element(Type.CLAUSE, clause.getKey(), parameterDef,
167 CHANGED, CHANGED, null));
168 }
169 result.add(new Element(Type.HEADER, header, clausesDef, CHANGED, CHANGED, null));
170 } else {
171 result.add(new Element(Type.HEADER, header +":"+ value, null,CHANGED, CHANGED, null));
172 }
173 }
174 return new Element(Type.MANIFEST, "<manifest>", result, CHANGED, CHANGED, null);
175 }
176
177 public Tree deserialize(Data data) throws Exception {
178 return new Element(data);
179 }
180
181}