001: //////////////////////////////////////////////////////////////////////////////
002: // Clirr: compares two versions of a java library for binary compatibility
003: // Copyright (C) 2003 - 2005 Lars Kühne
004: //
005: // This library is free software; you can redistribute it and/or
006: // modify it under the terms of the GNU Lesser General Public
007: // License as published by the Free Software Foundation; either
008: // version 2.1 of the License, or (at your option) any later version.
009: //
010: // This library is distributed in the hope that it will be useful,
011: // but WITHOUT ANY WARRANTY; without even the implied warranty of
012: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: // Lesser General Public License for more details.
014: //
015: // You should have received a copy of the GNU Lesser General Public
016: // License along with this library; if not, write to the Free Software
017: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: //////////////////////////////////////////////////////////////////////////////
019:
020: package net.sf.clirr.core.internal.checks;
021:
022: import java.util.Comparator;
023:
024: import net.sf.clirr.core.internal.ClassChangeCheck;
025: import net.sf.clirr.core.internal.AbstractDiffReporter;
026: import net.sf.clirr.core.internal.ApiDiffDispatcher;
027: import net.sf.clirr.core.internal.CoIterator;
028: import net.sf.clirr.core.internal.NameComparator;
029: import net.sf.clirr.core.spi.Field;
030: import net.sf.clirr.core.spi.JavaType;
031: import net.sf.clirr.core.spi.Scope;
032: import net.sf.clirr.core.ApiDifference;
033: import net.sf.clirr.core.Severity;
034: import net.sf.clirr.core.ScopeSelector;
035: import net.sf.clirr.core.Message;
036:
037: /**
038: * Checks the fields of a class.
039: *
040: * @author lkuehne
041: */
042: public class FieldSetCheck extends AbstractDiffReporter implements
043: ClassChangeCheck {
044: private static final Message MSG_FIELD_ADDED = new Message(6000);
045: private static final Message MSG_FIELD_REMOVED = new Message(6001);
046: private static final Message MSG_FIELD_NOT_CONSTANT = new Message(
047: 6002);
048: private static final Message MSG_FIELD_CONSTANT_CHANGED = new Message(
049: 6003);
050: private static final Message MSG_FIELD_TYPE_CHANGED = new Message(
051: 6004);
052: private static final Message MSG_FIELD_NOW_NON_FINAL = new Message(
053: 6005);
054: private static final Message MSG_FIELD_NOW_FINAL = new Message(6006);
055: private static final Message MSG_FIELD_NOW_NON_STATIC = new Message(
056: 6007);
057: private static final Message MSG_FIELD_NOW_STATIC = new Message(
058: 6008);
059: private static final Message MSG_FIELD_MORE_ACCESSIBLE = new Message(
060: 6009);
061: private static final Message MSG_FIELD_LESS_ACCESSIBLE = new Message(
062: 6010);
063: private static final Message MSG_CONSTANT_FIELD_REMOVED = new Message(
064: 6011);
065:
066: private static final Comparator COMPARATOR = new NameComparator();
067: private ScopeSelector scopeSelector;
068:
069: public FieldSetCheck(ApiDiffDispatcher dispatcher,
070: ScopeSelector scopeSelector) {
071: super (dispatcher);
072: this .scopeSelector = scopeSelector;
073: }
074:
075: public final boolean check(JavaType baselineClass,
076: JavaType currentClass) {
077: final Field[] baselineFields = baselineClass.getFields();
078: final Field[] currentFields = currentClass.getFields();
079:
080: CoIterator iter = new CoIterator(COMPARATOR, baselineFields,
081: currentFields);
082:
083: while (iter.hasNext()) {
084: iter.next();
085:
086: Field bField = (Field) iter.getLeft();
087: Field cField = (Field) iter.getRight();
088:
089: if (bField == null) {
090: if (scopeSelector.isSelected(cField)) {
091: String scope = cField.getDeclaredScope().getDesc();
092: fireDiff(MSG_FIELD_ADDED, Severity.INFO,
093: currentClass, cField,
094: new String[] { scope });
095: }
096: } else if (cField == null) {
097: if (scopeSelector.isSelected(bField)) {
098: if ((bField.getConstantValue() != null)
099: && bField.isFinal()) {
100: // Fields which are compile-time constants will have
101: // been inlined into callers; even though the field
102: // has been deleted, the caller will continue to use
103: // the old value. The result is therefore not
104: // technically a binary incompatibility, though it is
105: // a source-code incompatibility.
106: // See bugtracker #961222
107: fireDiff(MSG_CONSTANT_FIELD_REMOVED,
108: getSeverity(baselineClass, bField,
109: Severity.WARNING), getSeverity(
110: baselineClass, bField,
111: Severity.ERROR), baselineClass,
112: bField, null);
113: } else {
114: fireDiff(MSG_FIELD_REMOVED, getSeverity(
115: baselineClass, bField, Severity.ERROR),
116: baselineClass, bField, null);
117: }
118: }
119: } else if (scopeSelector.isSelected(bField)
120: || scopeSelector.isSelected(cField)) {
121: checkForModifierChange(bField, cField, currentClass);
122: checkForVisibilityChange(bField, cField, currentClass);
123: checkForTypeChange(bField, cField, currentClass);
124: checkForConstantValueChange(bField, cField,
125: currentClass);
126: }
127: }
128:
129: return true;
130: }
131:
132: private void checkForConstantValueChange(Field bField,
133: Field cField, JavaType currentClass) {
134: if (!(bField.isStatic() && bField.isFinal()
135: && cField.isStatic() && cField.isFinal())) {
136: return;
137: }
138:
139: final Object bVal = bField.getConstantValue();
140:
141: if (bVal != null) {
142: final String bValRep = bVal.toString();
143: final Object cVal = cField.getConstantValue();
144: if (cVal == null) {
145: // TODO: also check whether old field is final. If it's not
146: // final, then external code cannot have inlined the
147: // constant, and therefore we can issue an INFO instead
148: // of a warning. Actually, may be better to introduce a
149: // different message code rather than issue this code with
150: // two different severity levels..
151: fireDiff(MSG_FIELD_NOT_CONSTANT, getSeverity(
152: currentClass, bField, Severity.WARNING),
153: currentClass, cField, null);
154: return;
155: }
156:
157: final String cValRep = String.valueOf(cVal);
158: if (!bValRep.equals(cValRep)) {
159: // TODO: print out old and new value
160: // How can that be done with BCEL, esp. for boolean values?
161: //
162: // TODO: also check whether field is final (see above).
163: fireDiff(MSG_FIELD_CONSTANT_CHANGED, getSeverity(
164: currentClass, bField, Severity.WARNING),
165: currentClass, cField, null);
166: }
167: }
168: }
169:
170: private void checkForTypeChange(Field bField, Field cField,
171: JavaType currentClass) {
172: final String bSig = bField.getType().toString();
173: final String cSig = cField.getType().toString();
174: if (!bSig.equals(cSig)) {
175: fireDiff(MSG_FIELD_TYPE_CHANGED, getSeverity(currentClass,
176: bField, Severity.ERROR), currentClass, bField,
177: new String[] { bSig, cSig });
178: }
179: }
180:
181: private void checkForModifierChange(Field bField, Field cField,
182: JavaType clazz) {
183: if (bField.isFinal() && !cField.isFinal()) {
184: fireDiff(MSG_FIELD_NOW_NON_FINAL, Severity.INFO, clazz,
185: cField, null);
186: }
187:
188: if (!bField.isFinal() && cField.isFinal()) {
189: fireDiff(MSG_FIELD_NOW_FINAL, Severity.ERROR, clazz,
190: cField, null);
191: }
192:
193: if (bField.isStatic() && !cField.isStatic()) {
194: fireDiff(MSG_FIELD_NOW_NON_STATIC, getSeverity(clazz,
195: bField, Severity.ERROR), clazz, cField, null);
196: }
197:
198: if (!bField.isStatic() && cField.isStatic()) {
199: fireDiff(MSG_FIELD_NOW_STATIC, getSeverity(clazz, bField,
200: Severity.ERROR), clazz, cField, null);
201: }
202:
203: // JLS, 13.4.10: Adding or deleting a transient modifier of a field
204: // does not break compatibility with pre-existing binaries
205:
206: // TODO: What about volatile?
207: }
208:
209: private void checkForVisibilityChange(Field bField, Field cField,
210: JavaType clazz) {
211: Scope bScope = bField.getEffectiveScope();
212: Scope cScope = cField.getEffectiveScope();
213:
214: if (cScope.isMoreVisibleThan(bScope)) {
215: fireDiff(MSG_FIELD_MORE_ACCESSIBLE, Severity.INFO, clazz,
216: cField, new String[] { bScope.getDesc(),
217: cScope.getDesc() });
218: } else if (cScope.isLessVisibleThan(bScope)) {
219: fireDiff(MSG_FIELD_LESS_ACCESSIBLE, getSeverity(clazz,
220: bField, Severity.ERROR), clazz, cField,
221: new String[] { bScope.getDesc(), cScope.getDesc() });
222: }
223: }
224:
225: private void fireDiff(Message msg, Severity severity,
226: JavaType clazz, Field field, String[] args) {
227: fireDiff(msg, severity, severity, clazz, field, args);
228: }
229:
230: private void fireDiff(Message msg, Severity binarySeverity,
231: Severity sourceSeverity, JavaType clazz, Field field,
232: String[] args) {
233: final String className = clazz.getName();
234: final ApiDifference diff = new ApiDifference(msg,
235: binarySeverity, sourceSeverity, className, null, field
236: .getName(), args);
237: getApiDiffDispatcher().fireDiff(diff);
238: }
239: }
|