package com.intellij.refactoring.genUtils;
import com.intellij.psi.*;
import com.intellij.refactoring.util.classMembers.MemberInfo;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Copyright 2012, 2016 Université de Nantes
* Contributor : Julien Cohen (Ascola team, Univ. Nantes)
*
* 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.
*/
public class GenAnalysisUtils {
/** DEFINITIONS :
*
*
* The sister classes of a class are the direct subclasses of its direct superclass.
*/
public static boolean checkSubClassesHaveSameMethod(PsiMethod m, PsiClass superClass){
// Algorithmic skeleton : FORALL_BRANCHES : hasSameMethod
for (PsiClass c: SisterClassesUtil.findDirectSubClassesInDirectory(superClass)){
if (!Comparison.hasSameMethod(m, c)) {
// OK only if interface/abstract-class and the method is in all subclasses
if ( ! ( (c.isInterface() || Comparison.isAbstract(c)) && checkSubClassesHaveSameMethod(m, c)))
return false;
}
}
return true;
}
public static boolean checkSubClassesImplementInterface(PsiClassType t, PsiClass superClass){
// Algorithmic skeleton : FORALL_BRANCHES : hasSameImplements
for (PsiClass c: SisterClassesUtil.findDirectSubClassesInDirectory(superClass)){
if (!Comparison.hasSameImplements(c, t)) {
// OK only if interface/abstract-class and the method is in all subclasses
if ( ! ( (c.isInterface() || Comparison.isAbstract(c)) && checkSubClassesImplementInterface(t, c))) return false;
}
}
return true;
}
/* ------ Lookup for implements (interfaces) ------ */
public static Collection<PsiClass> findDirectSubClassesWithCompatibleImplements(PsiClass i, PsiClass superClass)
throws MemberNotImplemented
{
assert (i.isInterface());
final Collection <PsiClass> directSubClasses = SisterClassesUtil.findDirectSubClassesInDirectory(superClass);
for (PsiClass c: directSubClasses){
if (! hasCompatibleImplements(c, i)) throw new MemberNotImplemented(i, c);
}
return directSubClasses ;
}
// FIXME
public static boolean hasCompatibleImplements(PsiClass c, PsiClass i){
assert (i.isInterface());
for (PsiClass tmp : c.getInterfaces()) {
// Don't care of the type parameters (see hasSameImplements)
if (tmp.equals(i)) return true ; // not sure that it works.
}
return false ;
}
/* ------ Lookup for "compatible" members ------ */
@NotNull
public static Collection <PsiClass> findSubClassesWithCompatibleMember(MemberInfo mem, PsiClass superClass)
throws MemberNotImplemented, AmbiguousOverloading {
PsiMember m = mem.getMember();
if (m instanceof PsiMethod){
boolean mustBePublic = superClass.isInterface() ;
return findDirectSubClassesWithCompatibleMethod((PsiMethod) m, superClass, mustBePublic); // can throw MemberNotImplemented exception but cannot be null
}
else if (m instanceof PsiClass && Comparison.memberClassComesFromImplements(mem)) {
return findDirectSubClassesWithCompatibleImplements((PsiClass) m, superClass);}
else if (m instanceof PsiClass) {
throw new IncorrectOperationException("implement me : pull up class " + m);} // FIXME
else if (m instanceof PsiField) {
throw new IncorrectOperationException("implement me : pull up field");} // FIXME
else throw new IncorrectOperationException("cannot handle this kind of member:" + m); // FIXME
}
// Must find an implementation in each direct subclass.
// Otherwise: throws an exception.
@NotNull
public static Collection<PsiClass> findDirectSubClassesWithCompatibleMethod(PsiMethod m, PsiClass superClass, boolean checkpublic)
throws AmbiguousOverloading, MemberNotImplemented {
final Collection<PsiClass> res = new LinkedList<PsiClass>();
final Collection <PsiClass> directSubClasses = SisterClassesUtil.findDirectSubClassesInDirectory(superClass);
for (PsiClass c: directSubClasses){
final int count = hasCompatibleMethod(m, c, checkpublic);
if (count>1) throw new AmbiguousOverloading(m,c);
if (count==1) res.add(c); // the method is found
else { // no compatible method.
// The method is not found.
// If that class is concrete, pull up would introduce an error.
// If that class is abstract and the method is implemented in subclasses, the user has to first make a pull-up to that abstract class.
throw new MemberNotImplemented(m,c);
}
}
return res;
}
/* see definition of compatibility at the beginning of the file */
public static List<PsiMethod> findCompatibleMethods(PsiMethod method, Collection<PsiClass> sisterClasses) {
final List<PsiMethod> sisterMethods = new LinkedList<PsiMethod>();
for (PsiClass c:sisterClasses){
final List<PsiMethod> localSisterMethods = findCompatibleMethodsInClass(method, c);
if (localSisterMethods.size() == 0) {
throw new IncorrectOperationException("The method is not implemented by all sister classes.");
}
if (localSisterMethods.size() > 1) {
throw new IncorrectOperationException("The method is overloaded (with the same number of arguments).");
}
sisterMethods.add(localSisterMethods.get(0));
}
return sisterMethods;
}
public static boolean hasCompatibleMembers(PsiClass c, Iterable<MemberInfo> membersToPullUp)
throws AmbiguousOverloading {
for (MemberInfo member: membersToPullUp){
final PsiMember theMember = member.getMember();
if (theMember instanceof PsiMethod){
PsiMethod theMethod = (PsiMethod) theMember ;
final int count = hasCompatibleMethod(theMethod, c);
if (count >1) {
throw new AmbiguousOverloading(theMethod, c);
}
else
if (count == 0)
return false;
}
else
if (theMember instanceof PsiField){
PsiField theField = (PsiField) theMember;
if (!Comparison.hasField(theField, c))
return false ;
}
else
if (theMember instanceof PsiClass) {
throw new IncorrectOperationException("(hasCompatibleMembers) don't know what to do with that class : " + theMember);
}
else throw new IncorrectOperationException("This kind of member not handled yet : " + theMember); // FIXME
}
return true;
}
public static List<PsiMethod> findCompatibleMethodsInClass(PsiMethod m, PsiClass c){
final List <PsiMethod> result = new LinkedList<PsiMethod>();
for (PsiMethod m_tmp: c.getMethods()){
if (Compatibility.isCompatible(m, m_tmp))
result.add(m_tmp);
}
return result;
}
public static List<PsiMethod> findCompatiblePublicMethodsInClass(PsiMethod m, PsiClass c){
final List <PsiMethod> result = new LinkedList<PsiMethod>();
for (PsiMethod m_tmp: c.getMethods()){
if (Compatibility.isCompatible(m, m_tmp) && m_tmp.hasModifierProperty("public"))
result.add(m_tmp);
}
return result;
}
public static List<PsiMethod> findSemiCompatibleMethodsInClass(PsiMethod m, PsiClass c){
final List <PsiMethod> result = new LinkedList<PsiMethod>();
for (PsiMethod m_tmp: c.getMethods()){
if (Compatibility.isSemiCompatible(m, m_tmp))
result.add(m_tmp);
}
return result;
}
public static int hasCompatiblePublicMethod(PsiMethod m, PsiClass c) {
return findCompatiblePublicMethodsInClass(m, c).size();
}
public static int hasCompatibleMethod(PsiMethod m, PsiClass c) {
return findCompatibleMethodsInClass(m,c).size();
}
/** Same as hasCompatibleMethod but checks only the types of the parameters, not the return type.
* Used to detect problematic overloading in target superclass.
* */
public static int hasSemiCompatibleMethod(PsiMethod m, PsiClass c) {
return findSemiCompatibleMethodsInClass(m, c).size();
}
public static int hasCompatibleMethod(PsiMethod m, PsiClass c, boolean checkpublic){
if (!checkpublic) return hasCompatibleMethod(m, c);
else return hasCompatiblePublicMethod(m, c);
}
public static boolean memberClassIsInnerClass(MemberInfo m){
/** ************ THIS IS A COMMENT FROM MemberInfoBase.getOverrides ********************
* Returns Boolean.TRUE if getMember() overrides something, Boolean.FALSE if getMember()
* implements something, null if neither is the case.
* If getMember() is a PsiClass, returns Boolean.TRUE if this class comes
* from 'extends', Boolean.FALSE if it comes from 'implements' list, null
* if it is an inner class.
*/
assert (m.getMember() instanceof PsiClass) ;
if (m.getOverrides() == null ) return true ;
else return false;
}
/* ------ Analysis : can gen ------ */
/** returns null if the method cannot be pulled up with generification,
* returns the smallest set of classes with compatible method in hiearchy otherwise
* (the smallest set with compatible methods that covers all the branches of the hierarchy) */
// only valid for methods (used for GUI)
@Nullable
public static Collection<PsiClass> canGenMethod(MemberInfo member, PsiClass superclass){
assert(member.getMember() instanceof PsiMethod);
try {
Collection<PsiClass> res = findSubClassesWithCompatibleMember(member, superclass);
// no exception raised means there is a compatible member in all branches of the hierarchy.
// TODO : check that the method is not already overriding a method.
// TODO: There might be also a problem when introducing the method in super class introduces a nasty overloading.
if (hasSemiCompatibleMethod((PsiMethod) member.getMember(), superclass) > 0)
return null ; // This avoids a possibly problematic overloading in super class.
return res ;
}
catch (AmbiguousOverloading e) {
System.out.println("(debug) ambiguity found (overloading)") ; // debug
return null ;
}
catch (MemberNotImplemented e) {
System.out.println("(debug) no implementation for " + member.getMember() + " in " + e.c) ; // debug
return null;
}
}
// This is used for the GUI : the GUI just needs a boolean, so the set of classes is discarded.
// TODO : avoid to lose the information on the set of classes to avoid to have to compute it again later.
public static boolean computeCanGenMember(MemberInfo member, PsiClass sup) {
boolean result;
PsiMember m = member.getMember();
if(!(m instanceof PsiMethod)) {
result = false ;
}
else{
Collection<PsiClass> compatClasses = canGenMethod(member, sup);
if (compatClasses != null) result = true ;
else result = false ;
}
return result;
}
// See also hasMember
public static boolean computeCanDirectAbstractPullupMember(PsiClass selectedSuper, MemberInfo mem) {
PsiMember m = mem.getMember();
// *) Methods
if (m instanceof PsiMethod){
return checkSubClassesHaveSameMethod((PsiMethod) m, selectedSuper) ;
}
// *) Fields
else if (m instanceof PsiField) { return false ; } // fIXME
// *) Implements interface
else if (m instanceof PsiClass && Comparison.memberClassComesFromImplements(mem)) {
final PsiClassType[] referencedTypes = mem.getSourceReferenceList().getReferencedTypes();
assert(referencedTypes.length == 1) ;
return checkSubClassesImplementInterface(referencedTypes[0], selectedSuper) ;
}
// *) Other cases.
else throw new IncorrectOperationException("this type of member not handled yet : " + m);
}
}