| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * 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 com.intellij.psi.impl.source.tree.java; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiManagerEx; |
| import com.intellij.psi.impl.source.resolve.ParameterTypeInferencePolicy; |
| import com.intellij.psi.impl.source.resolve.ResolveCache; |
| import com.intellij.psi.impl.source.tree.ChildRole; |
| import com.intellij.psi.impl.source.tree.FileElement; |
| import com.intellij.psi.impl.source.tree.JavaElementType; |
| import com.intellij.psi.impl.source.tree.SharedImplUtil; |
| import com.intellij.psi.infos.CandidateInfo; |
| import com.intellij.psi.infos.ClassCandidateInfo; |
| import com.intellij.psi.infos.MethodCandidateInfo; |
| import com.intellij.psi.scope.ElementClassFilter; |
| import com.intellij.psi.scope.JavaScopeProcessorEvent; |
| import com.intellij.psi.scope.PsiConflictResolver; |
| import com.intellij.psi.scope.PsiScopeProcessor; |
| import com.intellij.psi.scope.conflictResolvers.JavaMethodsConflictResolver; |
| import com.intellij.psi.scope.processor.FilterScopeProcessor; |
| import com.intellij.psi.scope.processor.MethodCandidatesProcessor; |
| import com.intellij.psi.scope.util.PsiScopesUtil; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.*; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.SmartList; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase implements PsiMethodReferenceExpression { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiMethodReferenceExpressionImpl"); |
| |
| public PsiMethodReferenceExpressionImpl() { |
| super(JavaElementType.METHOD_REF_EXPRESSION); |
| } |
| |
| @Override |
| public PsiTypeElement getQualifierType() { |
| final PsiElement qualifier = getQualifier(); |
| return qualifier instanceof PsiTypeElement ? (PsiTypeElement)qualifier : null; |
| } |
| |
| @Nullable |
| @Override |
| public PsiType getFunctionalInterfaceType() { |
| return LambdaUtil.getFunctionalInterfaceType(this, true); |
| } |
| |
| @Override |
| public PsiExpression getQualifierExpression() { |
| final PsiElement qualifier = getQualifier(); |
| return qualifier instanceof PsiExpression ? (PsiExpression)qualifier : null; |
| } |
| |
| @Override |
| public PsiType getType() { |
| return new PsiMethodReferenceType(this); |
| } |
| |
| @Override |
| public PsiElement getReferenceNameElement() { |
| final PsiElement element = getLastChild(); |
| return element instanceof PsiIdentifier || PsiUtil.isJavaToken(element, JavaTokenType.NEW_KEYWORD) ? element : null; |
| } |
| |
| @Override |
| public void processVariants(@NotNull final PsiScopeProcessor processor) { |
| final FilterScopeProcessor proc = new FilterScopeProcessor(ElementClassFilter.METHOD, processor); |
| PsiScopesUtil.resolveAndWalk(proc, this, null, true); |
| } |
| |
| @Override |
| public void setQualifierExpression(@Nullable PsiExpression newQualifier) throws IncorrectOperationException { |
| if (newQualifier == null) { |
| super.setQualifierExpression(newQualifier); |
| return; |
| } |
| final PsiExpression expression = getQualifierExpression(); |
| if (expression != null) { |
| expression.replace(newQualifier); |
| } else { |
| final PsiElement qualifier = getQualifier(); |
| if (qualifier != null) { |
| qualifier.replace(newQualifier); |
| } |
| } |
| } |
| |
| @Override |
| public int getChildRole(ASTNode child) { |
| final IElementType elType = child.getElementType(); |
| if (elType == JavaTokenType.DOUBLE_COLON) { |
| return ChildRole.DOUBLE_COLON; |
| } else if (elType == JavaTokenType.IDENTIFIER) { |
| return ChildRole.REFERENCE_NAME; |
| } else if (elType == JavaElementType.REFERENCE_EXPRESSION) { |
| return ChildRole.CLASS_REFERENCE; |
| } |
| return ChildRole.EXPRESSION; |
| } |
| |
| @NotNull |
| @Override |
| public JavaResolveResult[] multiResolve(final boolean incompleteCode) { |
| FileElement fileElement = SharedImplUtil.findFileElement(this); |
| if (fileElement == null) { |
| LOG.error("fileElement == null!"); |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| final PsiManagerEx manager = fileElement.getManager(); |
| if (manager == null) { |
| LOG.error("getManager() == null!"); |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| PsiFile file = SharedImplUtil.getContainingFile(fileElement); |
| boolean valid = file != null && file.isValid(); |
| if (!valid) { |
| LOG.error("invalid!"); |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| final MethodReferenceResolver resolver = new MethodReferenceResolver(); |
| final Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.ourRefs.get(); |
| if (map != null && map.containsKey(this)) { |
| return (JavaResolveResult[])resolver.resolve(this, incompleteCode); |
| } |
| ResolveResult[] results = ResolveCache.getInstance(getProject()).resolveWithCaching(this, resolver, true, incompleteCode,file); |
| return results.length == 0 ? JavaResolveResult.EMPTY_ARRAY : (JavaResolveResult[])results; |
| } |
| |
| @Override |
| public PsiElement getQualifier() { |
| final PsiElement element = getFirstChild(); |
| return element instanceof PsiExpression || element instanceof PsiTypeElement ? element : null; |
| } |
| |
| @Override |
| public TextRange getRangeInElement() { |
| final PsiElement element = getReferenceNameElement(); |
| if (element != null) { |
| final int offsetInParent = element.getStartOffsetInParent(); |
| return new TextRange(offsetInParent, offsetInParent + element.getTextLength()); |
| } |
| final PsiElement colons = findPsiChildByType(JavaTokenType.DOUBLE_COLON); |
| if (colons != null) { |
| final int offsetInParent = colons.getStartOffsetInParent(); |
| return new TextRange(offsetInParent, offsetInParent + colons.getTextLength()); |
| } |
| LOG.error(getText()); |
| return null; |
| } |
| |
| @NotNull |
| @Override |
| public String getCanonicalText() { |
| return getText(); |
| } |
| |
| @Override |
| public boolean isReferenceTo(final PsiElement element) { |
| if (!(element instanceof PsiMethod)) return false; |
| final PsiMethod method = (PsiMethod)element; |
| |
| final PsiElement nameElement = getReferenceNameElement(); |
| if (nameElement instanceof PsiIdentifier) { |
| if (!nameElement.getText().equals(method.getName())) return false; |
| } |
| else if (PsiUtil.isJavaToken(nameElement, JavaTokenType.NEW_KEYWORD)) { |
| if (!method.isConstructor()) return false; |
| } |
| |
| return element.getManager().areElementsEquivalent(element, resolve()); |
| } |
| |
| @Override |
| public void accept(@NotNull final PsiElementVisitor visitor) { |
| if (visitor instanceof JavaElementVisitor) { |
| ((JavaElementVisitor)visitor).visitMethodReferenceExpression(this); |
| } |
| else { |
| visitor.visitElement(this); |
| } |
| } |
| |
| @Override |
| public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException { |
| return this; |
| } |
| |
| @Override |
| public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { |
| PsiElement oldIdentifier = findChildByRoleAsPsiElement(ChildRole.REFERENCE_NAME); |
| if (oldIdentifier == null) { |
| oldIdentifier = findChildByRoleAsPsiElement(ChildRole.CLASS_REFERENCE); |
| } |
| if (oldIdentifier == null) { |
| throw new IncorrectOperationException(); |
| } |
| final String oldRefName = oldIdentifier.getText(); |
| if (PsiKeyword.THIS.equals(oldRefName) || |
| PsiKeyword.SUPER.equals(oldRefName) || |
| PsiKeyword.NEW.equals(oldRefName) || |
| Comparing.strEqual(oldRefName, newElementName)) { |
| return this; |
| } |
| PsiIdentifier identifier = JavaPsiFacade.getInstance(getProject()).getElementFactory().createIdentifier(newElementName); |
| oldIdentifier.replace(identifier); |
| return this; |
| } |
| |
| @Override |
| public String toString() { |
| return "PsiMethodReferenceExpression:" + getText(); |
| } |
| |
| private boolean isLocatedInStaticContext(PsiClass containingClass) { |
| final PsiClass gContainingClass = containingClass.getContainingClass(); |
| if (gContainingClass == null || !containingClass.hasModifierProperty(PsiModifier.STATIC)) { |
| PsiClass aClass = null; |
| if (PsiTreeUtil.isAncestor(gContainingClass != null ? gContainingClass : containingClass, this, false)) { |
| aClass = gContainingClass != null ? gContainingClass : containingClass; |
| } |
| if (PsiUtil.getEnclosingStaticElement(this, aClass) != null) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private class MethodReferenceResolver implements ResolveCache.PolyVariantResolver<PsiJavaReference> { |
| @NotNull |
| @Override |
| public ResolveResult[] resolve(@NotNull PsiJavaReference reference, boolean incompleteCode) { |
| final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(PsiMethodReferenceExpressionImpl.this); |
| |
| final PsiClass containingClass = qualifierResolveResult.getContainingClass(); |
| PsiSubstitutor substitutor = qualifierResolveResult.getSubstitutor(); |
| |
| if (containingClass != null) { |
| final PsiElement element = getReferenceNameElement(); |
| final boolean isConstructor = element instanceof PsiKeyword && PsiKeyword.NEW.equals(element.getText()); |
| if (element instanceof PsiIdentifier || isConstructor) { |
| if (isConstructor && (containingClass.isEnum() || containingClass.hasModifierProperty(PsiModifier.ABSTRACT))) { |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| PsiType functionalInterfaceType = null; |
| final Map<PsiMethodReferenceExpression,PsiType> map = PsiMethodReferenceUtil.ourRefs.get(); |
| if (map != null) { |
| functionalInterfaceType = map.get(PsiMethodReferenceExpressionImpl.this); |
| } |
| if (functionalInterfaceType == null) { |
| functionalInterfaceType = getFunctionalInterfaceType(); |
| } |
| final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); |
| final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); |
| final MethodSignature signature = interfaceMethod != null ? interfaceMethod.getSignature(LambdaUtil.getSubstitutor(interfaceMethod, resolveResult)) : null; |
| final PsiType interfaceMethodReturnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType); |
| PsiFile containingFile = getContainingFile(); |
| final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(containingFile); |
| if (isConstructor && interfaceMethod != null) { |
| final PsiTypeParameter[] typeParameters = containingClass.getTypeParameters(); |
| final boolean isRawSubst = PsiUtil.isRawSubstitutor(containingClass, substitutor); |
| Project project = containingClass.getProject(); |
| final PsiClassType returnType = JavaPsiFacade.getElementFactory(project).createType(containingClass, |
| isRawSubst ? PsiSubstitutor.EMPTY : substitutor); |
| |
| substitutor = LambdaUtil.inferFromReturnType(typeParameters, returnType, interfaceMethodReturnType, substitutor, languageLevel, |
| project); |
| |
| if (containingClass.getConstructors().length == 0) { |
| ClassCandidateInfo candidateInfo = null; |
| if ((containingClass.getContainingClass() == null || !isLocatedInStaticContext(containingClass)) && signature.getParameterTypes().length == 0 || |
| PsiMethodReferenceUtil.onArrayType(containingClass, signature)) { |
| candidateInfo = new ClassCandidateInfo(containingClass, substitutor); |
| } |
| return candidateInfo == null ? JavaResolveResult.EMPTY_ARRAY : new JavaResolveResult[]{candidateInfo}; |
| } |
| } |
| |
| final MethodReferenceConflictResolver conflictResolver = |
| new MethodReferenceConflictResolver(qualifierResolveResult, signature, interfaceMethod != null && interfaceMethod.isVarArgs()); |
| final PsiConflictResolver[] resolvers; |
| if (signature != null) { |
| final PsiType[] parameterTypes = signature.getParameterTypes(); |
| resolvers = new PsiConflictResolver[]{conflictResolver, new MethodRefsSpecificResolver(parameterTypes, languageLevel)}; |
| } |
| else { |
| resolvers = new PsiConflictResolver[]{conflictResolver}; |
| } |
| final MethodCandidatesProcessor processor = |
| new MethodCandidatesProcessor(PsiMethodReferenceExpressionImpl.this, containingFile, resolvers, new SmartList<CandidateInfo>()) { |
| @Override |
| protected MethodCandidateInfo createCandidateInfo(final PsiMethod method, |
| final PsiSubstitutor substitutor, |
| final boolean staticProblem, |
| final boolean accessible) { |
| final PsiExpressionList argumentList = getArgumentList(); |
| return new MethodCandidateInfo(method, substitutor, !accessible, staticProblem, argumentList, myCurrentFileContext, |
| argumentList != null ? argumentList.getExpressionTypes() : null, getTypeArguments(), |
| getLanguageLevel()) { |
| @Override |
| public PsiSubstitutor inferTypeArguments(@NotNull ParameterTypeInferencePolicy policy, boolean includeReturnConstraint) { |
| return inferTypeArgumentsFromInterfaceMethod(signature, interfaceMethodReturnType, method, substitutor, languageLevel); |
| } |
| }; |
| } |
| }; |
| processor.setIsConstructor(isConstructor); |
| processor.setName(isConstructor ? containingClass.getName() : element.getText()); |
| final PsiExpression expression = getQualifierExpression(); |
| if (expression == null || !(expression.getType() instanceof PsiArrayType)) { |
| processor.setAccessClass(containingClass); |
| } |
| |
| if (qualifierResolveResult.isReferenceTypeQualified() && isLocatedInStaticContext(containingClass)) { |
| processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null); |
| } |
| ResolveState state = ResolveState.initial().put(PsiSubstitutor.KEY, substitutor); |
| containingClass.processDeclarations(processor, state, |
| PsiMethodReferenceExpressionImpl.this, |
| PsiMethodReferenceExpressionImpl.this); |
| return processor.getResult(); |
| } |
| } |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| |
| private PsiSubstitutor inferTypeArgumentsFromInterfaceMethod(@Nullable MethodSignature signature, |
| @Nullable PsiType interfaceMethodReturnType, |
| PsiMethod method, |
| PsiSubstitutor substitutor, |
| LanguageLevel languageLevel) { |
| if (signature == null) return PsiSubstitutor.EMPTY; |
| PsiType[] types = method.getSignature(PsiUtil.isRawSubstitutor(method, substitutor) ? PsiSubstitutor.EMPTY : substitutor).getParameterTypes(); |
| PsiType[] rightTypes = signature.getParameterTypes(); |
| if (!method.isVarArgs() || types.length == 0) { |
| if (types.length < rightTypes.length) { |
| return getSubstitutor(rightTypes[0]); |
| } else if (types.length > rightTypes.length) { |
| return getSubstitutor(types[0]); |
| } |
| } else { |
| if (rightTypes.length != types.length || rightTypes[rightTypes.length - 1].getArrayDimensions() != types[types.length-1].getArrayDimensions()) { |
| types[types.length - 1] = ((PsiArrayType)types[types.length - 1]).getComponentType(); |
| int min = Math.min(types.length, rightTypes.length); |
| types = Arrays.copyOf(types, min); |
| rightTypes = Arrays.copyOf(rightTypes, min); |
| } |
| } |
| |
| for (int i = 0; i < rightTypes.length; i++) { |
| rightTypes[i] = GenericsUtil.eliminateWildcards(rightTypes[i]); |
| } |
| final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(getProject()).getResolveHelper(); |
| PsiSubstitutor psiSubstitutor = resolveHelper.inferTypeArguments(method.getTypeParameters(), types, rightTypes, languageLevel); |
| psiSubstitutor = psiSubstitutor.putAll(substitutor); |
| if (method.isConstructor()) { |
| psiSubstitutor = psiSubstitutor.putAll(resolveHelper.inferTypeArguments(method.getContainingClass().getTypeParameters(), types, rightTypes, languageLevel)); |
| } |
| |
| return LambdaUtil.inferFromReturnType(method.getTypeParameters(), |
| psiSubstitutor.substitute(method.getReturnType()), |
| interfaceMethodReturnType, |
| psiSubstitutor, |
| languageLevel, getProject()); |
| } |
| |
| private PsiSubstitutor getSubstitutor(PsiType type) { |
| final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(GenericsUtil.eliminateWildcards(type)); |
| PsiSubstitutor psiSubstitutor = resolveResult.getSubstitutor(); |
| if (type instanceof PsiClassType) { |
| final PsiClass psiClass = resolveResult.getElement(); |
| if (psiClass instanceof PsiTypeParameter) { |
| for (PsiClass aClass : psiClass.getSupers()) { |
| psiSubstitutor = psiSubstitutor.putAll(TypeConversionUtil.getSuperClassSubstitutor(aClass, (PsiClassType)type)); |
| } |
| } |
| } |
| return psiSubstitutor; |
| } |
| |
| private class MethodReferenceConflictResolver implements PsiConflictResolver { |
| private final PsiSubstitutor mySubstitutor; |
| private final MethodSignature mySignature; |
| private final PsiMethodReferenceUtil.QualifierResolveResult myQualifierResolveResult; |
| private final boolean myFunctionalMethodVarArgs; |
| |
| private MethodReferenceConflictResolver(PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult, |
| @Nullable MethodSignature signature, boolean varArgs) { |
| myQualifierResolveResult = qualifierResolveResult; |
| myFunctionalMethodVarArgs = varArgs; |
| mySubstitutor = qualifierResolveResult.getSubstitutor(); |
| mySignature = signature; |
| } |
| |
| @Nullable |
| @Override |
| public CandidateInfo resolveConflict(@NotNull List<CandidateInfo> conflicts) { |
| if (mySignature == null) return null; |
| |
| final PsiType[] parameterTypes = mySignature.getParameterTypes(); |
| boolean hasReceiver = PsiMethodReferenceUtil.hasReceiver(parameterTypes, myQualifierResolveResult, |
| PsiMethodReferenceExpressionImpl.this); |
| |
| final List<CandidateInfo> firstCandidates = new ArrayList<CandidateInfo>(); |
| final List<CandidateInfo> secondCandidates = new ArrayList<CandidateInfo>(); |
| for (CandidateInfo conflict : conflicts) { |
| if (!(conflict instanceof MethodCandidateInfo)) continue; |
| final PsiMethod psiMethod = ((MethodCandidateInfo)conflict).getElement(); |
| if (psiMethod == null) continue; |
| PsiSubstitutor subst = PsiSubstitutor.EMPTY; |
| subst = subst.putAll(mySubstitutor); |
| subst = subst.putAll(conflict.getSubstitutor()); |
| final PsiType[] signatureParameterTypes2 = psiMethod.getSignature(subst).getParameterTypes(); |
| |
| final boolean varArgs = psiMethod.isVarArgs(); |
| |
| if (parameterTypes.length == signatureParameterTypes2.length || (varArgs && !myFunctionalMethodVarArgs)) { |
| boolean correct = true; |
| for (int i = 0; i < parameterTypes.length; i++) { |
| final PsiType type1 = subst.substitute(GenericsUtil.eliminateWildcards(parameterTypes[i])); |
| if (varArgs && i >= signatureParameterTypes2.length - 1) { |
| final PsiType type2 = signatureParameterTypes2[signatureParameterTypes2.length - 1]; |
| correct &= TypeConversionUtil.isAssignable(type2, type1) || TypeConversionUtil.isAssignable(((PsiArrayType)type2).getComponentType(), type1); |
| } |
| else { |
| correct &= TypeConversionUtil.isAssignable(signatureParameterTypes2[i], type1); |
| } |
| } |
| if (correct) { |
| firstCandidates.add(conflict); |
| } |
| } |
| |
| if (hasReceiver && parameterTypes.length == signatureParameterTypes2.length + 1) { |
| boolean correct = true; |
| for (int i = 0; i < signatureParameterTypes2.length; i++) { |
| final PsiType type1 = subst.substitute(GenericsUtil.eliminateWildcards(parameterTypes[i + 1])); |
| final PsiType type2 = signatureParameterTypes2[i]; |
| final boolean assignable = TypeConversionUtil.isAssignable(type2, type1); |
| if (varArgs && i == signatureParameterTypes2.length - 1) { |
| correct &= assignable || TypeConversionUtil.isAssignable(((PsiArrayType)type2).getComponentType(), type1); |
| } |
| else { |
| correct &= assignable; |
| } |
| } |
| if (correct) { |
| secondCandidates.add(conflict); |
| } |
| } |
| } |
| |
| final int acceptedCount = secondCandidates.size() + firstCandidates.size(); |
| if (acceptedCount != 1) { |
| if (acceptedCount == 0) { |
| conflicts.clear(); |
| } |
| firstCandidates.addAll(secondCandidates); |
| conflicts.clear(); |
| conflicts.addAll(firstCandidates); |
| return null; |
| } |
| return !firstCandidates.isEmpty() ? firstCandidates.get(0) : secondCandidates.get(0); |
| } |
| } |
| |
| private class MethodRefsSpecificResolver extends JavaMethodsConflictResolver { |
| public MethodRefsSpecificResolver(@NotNull PsiType[] parameterTypes, @NotNull LanguageLevel languageLevel) { |
| super(PsiMethodReferenceExpressionImpl.this, parameterTypes, languageLevel); |
| } |
| |
| @Override |
| public CandidateInfo resolveConflict(@NotNull List<CandidateInfo> conflicts) { |
| boolean varargs = false; |
| for (CandidateInfo conflict : conflicts) { |
| final PsiElement psiElement = conflict.getElement(); |
| if (psiElement instanceof PsiMethod && ((PsiMethod)psiElement).isVarArgs()) { |
| varargs = true; |
| break; |
| } |
| } |
| checkSpecifics(conflicts, |
| varargs ? MethodCandidateInfo.ApplicabilityLevel.VARARGS : MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY, |
| myLanguageLevel); |
| return conflicts.size() == 1 ? conflicts.get(0) : null; |
| } |
| |
| @Override |
| protected boolean checkSameConflicts(CandidateInfo method, CandidateInfo conflict) { |
| return method == conflict; |
| } |
| } |
| } |
| } |