1 | /* |
2 | * Copyright (c) 2001-2009, Jean Tessier |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * |
9 | * * Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * |
12 | * * Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in the |
14 | * documentation and/or other materials provided with the distribution. |
15 | * |
16 | * * Neither the name of Jean Tessier nor the names of his contributors |
17 | * may be used to endorse or promote products derived from this software |
18 | * without specific prior written permission. |
19 | * |
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR |
24 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
25 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
26 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
27 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | */ |
32 | |
33 | package com.jeantessier.diff; |
34 | |
35 | import java.util.*; |
36 | |
37 | import org.apache.log4j.*; |
38 | |
39 | import com.jeantessier.classreader.*; |
40 | |
41 | public class DifferencesFactory { |
42 | private Classfile oldClass; |
43 | private Classfile newClass; |
44 | |
45 | private DifferenceStrategy strategy; |
46 | |
47 | /** |
48 | * For tests only. |
49 | */ |
50 | DifferencesFactory() { |
51 | this(new APIDifferenceStrategy(new CodeDifferenceStrategy())); |
52 | } |
53 | |
54 | public DifferencesFactory(DifferenceStrategy strategy) { |
55 | this.strategy = strategy; |
56 | } |
57 | |
58 | public Differences createProjectDifferences(String name, String oldVersion, PackageMapper oldPackages, String newVersion, PackageMapper newPackages) { |
59 | Logger.getLogger(getClass()).debug("Begin " + name + " (" + oldVersion + " -> " + newVersion + ")"); |
60 | |
61 | ProjectDifferences projectDifferences = new ProjectDifferences(name, oldVersion, newVersion); |
62 | |
63 | Logger.getLogger(getClass()).debug(" Collecting packages ..."); |
64 | |
65 | Collection<String> packageNames = new TreeSet<String>(); |
66 | packageNames.addAll(oldPackages.getPackageNames()); |
67 | packageNames.addAll(newPackages.getPackageNames()); |
68 | |
69 | Logger.getLogger(getClass()).debug(" Diff'ing packages ..."); |
70 | |
71 | for (String packageName : packageNames) { |
72 | Map<String, Classfile> oldPackage = oldPackages.getPackage(packageName); |
73 | if (oldPackage == null) { |
74 | oldPackage = Collections.emptyMap(); |
75 | } |
76 | |
77 | Map<String, Classfile> newPackage = newPackages.getPackage(packageName); |
78 | if (newPackage == null) { |
79 | newPackage = Collections.emptyMap(); |
80 | } |
81 | |
82 | if (strategy.isPackageDifferent(oldPackage, newPackage)) { |
83 | projectDifferences.getPackageDifferences().add(createPackageDifferences(packageName, oldPackage, newPackage)); |
84 | } |
85 | } |
86 | |
87 | Logger.getLogger(getClass()).debug("End " + name + " (" + oldVersion + " -> " + newVersion + ")"); |
88 | |
89 | return projectDifferences; |
90 | } |
91 | |
92 | public Differences createPackageDifferences(String name, Map<String, Classfile> oldPackage, Map<String, Classfile> newPackage) { |
93 | Logger.getLogger(getClass()).debug("Begin " + name); |
94 | |
95 | PackageDifferences packageDifferences = new PackageDifferences(name, oldPackage, newPackage); |
96 | |
97 | if (oldPackage != null && !oldPackage.isEmpty() && newPackage != null && !newPackage.isEmpty()) { |
98 | Logger.getLogger(getClass()).debug(" Diff'ing classes ..."); |
99 | |
100 | Collection<String> classNames = new TreeSet<String>(); |
101 | classNames.addAll(oldPackage.keySet()); |
102 | classNames.addAll(newPackage.keySet()); |
103 | |
104 | for (String className : classNames) { |
105 | Classfile oldClass = oldPackage.get(className); |
106 | Classfile newClass = newPackage.get(className); |
107 | |
108 | if (strategy.isClassDifferent(oldClass, newClass)) { |
109 | packageDifferences.getClassDifferences().add(createClassDifferences(className, oldClass, newClass)); |
110 | } |
111 | } |
112 | |
113 | Logger.getLogger(getClass()).debug(" " + name + " has " + packageDifferences.getClassDifferences().size() + " class(es) that changed."); |
114 | } |
115 | |
116 | Logger.getLogger(getClass()).debug("End " + name); |
117 | |
118 | return packageDifferences; |
119 | } |
120 | |
121 | public Differences createClassDifferences(String name, Classfile oldClass, Classfile newClass) { |
122 | Logger.getLogger(getClass()).debug("Begin " + name); |
123 | |
124 | ClassDifferences classDifferences; |
125 | if (((oldClass != null) && oldClass.isInterface()) || ((newClass != null) && newClass.isInterface())) { |
126 | classDifferences = new InterfaceDifferences(name, oldClass, newClass); |
127 | } else { |
128 | classDifferences = new ClassDifferences(name, oldClass, newClass); |
129 | } |
130 | |
131 | if (!classDifferences.isRemoved() && !classDifferences.isNew() && strategy.isDeclarationModified(oldClass, newClass)) { |
132 | classDifferences.setDeclarationModified(true); |
133 | } |
134 | |
135 | Differences result = classDifferences; |
136 | |
137 | this.oldClass = oldClass; |
138 | this.newClass = newClass; |
139 | |
140 | if (oldClass != null && newClass != null) { |
141 | Logger.getLogger(getClass()).debug(" Collecting fields ..."); |
142 | |
143 | Map<String, String> fieldLevel = new TreeMap<String, String>(); |
144 | |
145 | for (Field_info field : oldClass.getAllFields()) { |
146 | fieldLevel.put(field.getName(), field.getFullSignature()); |
147 | } |
148 | |
149 | for (Field_info field : newClass.getAllFields()) { |
150 | fieldLevel.put(field.getName(), field.getFullSignature()); |
151 | } |
152 | |
153 | Logger.getLogger(getClass()).debug(" Diff'ing fields ..."); |
154 | |
155 | for (Map.Entry<String, String> fieldEntry : fieldLevel.entrySet()) { |
156 | Field_info oldField = oldClass.getField(fieldEntry.getKey()); |
157 | Field_info newField = newClass.getField(fieldEntry.getKey()); |
158 | |
159 | if (strategy.isFieldDifferent(oldField, newField)) { |
160 | classDifferences.getFeatureDifferences().add(createFeatureDifferences(fieldEntry.getValue(), oldField, newField)); |
161 | } |
162 | } |
163 | |
164 | Logger.getLogger(getClass()).debug(" Collecting methods ..."); |
165 | |
166 | Map<String, String> methodLevel = new TreeMap<String, String>(); |
167 | |
168 | for (Method_info method : oldClass.getAllMethods()) { |
169 | methodLevel.put(method.getSignature(), method.getFullSignature()); |
170 | } |
171 | |
172 | for (Method_info method : newClass.getAllMethods()) { |
173 | methodLevel.put(method.getSignature(), method.getFullSignature()); |
174 | } |
175 | |
176 | Logger.getLogger(getClass()).debug(" Diff'ing methods ..."); |
177 | |
178 | for (Map.Entry<String, String> methodEntry : methodLevel.entrySet()) { |
179 | Method_info oldMethod = oldClass.getMethod(methodEntry.getKey()); |
180 | Method_info newMethod = newClass.getMethod(methodEntry.getKey()); |
181 | |
182 | if (strategy.isMethodDifferent(oldMethod, newMethod)) { |
183 | classDifferences.getFeatureDifferences().add(createFeatureDifferences(methodEntry.getValue(), oldMethod, newMethod)); |
184 | } |
185 | } |
186 | |
187 | Logger.getLogger(getClass()).debug(name + " has " + classDifferences.getFeatureDifferences().size() + " feature(s) that changed."); |
188 | |
189 | if (oldClass.isDeprecated() != newClass.isDeprecated()) { |
190 | result = new DeprecatableDifferences(result, oldClass, newClass); |
191 | } |
192 | } |
193 | |
194 | Logger.getLogger(getClass()).debug("End " + name); |
195 | |
196 | return result; |
197 | } |
198 | |
199 | public Differences createFeatureDifferences(String name, Feature_info oldFeature, Feature_info newFeature) { |
200 | Logger.getLogger(getClass()).debug("Begin " + name); |
201 | |
202 | FeatureDifferences featureDifferences; |
203 | if (oldFeature instanceof Field_info || newFeature instanceof Field_info) { |
204 | featureDifferences = new FieldDifferences(name, (Field_info) oldFeature, (Field_info) newFeature); |
205 | |
206 | if (!featureDifferences.isRemoved() && !featureDifferences.isNew() && strategy.isConstantValueDifferent(((Field_info) oldFeature).getConstantValue(), ((Field_info) newFeature).getConstantValue())) { |
207 | ((FieldDifferences) featureDifferences).setConstantValueDifference(true); |
208 | } |
209 | |
210 | if (featureDifferences.isRemoved() && newClass.locateField(name) != null) { |
211 | featureDifferences.setInherited(true); |
212 | } |
213 | } else { |
214 | if (((oldFeature instanceof Method_info) && ((Method_info) oldFeature).isConstructor()) || ((newFeature instanceof Method_info) && ((Method_info) newFeature).isConstructor())) { |
215 | featureDifferences = new ConstructorDifferences(name, (Method_info) oldFeature, (Method_info) newFeature); |
216 | } else { |
217 | featureDifferences = new MethodDifferences(name, (Method_info) oldFeature, (Method_info) newFeature); |
218 | } |
219 | |
220 | if (!featureDifferences.isRemoved() && !featureDifferences.isNew() && strategy.isCodeDifferent(((Method_info) oldFeature).getCode(), ((Method_info) newFeature).getCode())) { |
221 | ((CodeDifferences) featureDifferences).setCodeDifference(true); |
222 | } |
223 | |
224 | if (featureDifferences.isRemoved()) { |
225 | Method_info attempt = newClass.locateMethod(name); |
226 | if ((attempt != null) && (oldFeature.getClassfile().isInterface() == attempt.getClassfile().isInterface())) { |
227 | featureDifferences.setInherited(true); |
228 | } |
229 | } |
230 | } |
231 | |
232 | Differences result = featureDifferences; |
233 | |
234 | if (oldFeature != null && newFeature != null) { |
235 | if (oldFeature.isDeprecated() != newFeature.isDeprecated()) { |
236 | result = new DeprecatableDifferences(result, oldFeature, newFeature); |
237 | } |
238 | } |
239 | |
240 | Logger.getLogger(getClass()).debug("End " + name); |
241 | |
242 | return result; |
243 | } |
244 | } |