package org.pdtextensions.semanticanalysis.validation.validator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.dltk.ast.declarations.MethodDeclaration;
import org.eclipse.dltk.ast.references.TypeReference;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.index2.search.ISearchEngine.MatchRule;
import org.eclipse.dltk.core.search.IDLTKSearchScope;
import org.eclipse.dltk.core.search.SearchEngine;
import org.eclipse.dltk.ti.types.IEvaluatedType;
import org.eclipse.php.core.compiler.PHPFlags;
import org.eclipse.php.core.compiler.ast.nodes.ClassDeclaration;
import org.eclipse.php.core.compiler.ast.nodes.FullyQualifiedReference;
import org.eclipse.php.internal.core.model.PerFileModelAccessCache;
import org.eclipse.php.internal.core.model.PHPModelAccess;
import org.eclipse.php.internal.core.typeinference.IModelAccessCache;
import org.eclipse.php.internal.core.typeinference.PHPModelUtils;
import org.eclipse.php.internal.core.typeinference.PHPTypeInferenceUtils;
import org.pdtextensions.core.log.Logger;
import org.pdtextensions.core.util.PDTModelUtils;
import org.pdtextensions.internal.semanticanalysis.validation.PEXProblemIdentifier;
import org.pdtextensions.semanticanalysis.validation.AbstractValidator;
import org.pdtextensions.semanticanalysis.validation.IValidatorContext;
import org.pdtextensions.semanticanalysis.validation.MissingMethodImplementation;
/**
*
* Checks ClassDeclarations for missing method implementations.
*
* TODO: Currently assumes one class per file. Refactor to handle multiple classes per file.
* TODO: Optimisation
*
* @author Robert Gruendler <r.gruendler@gmail.com>
*/
@SuppressWarnings("restriction")
public class ImplementationValidator extends AbstractValidator{
public static final String ID = "org.pdtextensions.semanticanalysis.validator.implementationValidator"; //$NON-NLS-1$
private static final String BACK_SLASH = "\\"; //$NON-NLS-1$
private IScriptProject project;
private ISourceModule sourceModule;
private ClassDeclaration classDeclaration;
private List<MissingMethodImplementation> missingMethods;
public ImplementationValidator() {
}
public ImplementationValidator(ISourceModule module) {
this.sourceModule = module;
this.project = module.getScriptProject();
}
@Override
public boolean visit(ClassDeclaration s) throws Exception {
this.classDeclaration = s;
missingMethods = new ArrayList<MissingMethodImplementation>();
if (getClassDeclaration().isAbstract()) {
return false;
}
List<IMethod> unimplemented = new ArrayList<IMethod>();
IDLTKSearchScope scope = SearchEngine.createSearchScope(project);
PHPModelAccess model = PHPModelAccess.getDefault();
IType nss = PHPModelUtils.getCurrentNamespace(sourceModule, getClassDeclaration().getNameStart());
String search = nss != null ? nss.getElementName() + BACK_SLASH + getClassDeclaration().getName() : getClassDeclaration().getName();
IType classType = context != null ? PDTModelUtils.findType(sourceModule, search) : null;
if (classType == null) {
for (IType t : PDTModelUtils.findTypes(project, search)) {
classType = t;
break;
}
if (classType == null) {
return false;
}
}
Map<String, IMethod> listImported = PDTModelUtils.getImportedMethods(classType);
Collection<TypeReference> interfaces = getClassDeclaration().getInterfaceList();
// iterate over all interfaces and check if the current class
// or any of the superclasses implements the method
if (listImported == null || interfaces == null) {
return true;
}
for (TypeReference interf : interfaces) {
if (interf instanceof FullyQualifiedReference) {
FullyQualifiedReference fqr = (FullyQualifiedReference) interf;
String name = null;
// we have a namespace
if (fqr.getNamespace() != null) {
name = fqr.getNamespace().getName() + BACK_SLASH + fqr.getName();
} else {
IEvaluatedType eval = PHPTypeInferenceUtils.resolveExpression(sourceModule, fqr);
if (eval != null) {
name = eval.getTypeName();
if (eval.getTypeName().startsWith(BACK_SLASH)) {
name = eval.getTypeName().replaceFirst("\\\\", "");
}
}
}
if (name == null) {
continue;
}
IType[] types;
if (PDTModelUtils.findType(sourceModule, name) != null) {
types = new IType[] {PDTModelUtils.findType(sourceModule, name)};
} else {
types = model.findTypes(name, MatchRule.EXACT, 0, 0, scope, new NullProgressMonitor());
}
if (types.length != 1) {
continue;
}
IType type = types[0];
try {
for (IMethod method : type.getMethods()) {
boolean implemented = false;
String methodSignature = PDTModelUtils.getMethodSignature(method);
if (methodSignature == null) {
continue;
}
IMethod[] ms = PHPModelUtils.getTypeMethod(classType, method.getElementName(), true);
for (IMethod me : ms) {
if (me.getParent().getElementName().equals(fqr.getName())) {
continue;
}
if (!PHPFlags.isAbstract(me.getFlags())) {
implemented = true;
}
}
for (MethodDeclaration typeMethod : getClassDeclaration().getMethods()) {
String signature = PDTModelUtils.getMethodSignature(typeMethod, project);
if (methodSignature.equals(signature) && !typeMethod.isAbstract()) {
implemented = true;
break;
}
}
if (!implemented) {
/*
* Trait searching, currently the best method that I found in PDT code //@zulus
*
* TODO Check real method signature (withoutName)
*/
for (Entry<String, IMethod> entry : listImported.entrySet()) {
if (entry.getKey().toLowerCase().equals(method.getElementName().toLowerCase()) && !PHPFlags.isAbstract(entry.getValue().getFlags())) {
implemented = true;
break;
}
}
}
if (implemented == false) {
unimplemented.add(method);
}
}
} catch (ModelException e) {
Logger.debug(e.getMessage());
e.printStackTrace();
} catch (CoreException e) {
Logger.debug(e.getMessage());
}
}
}
if (unimplemented.size() > 0) {
MissingMethodImplementation missing = new MissingMethodImplementation(getClassDeclaration(), unimplemented);
getMissing().add(missing);
}
return true;
}
public boolean hasMissing() {
return missingMethods != null && missingMethods.size() > 0;
}
public List<MissingMethodImplementation> getMissing() {
return missingMethods;
}
public void setMissingInterfaceImplemetations(
List<MissingMethodImplementation> missingInterfaceImplemetations) {
this.missingMethods = missingInterfaceImplemetations;
}
public ClassDeclaration getClassDeclaration() {
return classDeclaration;
}
@Override
public void validate(IValidatorContext context) throws Exception {
project = context.getProject();
sourceModule = context.getSourceModule();
super.validate(context);
if (hasMissing() && getClassDeclaration() != null) {
int start = getClassDeclaration().getNameStart();
int stop = getClassDeclaration().getNameEnd();
String message = "The class " + getClassDeclaration().getName() + " must implement the inherited abstract method "
+ getMissing().get(0).getFirstMethodName();
context.registerProblem(PEXProblemIdentifier.INTERFACE_RELATED, message, start, stop);
}
}
}