/**
* Copyright 2012-2014 Julien Eluard and contributors
* This project includes software developed by Julien Eluard: https://github.com/jeluard/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.semver;
import java.util.Collections;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import org.osjava.jardiff.AbstractInfo;
/**
*
* Encapsulates differences between two sets of classes.
*
* Provides convenient methods to validate that chosen {@link Version} are correct.
*
*/
@Immutable
public final class Delta {
/**
* Library compatibility type. From most compatible to less compatible.
*/
public enum CompatibilityType {
/**
* No (public) changes.
*/
BACKWARD_COMPATIBLE_IMPLEMENTER,
/**
* Only added and deprecated changes,
* i.e. source compatible changes.
*/
BACKWARD_COMPATIBLE_USER,
/**
* Contains binary compatible changes,
* but may not be fully source compatible
* and may contain changed values of fields.
*/
BACKWARD_COMPATIBLE_BINARY,
/**
* Contains non binary compatible changes.
*/
NON_BACKWARD_COMPATIBLE
}
@Immutable
public static class Difference implements Comparable {
private final String className;
private final AbstractInfo info;
public Difference(@Nonnull final String className, @Nonnull final AbstractInfo info) {
if (className == null) {
throw new IllegalArgumentException("null className");
}
if (info == null) {
throw new IllegalArgumentException("null info");
}
this.className = className;
this.info = info;
}
@Nonnull
public String getClassName() {
return this.className;
}
@Nonnull
public AbstractInfo getInfo() {
return info;
}
@Override
public int compareTo(final Difference other) {
return getClassName().compareTo(other.getClassName());
}
}
@Immutable
public static class Add extends Difference {
public Add(@Nonnull final String className, @Nonnull final AbstractInfo info) {
super(className, info);
}
}
@Immutable
public static class Change extends Difference {
private final AbstractInfo modifiedInfo;
public Change(@Nonnull final String className, @Nonnull final AbstractInfo info, @Nonnull final AbstractInfo modifiedInfo) {
super(className, info);
this.modifiedInfo = modifiedInfo;
}
public AbstractInfo getModifiedInfo() {
return this.modifiedInfo;
}
}
@Immutable
public static class CompatChange extends Difference {
private final AbstractInfo modifiedInfo;
public CompatChange(@Nonnull final String className, @Nonnull final AbstractInfo info, @Nonnull final AbstractInfo modifiedInfo) {
super(className, info);
this.modifiedInfo = modifiedInfo;
}
public AbstractInfo getModifiedInfo() {
return this.modifiedInfo;
}
}
@Immutable
public static class Deprecate extends Difference {
private final AbstractInfo modifiedInfo;
public Deprecate(@Nonnull final String className,
@Nonnull final AbstractInfo info,
@Nonnull final AbstractInfo modifiedInfo) {
super(className, info);
this.modifiedInfo = modifiedInfo;
}
public AbstractInfo getModifiedInfo() {
return this.modifiedInfo;
}
}
@Immutable
public static class Remove extends Difference {
public Remove(@Nonnull final String className, @Nonnull final AbstractInfo info) {
super(className, info);
}
}
private final Set differences;
private final boolean fieldCompatChanged;
public Delta(@Nonnull final Set extends Difference> differences, final boolean fieldCompatChanged) {
this.differences = Collections.unmodifiableSet(differences);
this.fieldCompatChanged = fieldCompatChanged;
}
@Nonnull
public final Set getDifferences() {
return this.differences;
}
public final boolean fieldCompatChanged() { return fieldCompatChanged; }
/**
* @return {@link CompatibilityType} based on this {@link Delta}
*/
@Nonnull
public final CompatibilityType computeCompatibilityType() {
if (contains(this.differences, Change.class) ||
contains(this.differences, Remove.class)) {
return CompatibilityType.NON_BACKWARD_COMPATIBLE;
} else if (contains(this.differences, CompatChange.class)) {
return CompatibilityType.BACKWARD_COMPATIBLE_BINARY;
} else if (contains(this.differences, Add.class) ||
contains(this.differences, Deprecate.class)) {
return CompatibilityType.BACKWARD_COMPATIBLE_USER;
} else {
return CompatibilityType.BACKWARD_COMPATIBLE_IMPLEMENTER;
}
}
/**
* @param type {@link Difference} type to test
* @return {@code true}, if given {@link Difference} type is contained by this {@link Delta}
*/
public final boolean contains(final Class extends Difference> type) {
return contains(this.differences, type);
}
protected final boolean contains(final Set differences, final Class extends Difference> type) {
for (final Difference difference : differences) {
if (type.isInstance(difference)) {
return true;
}
}
return false;
}
/**
*
* Infers next {@link Version} depending on provided {@link CompatibilityType}.
*
* @param version
* @param compatibilityType
* @return
*/
@Nonnull
public static Version inferNextVersion(@Nonnull final Version version, @Nonnull final CompatibilityType compatibilityType) {
if (version == null) {
throw new IllegalArgumentException("null version");
}
if (compatibilityType == null) {
throw new IllegalArgumentException("null compatibilityType");
}
switch (compatibilityType) {
case BACKWARD_COMPATIBLE_IMPLEMENTER:
return version.next(Version.Element.PATCH);
case BACKWARD_COMPATIBLE_USER:
case BACKWARD_COMPATIBLE_BINARY:
return version.next(Version.Element.MINOR);
case NON_BACKWARD_COMPATIBLE:
return version.next(Version.Element.MAJOR);
default:
throw new IllegalArgumentException("Unknown type <"+compatibilityType+">");
}
}
/**
* @param previous
* @return an inferred {@link Version} for current JAR based on previous JAR content/version.
* @throws IOException
*/
@Nonnull
public final Version infer(@Nonnull final Version previous) {
if (previous == null) {
throw new IllegalArgumentException("null previous");
}
if (previous.isInDevelopment()) {
throw new IllegalArgumentException("Cannot infer for in development version <"+previous+">");
}
final CompatibilityType compatibilityType = computeCompatibilityType();
return inferNextVersion(previous, compatibilityType);
}
/**
* @param previous
* @param current
* @return true if {@link Version} provided for current JAR is compatible with previous JAR content/version.
* @throws IOException
*/
public final boolean validate(@Nonnull final Version previous, @Nonnull final Version current) {
if (previous == null) {
throw new IllegalArgumentException("null previous");
}
if (current == null) {
throw new IllegalArgumentException("null current");
}
if (current.compareTo(previous) <= 0) {
throw new IllegalArgumentException("Current version <"+previous+"> must be more recent than previous version <"+current+">.");
}
//When in development public API is not considered stable
if (current.isInDevelopment()) {
return true;
}
//Current version must be superior or equals to inferred version
final Version inferredVersion = infer(previous);
// if the current version is a pre-release then the corresponding release need to be superior or equal
return current.toReleaseVersion().compareTo(inferredVersion) >= 0;
}
}