// Copyright 2015 ThoughtWorks, Inc.
// This file is part of getgauge/Intellij-plugin.
// getgauge/Intellij-plugin is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// getgauge/Intellij-plugin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with getgauge/Intellij-plugin. If not, see <http://www.gnu.org/licenses/>.
package com.thoughtworks.gauge.util;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.AnnotatedElementsSearch;
import com.thoughtworks.gauge.Constants;
import com.thoughtworks.gauge.connection.GaugeConnection;
import com.thoughtworks.gauge.Step;
import com.thoughtworks.gauge.StepValue;
import com.thoughtworks.gauge.core.Gauge;
import com.thoughtworks.gauge.language.psi.SpecPsiImplUtil;
import com.thoughtworks.gauge.language.psi.SpecStep;
import com.thoughtworks.gauge.language.psi.impl.ConceptConceptImpl;
import com.thoughtworks.gauge.language.psi.impl.ConceptStepImpl;
import com.thoughtworks.gauge.language.psi.impl.SpecStepImpl;
import com.thoughtworks.gauge.reference.ReferenceCache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import static com.thoughtworks.gauge.GaugeConstant.STEP_ANNOTATION_QUALIFIER;
public class StepUtil {
private static HashMap<String, StepValue> stepValueCache = new HashMap<>();
public static PsiElement findStepImpl(SpecStep step, Module module) {
if (module == null) {
return null;
}
ReferenceCache cache = Gauge.getReferenceCache(module);
PsiElement reference = cache.searchReferenceFor(step);
if (reference == null) {
reference = findStepReference(step, module);
}
return reference;
}
private static PsiElement findStepReference(SpecStep step, Module module) {
Collection<PsiMethod> stepMethods = getStepMethods(module);
PsiMethod method = findStepImplementationMethod(stepMethods, step, module);
PsiElement referenceElement;
if (method == null) {
referenceElement = searchConceptsForImpl(step, module);
if (referenceElement != null) {
referenceElement = new ConceptStepImpl(referenceElement.getNode(), true);
}
} else {
referenceElement = method;
}
addReferenceToCache(step, referenceElement, module);
return referenceElement;
}
private static void addReferenceToCache(SpecStep step, PsiElement referenceElement, Module module) {
Gauge.getReferenceCache(module).addStepReference(step, referenceElement);
}
private static PsiElement searchConceptsForImpl(SpecStep step, Module module) {
try {
VirtualFile[] conceptFiles = findConceptFiles(module);
if (conceptFiles.length > 0) {
PsiElement reference;
for (VirtualFile file : conceptFiles) {
reference = searchConceptReference(file, step, module);
if (reference != null) {
return reference;
}
}
}
} catch (Exception e) {
return null;
}
return null;
}
private static PsiElement searchConceptReference(VirtualFile virtualFile, SpecStep step, Module module) {
PsiFile psiFile = PsiManager.getInstance(module.getProject()).findFile(virtualFile);
if (psiFile == null || !psiFile.isValid()) {
return null;
}
for (PsiElement psiElement : psiFile.getChildren()) {
boolean isConcept = psiElement.getClass().equals(ConceptConceptImpl.class);
if (psiElement.getClass().equals(ConceptConceptImpl.class)) {
StepValue conceptStepValue = ((ConceptConceptImpl) psiElement).getStepValue();
if (isConcept && step.getStepValue().getStepText().equals(conceptStepValue.getStepText()))
return psiElement;
}
}
return null;
}
private static VirtualFile[] findConceptFiles(Module module) {
Collection<VirtualFile> conceptFiles = FilenameIndex.getAllFilesByExt(module.getProject(), Constants.CONCEPT_EXTENSION);
return conceptFiles.toArray(new VirtualFile[conceptFiles.size()]);
}
private static PsiMethod findStepImplementationMethod(Collection<PsiMethod> stepMethods, SpecStep step, Module module) {
String stepText = step.getStepValue().getStepText();
for (PsiMethod stepMethod : stepMethods) {
if (isMatch(stepMethod, stepText, module)) {
return stepMethod;
}
}
return null;
}
public static boolean isMatch(PsiMethod stepMethod, String stepText, Module module) {
List<String> annotationValues = getGaugeStepAnnotationValues(stepMethod);
for (String value : annotationValues)
if (SpecPsiImplUtil.getStepValueFor(module, stepMethod, value, false).getStepText().equals(stepText))
return true;
return false;
}
public static StepValue getStepValue(final GaugeConnection connection, final String text, Boolean hasInlineTable) {
String stepText = hasInlineTable ? text + " <table>" : text;
StepValue value = stepValueCache.get(stepText);
if (value == null || value.getStepText().isEmpty()) {
value = connection.getStepValue(text, hasInlineTable);
stepValueCache.put(stepText, value);
}
return value;
}
public static List<String> getGaugeStepAnnotationValues(PsiMethod stepMethod) {
final PsiModifierList modifierList = stepMethod.getModifierList();
final PsiAnnotation[] annotations = modifierList.getAnnotations();
List<String> values = new ArrayList<>();
for (PsiAnnotation annotation : annotations) {
values.addAll(getGaugeStepAnnotationValues(annotation));
}
return values;
}
private static List<String> getGaugeStepAnnotationValues(PsiAnnotation annotation) {
List<String> values = new ArrayList<>();
if (!isGaugeAnnotation(annotation)) {
return values;
}
PsiAnnotationMemberValue attributeValue = annotation.findAttributeValue("value");
Object value = JavaPsiFacade.getInstance(annotation.getProject()).getConstantEvaluationHelper().computeConstantExpression(attributeValue);
if (value != null && value instanceof String) {
values.add((String) value);
} else if (attributeValue instanceof PsiArrayInitializerMemberValue) {
PsiAnnotationMemberValue[] memberValues = ((PsiArrayInitializerMemberValue) attributeValue).getInitializers();
for (PsiAnnotationMemberValue memberValue : memberValues) {
Object val = JavaPsiFacade.getInstance(annotation.getProject()).getConstantEvaluationHelper().computeConstantExpression(memberValue);
if (val != null && val instanceof String) {
values.add((String) val);
}
}
}
return values;
}
private static boolean isGaugeAnnotation(PsiAnnotation annotation) {
return Step.class.getCanonicalName().equals(annotation.getQualifiedName());
}
public static Collection<PsiMethod> getStepMethods(Module module) {
final PsiClass step = JavaPsiFacade.getInstance(module.getProject()).findClass(STEP_ANNOTATION_QUALIFIER, GlobalSearchScope.allScope(module.getProject()));
if (step != null) {
Collection<PsiMethod> methods = new ArrayList<>();
for (Module m : Gauge.getSubModules(module))
methods.addAll(AnnotatedElementsSearch.searchPsiMethods(step, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(m, true)).findAll());
return methods;
}
return new ArrayList<>();
}
public static boolean isImplementedStep(SpecStep step, Module module) {
return findStepImpl(step, module) != null;
}
public static boolean isStep(PsiElement element) {
return element instanceof SpecStepImpl;
}
public static boolean isConcept(PsiElement element) {
return element instanceof ConceptStepImpl;
}
public static boolean isMethod(PsiElement element) {
return element instanceof PsiMethod;
}
}