package japicmp.model; import com.google.common.base.Optional; import japicmp.cmp.JarArchiveComparator; import japicmp.cmp.JarArchiveComparatorOptions; import japicmp.exception.JApiCmpException; import javassist.*; import java.io.Externalizable; import java.io.Serializable; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; public class JavaObjectSerializationCompatibility { private static final Logger LOGGER = Logger.getLogger(JavaObjectSerializationCompatibility.class.getName()); public static final String SERIAL_VERSION_UID = "serialVersionUID"; public static JApiSerialVersionUid extractSerialVersionUid(JarArchiveComparatorOptions options, JarArchiveComparator jarArchiveComparator, Optional<CtClass> oldClass, Optional<CtClass> newClass) { SerialVersionUidResult resultOld = computeSerialVersionUid(options, oldClass, jarArchiveComparator); SerialVersionUidResult resultNew = computeSerialVersionUid(options, newClass, jarArchiveComparator); return new JApiSerialVersionUid(resultOld.serializable, resultNew.serializable, resultOld.serialVersionUidDefault, resultNew.serialVersionUidDefault, resultOld.serialVersionUid, resultNew.serialVersionUid); } public void evaluate(List<JApiClass> jApiClasses) { for (JApiClass jApiClass : jApiClasses) { computeChangeStatus(jApiClass); } } private static class SerialVersionUidResult { boolean serializable = false; Optional<Long> serialVersionUid = Optional.absent(); Optional<Long> serialVersionUidDefault = Optional.absent(); } private static SerialVersionUidResult computeSerialVersionUid(JarArchiveComparatorOptions options, Optional<CtClass> ctClassOptional, JarArchiveComparator jarArchiveComparator) { SerialVersionUidResult result = new SerialVersionUidResult(); if (ctClassOptional.isPresent()) { CtClass ctClass = ctClassOptional.get(); if (isCtClassSerializable(options, ctClass, jarArchiveComparator)) { result.serializable = true; try { CtField declaredField = ctClass.getDeclaredField(SERIAL_VERSION_UID); Object constantValue = declaredField.getConstantValue(); if (constantValue instanceof Long) { result.serialVersionUid = Optional.of((Long) constantValue); } } catch (Exception e) { LOGGER.log(Level.FINE, "Failed to get serialVersionUid from class " + ctClass.getName() + ": " + e.getLocalizedMessage(), e); try { SerialVersionUID.setSerialVersionUID(ctClass); CtField declaredField = ctClass.getDeclaredField(SERIAL_VERSION_UID); Object constantValue = declaredField.getConstantValue(); if (constantValue instanceof Long) { result.serialVersionUidDefault = Optional.of((Long) constantValue); } ctClass.removeField(declaredField); } catch (Exception ex) { LOGGER.log(Level.FINE, "Failed to compute default serialVersionUid for class " + ctClass.getName() + ": " + ex.getLocalizedMessage(), ex); } } if (!result.serialVersionUidDefault.isPresent()) { try { CtField declaredFieldOriginal = ctClass.getDeclaredField(SERIAL_VERSION_UID); ctClass.removeField(declaredFieldOriginal); SerialVersionUID.setSerialVersionUID(ctClass); CtField declaredField = ctClass.getDeclaredField(SERIAL_VERSION_UID); Object constantValue = declaredField.getConstantValue(); if (constantValue instanceof Long) { result.serialVersionUidDefault = Optional.of((Long) constantValue); } ctClass.removeField(declaredField); ctClass.addField(declaredFieldOriginal); } catch (Exception ex) { LOGGER.log(Level.FINE, "Failed to compute default serialVersionUid for class " + ctClass.getName() + ": " + ex.getLocalizedMessage(), ex); } } } } return result; } private static boolean isCtClassSerializable(JarArchiveComparatorOptions options, CtClass clazz, JarArchiveComparator jarArchiveComparator) { ClassPool pool = clazz.getClassPool(); try { return clazz.subtypeOf(pool.get("java.io.Serializable")); } catch (NotFoundException e) { if (options.getIgnoreMissingClasses().ignoreClass(e.getMessage())) { return false; } else { throw JApiCmpException.forClassLoading(e, clazz.getName(), jarArchiveComparator); } } } private void computeChangeStatus(JApiClass jApiClass) { JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.NOT_SERIALIZABLE; JApiSerialVersionUid jApiSerialVersionUid = jApiClass.getSerialVersionUid(); if (jApiSerialVersionUid.isSerializableOld() || jApiSerialVersionUid.isSerializableNew()) { state = checkChanges(jApiClass); if (!state.isIncompatible()) { if (jApiSerialVersionUid.getSerialVersionUidInClassOld().isPresent() && jApiSerialVersionUid.getSerialVersionUidInClassNew().isPresent()) { Long suidOld = jApiSerialVersionUid.getSerialVersionUidInClassOld().get(); Long suidNew = jApiSerialVersionUid.getSerialVersionUidInClassNew().get(); if (suidOld.equals(suidNew)) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_SERIALVERSIONUID_MODIFIED; } } else if (jApiSerialVersionUid.getSerialVersionUidInClassOld().isPresent()) { Long suidOld = jApiSerialVersionUid.getSerialVersionUidInClassOld().get(); if (jApiClass.getChangeStatus() != JApiChangeStatus.REMOVED) { if (jApiSerialVersionUid.isSerializableNew()) { if (jApiSerialVersionUid.getSerialVersionUidDefaultNew().isPresent()) { Long suidNewDefault = jApiSerialVersionUid.getSerialVersionUidDefaultNew().get(); if (suidOld.equals(suidNewDefault)) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_SERIALVERSIONUID_REMOVED_AND_NOT_MACHTES_NEW_DEFAULT; } } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_CLASS_REMOVED; } } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_SERIALIZABLE_REMOVED; } } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_CLASS_REMOVED; } } else if (jApiSerialVersionUid.getSerialVersionUidInClassNew().isPresent()) { if (jApiClass.getChangeStatus() != JApiChangeStatus.NEW) { if (jApiSerialVersionUid.isSerializableOld()) { Long suidNew = jApiSerialVersionUid.getSerialVersionUidInClassNew().get(); if (jApiSerialVersionUid.getSerialVersionUidDefaultOld().isPresent()) { Long suidOldDefault = jApiSerialVersionUid.getSerialVersionUidDefaultOld().get(); if (suidNew.equals(suidOldDefault)) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_SERIALVERSIONUID_REMOVED_AND_NOT_MACHTES_NEW_DEFAULT; } } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; } } } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; } } else { if (jApiClass.getChangeStatus() == JApiChangeStatus.NEW) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; } else if (jApiClass.getChangeStatus() == JApiChangeStatus.REMOVED) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_CLASS_REMOVED; } else { if (!isEnum(jApiClass)) { //default serialVersionUID is ignored for enums (section 1.12 "Serialization of Enum Constants") if (jApiSerialVersionUid.getSerialVersionUidDefaultOld().isPresent() && jApiSerialVersionUid.getSerialVersionUidDefaultNew().isPresent()) { Long defaultOld = jApiSerialVersionUid.getSerialVersionUidDefaultOld().get(); Long defaultNew = jApiSerialVersionUid.getSerialVersionUidDefaultNew().get(); if (defaultOld.equals(defaultNew)) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_DEFAULT_SERIALVERSIONUID_CHANGED; } } else if (jApiSerialVersionUid.getSerialVersionUidDefaultOld().isPresent()) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_SERIALIZABLE_REMOVED; } else { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; } } } } } } jApiClass.setJavaObjectSerializationCompatible(state); } private boolean isEnum(JApiClass jApiClass) { return jApiClass.getClassType().getNewTypeOptional().isPresent() && jApiClass.getClassType().getNewTypeOptional().get() == JApiClassType.ClassType.ENUM; } /** * Checks compatibility of changes according to http://docs.oracle.com/javase/7/docs/platform/serialization/spec/version.html#5172. * * @param jApiClass the class to check * @return either SERIALIZABLE_INCOMPATIBLE or SERIALIZABLE_COMPATIBLE */ private JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus checkChanges(JApiClass jApiClass) { JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE; if (jApiClass.getChangeStatus() == JApiChangeStatus.REMOVED) { return JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_CLASS_REMOVED; } state = checkChangesForClassType(jApiClass, state); if (state != JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE) { return state; } state = checkChangesForSuperclass(jApiClass, state); if (state != JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE) { return state; } state = checkChangesForFields(jApiClass, state); if (state != JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE) { return state; } state = checkChangesForInterfaces(jApiClass, state); return state; } private JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus checkChangesForSuperclass(JApiClass jApiClass, JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus state) { JApiSuperclass jApiClassSuperclass = jApiClass.getSuperclass(); if (jApiClassSuperclass.getChangeStatus() == JApiChangeStatus.MODIFIED) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_SUPERCLASS_MODIFIED; } return state; } private JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus checkChangesForClassType(JApiClass jApiClass, JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus state) { JApiClassType classType = jApiClass.getClassType(); if (classType.getChangeStatus() == JApiChangeStatus.MODIFIED) { JApiClassType.ClassType oldClassType = classType.getOldTypeOptional().get(); JApiClassType.ClassType newClassType = classType.getNewTypeOptional().get(); if (oldClassType != newClassType) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_CLASS_TYPE_MODIFIED; } } return state; } private JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus checkChangesForInterfaces(JApiClass jApiClass, JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus state) { boolean serializableAdded = false; boolean serializableRemoved = false; boolean serializableUnchanged = false; boolean externalizableAdded = false; boolean externalizableRemoved = false; for (JApiImplementedInterface implementedInterface : jApiClass.getInterfaces()) { if (Serializable.class.getCanonicalName().equals(implementedInterface.getFullyQualifiedName())) { if (implementedInterface.getChangeStatus() == JApiChangeStatus.NEW) { serializableAdded = true; } else if (implementedInterface.getChangeStatus() == JApiChangeStatus.REMOVED) { serializableRemoved = true; } else if (implementedInterface.getChangeStatus() == JApiChangeStatus.UNCHANGED) { serializableUnchanged = true; } } if (Externalizable.class.getCanonicalName().equals(implementedInterface.getFullyQualifiedName())) { if (implementedInterface.getChangeStatus() == JApiChangeStatus.NEW) { externalizableAdded = true; } else if (implementedInterface.getChangeStatus() == JApiChangeStatus.REMOVED) { externalizableRemoved = true; } } } if (serializableRemoved) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_SERIALIZABLE_REMOVED; } if (externalizableRemoved) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_EXTERNALIZABLE_REMOVED; } if ((serializableRemoved || serializableUnchanged || serializableAdded) && externalizableAdded) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_CHANGED_FROM_SERIALIZABLE_TO_EXTERNALIZABLE; } if ((serializableUnchanged || serializableAdded) && externalizableRemoved) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_CHANGED_FROM_EXTERNALIZABLE_TO_SERIALIZABLE; } return state; } private JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus checkChangesForFields(JApiClass jApiClass, JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus state) { for (JApiField field : jApiClass.getFields()) { if (field.getChangeStatus() == JApiChangeStatus.REMOVED) { if (!"serialVersionUID".equals(field.getName())) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_FIELD_REMOVED; } } JApiModifier<StaticModifier> staticModifier = field.getStaticModifier(); if (staticModifier.getOldModifier().isPresent() && staticModifier.getNewModifier().isPresent()) { if (staticModifier.getOldModifier().get() == StaticModifier.NON_STATIC && staticModifier.getNewModifier().get() == StaticModifier.STATIC) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_FIELD_CHANGED_FROM_NONSTATIC_TO_STATIC; } } JApiModifier<TransientModifier> transientModifier = field.getTransientModifier(); if (transientModifier.getOldModifier().isPresent() && transientModifier.getNewModifier().isPresent()) { if (transientModifier.getOldModifier().get() == TransientModifier.NON_TRANSIENT && transientModifier.getNewModifier().get() == TransientModifier.TRANSIENT) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_FIELD_CHANGED_FROM_NONTRANSIENT_TO_TRANSIENT; } } if (field.getType().getChangeStatus() == JApiChangeStatus.MODIFIED) { state = JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_INCOMPATIBLE_FIELD_TYPE_MODIFIED; } } return state; } }