/*
* Copyright 2015 Lukas Krejci
*
* 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.revapi.java.checks.classes;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nonnull;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.revapi.CoIterator;
import org.revapi.Difference;
import org.revapi.java.spi.CheckBase;
import org.revapi.java.spi.Code;
import org.revapi.java.spi.JavaTypeElement;
import org.revapi.java.spi.TypeEnvironment;
import org.revapi.java.spi.Util;
/**
* @author Lukas Krejci
* @since 0.1
*/
public final class InheritanceChainChanged extends CheckBase {
@Override
public EnumSet<Type> getInterest() {
return EnumSet.of(Type.CLASS);
}
@Override
protected List<Difference> doEnd() {
ActiveElements<JavaTypeElement> types = popIfActive();
if (types != null) {
List<Difference> ret = new ArrayList<>();
@SuppressWarnings("unchecked")
List<TypeMirror> oldSuperClasses = (List<TypeMirror>) types.context[0];
@SuppressWarnings("unchecked")
List<TypeMirror> newSuperClasses = (List<TypeMirror>) types.context[1];
Comparator<TypeMirror> typeNameComparator = Comparator.comparing(Util::toUniqueString);
List<TypeMirror> removedSuperClasses = new ArrayList<>();
List<TypeMirror> addedSuperClasses = new ArrayList<>();
oldSuperClasses.sort(typeNameComparator);
newSuperClasses.sort(typeNameComparator);
CoIterator<TypeMirror> iterator = new CoIterator<>(oldSuperClasses.iterator(), newSuperClasses.iterator(),
typeNameComparator);
while (iterator.hasNext()) {
iterator.next();
TypeMirror oldType = iterator.getLeft();
TypeMirror newType = iterator.getRight();
if (oldType == null) {
addedSuperClasses.add(newType);
} else if (newType == null) {
removedSuperClasses.add(oldType);
}
}
//this will give us the equivalent of removed/added superclasses but ordered by the inheritance chain
//not by name
removedSuperClasses = retainInCopy(oldSuperClasses, removedSuperClasses);
addedSuperClasses = retainInCopy(newSuperClasses, addedSuperClasses);
Iterator<TypeMirror> removedIt = removedSuperClasses.iterator();
Iterator<TypeMirror> addedIt = addedSuperClasses.iterator();
//always report the most concrete classes
if (removedIt.hasNext()) {
removedIt.next();
}
if (addedIt.hasNext()) {
addedIt.next();
}
//ok, now we only have super types left of the most concrete removed/added super class.
//we are only going to report those that changed their inheritance hierarchy in the other version of the API.
removeClassesWithEquivalentSuperClassChain(removedIt, getOldTypeEnvironment(), getNewTypeEnvironment());
removeClassesWithEquivalentSuperClassChain(addedIt, getNewTypeEnvironment(), getOldTypeEnvironment());
for (TypeMirror t : removedSuperClasses) {
String str = Util.toHumanReadableString(t);
ret.add(createDifference(Code.CLASS_NO_LONGER_INHERITS_FROM_CLASS,
Code.attachmentsFor(types.oldElement, types.newElement, "superClass", str)));
}
for (TypeMirror t : addedSuperClasses) {
String str = Util.toHumanReadableString(t);
Code code = types.oldElement.getDeclaringElement().getModifiers().contains(Modifier.FINAL)
? Code.CLASS_FINAL_CLASS_INHERITS_FROM_NEW_CLASS
: Code.CLASS_NON_FINAL_CLASS_INHERITS_FROM_NEW_CLASS;
ret.add(createDifference(code, Code.attachmentsFor(types.oldElement, types.newElement, "superClass", str)));
//additionally add a difference about checked exceptions
if (changedToCheckedException(getNewTypeEnvironment().getTypeUtils(), t, oldSuperClasses)) {
ret.add(createDifference(Code.CLASS_NOW_CHECKED_EXCEPTION,
Code.attachmentsFor(types.oldElement, types.newElement)));
}
}
return ret;
}
return null;
}
@Override
protected void doVisitClass(JavaTypeElement oldEl, JavaTypeElement newEl) {
if (oldEl == null || newEl == null) {
return;
}
TypeElement oldType = oldEl.getDeclaringElement();
TypeElement newType = newEl.getDeclaringElement();
if (isBothPrivate(oldEl, newEl)) {
return;
}
List<TypeMirror> oldSuperTypes = Util
.getAllSuperClasses(getOldTypeEnvironment().getTypeUtils(), oldType.asType());
List<TypeMirror> newSuperTypes = Util
.getAllSuperClasses(getNewTypeEnvironment().getTypeUtils(), newType.asType());
if (oldSuperTypes.size() != newSuperTypes.size()) {
pushActive(oldEl, newEl, oldSuperTypes, newSuperTypes);
} else {
Types oldTypes = getOldTypeEnvironment().getTypeUtils();
Types newTypes = getNewTypeEnvironment().getTypeUtils();
for (int i = 0; i < oldSuperTypes.size(); ++i) {
//need to erase them all so that we get only true type changes. formal type parameter changes
//are captured by SuperTypeParametersChanged check
TypeMirror oldSuperClass = oldTypes.erasure(oldSuperTypes.get(i));
TypeMirror newSuperClass = newTypes.erasure(newSuperTypes.get(i));
if (!Util.isSameType(oldSuperClass, newSuperClass)) {
pushActive(oldEl, newEl, oldSuperTypes, newSuperTypes);
break;
}
}
}
}
private boolean changedToCheckedException(@Nonnull Types newTypeEnv, @Nonnull TypeMirror newType,
@Nonnull List<TypeMirror> oldTypes) {
if ("java.lang.Exception".equals(Util.toHumanReadableString(newType))) {
return isTypeThrowable(oldTypes);
} else {
for (TypeMirror sc : Util.getAllSuperClasses(newTypeEnv, newType)) {
if ("java.lang.Exception".equals(Util.toHumanReadableString(sc))) {
return isTypeThrowable(oldTypes);
}
}
}
return false;
}
private boolean isTypeThrowable(@Nonnull List<TypeMirror> superClassesOfType) {
for (TypeMirror sc : superClassesOfType) {
if (Util.toHumanReadableString(sc).equals("java.lang.Throwable")) {
return true;
}
}
return false;
}
private List<String> superClassChainAsUniqueStrings(@Nonnull TypeMirror cls, @Nonnull Types env) {
List<TypeMirror> supers = Util.getAllSuperClasses(env, cls);
List<String> ret = new ArrayList<>(supers.size());
for (TypeMirror s : supers) {
ret.add(Util.toUniqueString(env.erasure(s)));
}
return ret;
}
private void removeClassesWithEquivalentSuperClassChain(Iterator<TypeMirror> candidates,
TypeEnvironment candidateEnvironment, TypeEnvironment oppositeEnvironment) {
while (candidates.hasNext()) {
boolean report = true;
TypeMirror candidate = candidates.next();
String typeName = Util.toHumanReadableString(candidate);
TypeElement el = oppositeEnvironment.getElementUtils().getTypeElement(typeName);
if (el != null) {
TypeMirror opposite = el.asType();
List<String> candidateSuperChain = superClassChainAsUniqueStrings(candidate,
candidateEnvironment.getTypeUtils());
List<String> oppositeSuperChain = superClassChainAsUniqueStrings(opposite,
oppositeEnvironment.getTypeUtils());
report = !candidateSuperChain.equals(oppositeSuperChain);
}
if (!report) {
candidates.remove();
}
}
}
private List<TypeMirror> retainInCopy(List<TypeMirror> all, List<TypeMirror> retained) {
List<TypeMirror> tmp = new ArrayList<>(all);
tmp.retainAll(retained);
return tmp;
}
}