package net.jangaroo.ide.idea.exml; import com.intellij.find.FindManager; import com.intellij.find.findUsages.FindUsagesHandler; import com.intellij.find.findUsages.FindUsagesHandlerFactory; import com.intellij.find.findUsages.FindUsagesManager; import com.intellij.find.findUsages.FindUsagesOptions; import com.intellij.find.impl.FindManagerImpl; import com.intellij.lang.javascript.psi.JSFunction; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; import com.intellij.psi.PsiReference; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.search.SearchScope; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.usageView.UsageInfo; import com.intellij.util.Processor; import com.intellij.xml.util.XmlPsiUtil; import com.intellij.xml.util.XmlUtil; import net.jangaroo.exml.api.Exmlc; import net.jangaroo.ide.idea.Utils; import net.jangaroo.ide.idea.jps.exml.ExmlcConfigurationBean; import net.jangaroo.ide.idea.jps.util.IdeaFileUtils; import net.jangaroo.utils.CompilerUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; /** * Find usages of EXML elements by the EXML file that defines the element. */ public class FindExmlUsagesHandlerFactory extends FindUsagesHandlerFactory { @Override public boolean canFindUsages(@NotNull PsiElement element) { if (element.getContainingFile() == null || !element.getContainingFile().getName().endsWith(Exmlc.EXML_SUFFIX)) { return false; } if (element instanceof XmlFile) { // find EXML usages of the EXML element defined in this file! return true; } // check for <exml:cfg name="foo" ...> if (element instanceof XmlAttributeValue) { XmlAttribute xmlAttribute = (XmlAttribute)element.getParent(); if (Exmlc.EXML_CFG_NAME_ATTRIBUTE.equals(xmlAttribute.getLocalName())) { return hasQName(xmlAttribute.getParent(), Exmlc.EXML_NAMESPACE_URI, Exmlc.EXML_CFG_NODE_NAME); } } return false; } private static boolean hasQName(XmlTag xmlTag, String namespace, String localName) { return namespace.equals(xmlTag.getNamespace()) && localName.equals(xmlTag.getLocalName()); } @Nullable @Override public FindUsagesHandler createFindUsagesHandler(@NotNull PsiElement element, boolean forHighlightUsages) { if (canFindUsages(element)) { // find module: VirtualFile exmlFile = element.getContainingFile().getVirtualFile(); Project project = element.getProject(); final Module module = ProjectRootManager.getInstance(project).getFileIndex().getModuleForFile(exmlFile); if (module != null) { // find EXML facet configuration: ExmlFacet exmlFacet = ExmlFacet.ofModule(module); if (exmlFacet != null) { ExmlcConfigurationBean exmlConfiguration = exmlFacet.getConfiguration().getState(); // find generated XSD file of this EXML module: String configClassPackage = exmlConfiguration.getConfigClassPackage(); String generatedResourcesDirectory = exmlConfiguration.getGeneratedResourcesDirectory(); VirtualFile xsdFile = VfsUtil.findFileByIoFile(new File(IdeaFileUtils.toPath(generatedResourcesDirectory), configClassPackage + XSD.FILE_SUFFIX), true); if (xsdFile != null) { XmlFile xsdPsiFile = (XmlFile)PsiManager.getInstance(project).findFile(xsdFile); if (xsdPsiFile != null) { XmlTag rootTag = xsdPsiFile.getRootTag(); if (rootTag != null) { // collect all generated declarations to find usages of: final List<PsiElement> generatedDeclarations = new ArrayList<PsiElement>(3); String elementName = CompilerUtils.uncapitalize(exmlFile.getNameWithoutExtension()); String configClassName = CompilerUtils.qName(configClassPackage, elementName); if (element instanceof XmlFile) { addGeneratedDeclarationsOfExmlFile(rootTag, elementName, configClassName, generatedDeclarations); } else { addGeneratedDeclarationsOfExmlCfgElement((XmlAttributeValue)element, rootTag, configClassName, generatedDeclarations); } return getFindUsageHandler(generatedDeclarations, element, forHighlightUsages); } } } } } } return null; } private FindUsagesHandler getFindUsageHandler(List<PsiElement> generatedDeclarations, PsiElement element, boolean forHighlightUsages) { if (generatedDeclarations.isEmpty()) { return null; } FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(element.getProject())).getFindUsagesManager(); List<FindUsagesHandler> delegateFindUsageHandlers = new ArrayList<FindUsagesHandler>(3); for (PsiElement psiElement : generatedDeclarations) { delegateFindUsageHandlers.add(findUsagesManager.getFindUsagesHandler(psiElement, forHighlightUsages)); } return delegateFindUsageHandlers.size() == 1 ? delegateFindUsageHandlers.get(0) : new DelegatingFindUsagesHandler(element, delegateFindUsageHandlers); } private void addGeneratedDeclarationsOfExmlCfgElement(XmlAttributeValue element, XmlTag rootTag, String configClassName, final List<PsiElement> generatedDeclarations) { final String attributeName = element.getValue(); JSClass configClass = Utils.getActionScriptClass(element, configClassName); if (configClass != null) { JSFunction attributeSetter = configClass.findFunctionByNameAndKind(attributeName, JSFunction.FunctionKind.SETTER); if (attributeSetter != null) { generatedDeclarations.add(attributeSetter); } JSFunction attributeGetter = configClass.findFunctionByNameAndKind(attributeName, JSFunction.FunctionKind.GETTER); if (attributeGetter != null) { generatedDeclarations.add(attributeGetter); } } final int generatedDeclarationsSize = generatedDeclarations.size(); XmlTag[] typeDeclarations = rootTag.findSubTags(XSD.COMPLEX_TYPE_ELEMENT_NAME, XmlUtil.XML_SCHEMA_URI); for (XmlTag typeDeclaration : typeDeclarations) { if (configClassName.equals(typeDeclaration.getAttributeValue(XSD.NAME_ATTRIBUTE_NAME))) { // find attribute and element declaration XmlPsiUtil.processXmlElementChildren(typeDeclaration, new PsiElementProcessor() { @Override public boolean execute(@NotNull PsiElement element) { XmlTag xmlTag = null; if (element instanceof XmlAttributeValue) { XmlAttribute xmlAttribute = (XmlAttribute)element.getParent(); if (XSD.NAME_ATTRIBUTE_NAME.equals(xmlAttribute.getLocalName()) && attributeName.equals(xmlAttribute.getValue())) { if (hasQName(xmlAttribute.getParent(), XmlUtil.XML_SCHEMA_URI, XSD.ATTRIBUTE_ELEMENT_NAME)) { xmlTag = xmlAttribute.getParent(); } } } else if (element instanceof XmlTag && hasQName((XmlTag)element, XmlUtil.XML_SCHEMA_URI, XSD.ELEMENT_ELEMENT_NAME)) { xmlTag = (XmlTag)element; } if (xmlTag != null && attributeName.equals(xmlTag.getAttributeValue(XSD.NAME_ATTRIBUTE_NAME))) { generatedDeclarations.add(xmlTag); } return generatedDeclarations.size() < generatedDeclarationsSize + 2; } }, true); break; } } } private void addGeneratedDeclarationsOfExmlFile(XmlTag xsdRootTag, String elementName, String configClassName, List<PsiElement> generatedDeclarations) { JSClass configClass = Utils.getActionScriptClass(xsdRootTag, configClassName); if (configClass != null) { String targetClassName = ExmlElementGotoDeclarationHandler.findTargetClassName(configClass); if (targetClassName != null) { JSClass targetClass = Utils.getActionScriptClass(configClass, targetClassName); if (targetClass != null) { generatedDeclarations.add(targetClass); } } generatedDeclarations.add(configClass); } // find <xs:element name="<elementName>" ...> in xsdPsiFile and find its usages! XmlTag[] elementDeclarations = xsdRootTag.findSubTags("element", XmlUtil.XML_SCHEMA_URI); for (XmlTag elementDeclaration : elementDeclarations) { if (elementName.equals(elementDeclaration.getAttributeValue("name"))) { generatedDeclarations.add(elementDeclaration); break; } } } private static class DelegatingFindUsagesHandler extends FindUsagesHandler { private List<FindUsagesHandler> delegates; private DelegatingFindUsagesHandler(@NotNull PsiElement psiElement, List<FindUsagesHandler> delegates) { super(psiElement); this.delegates = delegates; } @NotNull @Override public PsiElement[] getPrimaryElements() { List<PsiElement> result = new ArrayList<PsiElement>(); for (FindUsagesHandler delegate : delegates) { result.addAll(Arrays.asList(delegate.getPrimaryElements())); } return result.toArray(new PsiElement[result.size()]); } @NotNull @Override public PsiElement[] getSecondaryElements() { List<PsiElement> result = new ArrayList<PsiElement>(); for (FindUsagesHandler delegate : delegates) { result.addAll(Arrays.asList(delegate.getSecondaryElements())); } return result.toArray(new PsiElement[result.size()]); } @Override public boolean processElementUsages(@NotNull PsiElement element, @NotNull Processor<UsageInfo> processor, @NotNull FindUsagesOptions options) { boolean result = true; for (FindUsagesHandler delegate : delegates) { result = result && delegate.processElementUsages(element, processor, options); } return result; } @Override public boolean processUsagesInText(@NotNull PsiElement element, @NotNull Processor<UsageInfo> processor, @NotNull GlobalSearchScope searchScope) { boolean result = true; for (FindUsagesHandler delegate : delegates) { result = result && delegate.processUsagesInText(element, processor, searchScope); } return result; } @Override public Collection<PsiReference> findReferencesToHighlight(@NotNull PsiElement target, @NotNull SearchScope searchScope) { List<PsiReference> result = new ArrayList<PsiReference>(); for (FindUsagesHandler delegate : delegates) { result.addAll((delegate.findReferencesToHighlight(target, searchScope))); } return result; } } }