/*
* Copyright 2011 The authors
* 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.struts2.annotators;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder;
import com.intellij.jsp.impl.TldDescriptor;
import com.intellij.lang.jsp.JspFileViewProvider;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.jsp.JspFile;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlTag;
import com.intellij.struts2.StrutsBundle;
import com.intellij.struts2.StrutsConstants;
import com.intellij.struts2.StrutsIcons;
import com.intellij.struts2.dom.struts.action.Action;
import com.intellij.struts2.dom.struts.model.StrutsManager;
import com.intellij.struts2.dom.struts.model.StrutsModel;
import com.intellij.struts2.facet.StrutsFacet;
import com.intellij.util.NullableFunction;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.XmlNSDescriptor;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Annotates custom tags with "action" attribute.
*
* @author Yann Cébron
*/
public class JspActionAnnotator implements LineMarkerProvider {
@NonNls
private static final String ACTION_ATTRIBUTE_NAME = "action";
@NonNls
private static final String[] TAGS_WITH_ACTION_ATTRIBUTE = new String[]{"action", "form", "reset", "submit", "url"};
private static final NullableFunction<Action, PsiMethod> ACTION_METHOD_FUNCTION = new NullableFunction<Action, PsiMethod>() {
public PsiMethod fun(final Action action) {
return action.searchActionMethod();
}
};
@Override
public LineMarkerInfo getLineMarkerInfo(final PsiElement psiElement) {
return null;
}
@Override
public void collectSlowLineMarkers(final List<PsiElement> psiElements,
final Collection<LineMarkerInfo> lineMarkerInfos) {
if (psiElements.isEmpty()) {
return;
}
for (final PsiElement element : psiElements) {
annotate(element, lineMarkerInfos);
}
}
private void annotate(@NotNull final PsiElement element,
@NotNull final Collection<LineMarkerInfo> lineMarkerInfos) {
if (!(element instanceof XmlTag)) {
return;
}
// short exit when Struts 2 facet not present
if (StrutsFacet.getInstance(element) == null) {
return;
}
final XmlTag xmlTag = (XmlTag) element;
// any of our tags?
final String tagName = xmlTag.getLocalName();
if (Arrays.binarySearch(TAGS_WITH_ACTION_ATTRIBUTE, tagName) < 0) {
return;
}
// determine Struts 2 taglib prefix
final String uiTaglibPrefix = getUITaglibPrefix(xmlTag);
if (uiTaglibPrefix == null ||
!Comparing.equal(xmlTag.getNamespacePrefix(), uiTaglibPrefix)) {
return;
}
// special case for <action>
final String actionPath = Comparing.equal(tagName, ACTION_ATTRIBUTE_NAME) ?
xmlTag.getAttributeValue("name") :
xmlTag.getAttributeValue(ACTION_ATTRIBUTE_NAME);
if (actionPath == null) {
return;
}
final StrutsModel strutsModel = StrutsManager.getInstance(element.getProject())
.getCombinedModel(ModuleUtil.findModuleForPsiElement(element));
if (strutsModel == null) {
return;
}
final String namespace = xmlTag.getAttributeValue("namespace");
final List<Action> actions = strutsModel.findActionsByName(actionPath, namespace);
if (actions.isEmpty()) {
return;
}
// resolve to action method should be exactly 1
final NavigationGutterIconBuilder<PsiElement> gutterIconBuilder =
NavigationGutterIconBuilder.create(StrutsIcons.ACTION_CLASS).
setTooltipText(StrutsBundle.message("annotators.jsp.goto.action.method")).
setEmptyPopupText(StrutsBundle.message("annotators.jsp.goto.action.method.notfound")).
setTargets(new NotNullLazyValue<Collection<? extends PsiElement>>() {
@NotNull
protected Collection<PsiMethod> compute() {
return ContainerUtil.mapNotNull(actions, ACTION_METHOD_FUNCTION);
}
});
lineMarkerInfos.add(gutterIconBuilder.createLineMarkerInfo(xmlTag));
}
// TODO cache in JspFile's UserData?
@Nullable
private static String getUITaglibPrefix(final XmlTag xmlTag) {
final PsiFile containingFile = xmlTag.getContainingFile();
if (!(containingFile instanceof JspFile)) {
return null;
}
final JspFile jspFile = (JspFile) containingFile;
final XmlDocument document = jspFile.getDocument();
if (document == null) {
return null;
}
final XmlTag rootTag = document.getRootTag();
if (rootTag == null) {
return null;
}
final Set<String> knownTaglibPrefixes = ((JspFileViewProvider) jspFile.getViewProvider()).getKnownTaglibPrefixes();
return ContainerUtil.find(knownTaglibPrefixes, new Condition<String>() {
public boolean value(final String s) {
final String namespaceByPrefix = rootTag.getNamespaceByPrefix(s);
final XmlNSDescriptor descriptor = rootTag.getNSDescriptor(namespaceByPrefix, true);
if (descriptor instanceof TldDescriptor) {
final String uri = ((TldDescriptor) descriptor).getUri(); // URI is optional in TLD!
return Comparing.equal(uri, StrutsConstants.TAGLIB_STRUTS_UI_URI);
}
return false;
}
});
}
}