/* * 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.RelatedItemLineMarkerInfo; import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider; import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; import com.intellij.navigation.GotoRelatedItem; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.paths.PathReference; import com.intellij.psi.*; import com.intellij.psi.xml.XmlFile; import com.intellij.struts2.StrutsBundle; import com.intellij.struts2.StrutsIcons; import com.intellij.struts2.dom.struts.action.Action; import com.intellij.struts2.dom.struts.action.Result; import com.intellij.struts2.dom.struts.model.StrutsManager; import com.intellij.struts2.dom.struts.model.StrutsModel; import com.intellij.struts2.dom.validator.ValidatorManager; import com.intellij.struts2.facet.StrutsFacet; import com.intellij.util.NotNullFunction; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xml.DomElement; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; /** * Base class for annotating Action-Classes. * Provides gutter icon/Go To Related File-navigation to <action> declaration(s) in struts.xml, * corresponding {@code validation.xml}-files and navigation to result(s) from action methods. * * @author Yann Cébron */ abstract class ActionAnnotatorBase extends RelatedItemLineMarkerProvider { private static final DomElementListCellRenderer ACTION_RENDERER = new DomElementListCellRenderer<Action>(StrutsBundle.message("annotators.action.noname")) { @NotNull @NonNls public String getAdditionalLocation(final Action action) { return action != null ? "[" + action.getNamespace() + "] " : ""; } }; private static final NotNullFunction<PathReference, Collection<? extends PsiElement>> PATH_REFERENCE_CONVERTER = new NotNullFunction<PathReference, Collection<? extends PsiElement>>() { @NotNull @Override public Collection<? extends PsiElement> fun(final PathReference pathReference) { final PsiElement resolve = pathReference.resolve(); return resolve != null ? Collections.singleton(resolve) : Collections.<PsiElement>emptyList(); } }; private static final NotNullFunction<PathReference, Collection<? extends GotoRelatedItem>> PATH_REFERENCE_GOTO_RELATED_ITEM_PROVIDER = new NotNullFunction<PathReference, Collection<? extends GotoRelatedItem>>() { @NotNull @Override public Collection<? extends GotoRelatedItem> fun(final PathReference pathReference) { final PsiElement resolve = pathReference.resolve(); return resolve != null ? Collections.singleton(new GotoRelatedItem(resolve) { @Override public Icon getCustomIcon() { return pathReference.getIcon(); } @Override public String getCustomName() { return pathReference.getPath(); } }) : Collections.<GotoRelatedItem>emptyList(); } }; /** * Determine the Action-PsiClass. * * @param psiElement Passed from annotator. * @return null if PsiClass cannot be determined or is not suitable. */ @Nullable protected abstract PsiClass getActionPsiClass(@NotNull final PsiElement psiElement); @Override protected void collectNavigationMarkers(final @NotNull PsiElement element, final Collection<? super RelatedItemLineMarkerInfo> lineMarkerInfos) { if (!(element instanceof PsiIdentifier)) return; final PsiClass clazz = getActionPsiClass(element.getParent()); if (clazz == null || clazz.getNameIdentifier() != element) { return; } // do not run on non-public, abstract classes or interfaces if (clazz.isInterface() || clazz.isAnnotationType() || !clazz.hasModifierProperty(PsiModifier.PUBLIC) || clazz.hasModifierProperty(PsiModifier.ABSTRACT)) { return; } // short exit if Struts Facet not present final Module module = ModuleUtil.findModuleForPsiElement(clazz); if (module == null || StrutsFacet.getInstance(module) == null) { return; } final StrutsManager strutsManager = StrutsManager.getInstance(element.getProject()); final StrutsModel strutsModel = strutsManager.getCombinedModel(module); if (strutsModel == null) { return; } installValidationTargets(element, lineMarkerInfos, clazz); final List<Action> actions = strutsModel.findActionsByClass(clazz); if (actions.isEmpty()) { return; } installActionTargets(element, lineMarkerInfos, actions); installActionMethods(lineMarkerInfos, clazz, actions); } /** * Annotate action class to {@code <action>}-declarations. * * @param element Class element to annotate. * @param lineMarkerInfos Current line markers. * @param actions Corresponding Actions. */ private void installActionTargets(final PsiElement element, final Collection<? super RelatedItemLineMarkerInfo> lineMarkerInfos, final List<Action> actions) { final String tooltip = actions.size() == 1 ? StrutsBundle.message("annotators.action.goto.tooltip.single") : StrutsBundle.message("annotators.action.goto.tooltip"); final NavigationGutterIconBuilder<DomElement> gutterIconBuilder = NavigationGutterIconBuilder.create(StrutsIcons.ACTION, NavigationGutterIconBuilder.DEFAULT_DOM_CONVERTOR, NavigationGutterIconBuilder.DOM_GOTO_RELATED_ITEM_PROVIDER) .setPopupTitle(StrutsBundle.message("annotators.action.goto.declaration")) .setTargets(actions) .setTooltipTitle(tooltip) .setCellRenderer(ACTION_RENDERER); lineMarkerInfos.add(gutterIconBuilder.createLineMarkerInfo(element)); } /** * Annotate action-methods of this class with result(s). * * @param lineMarkerInfos Current line markers. * @param clazz Class to annotate. * @param actions Corresponding Actions. */ private void installActionMethods(final Collection<? super RelatedItemLineMarkerInfo> lineMarkerInfos, final PsiClass clazz, final List<Action> actions) { final Map<PsiMethod, Set<PathReference>> pathReferenceMap = new HashMap<PsiMethod, Set<PathReference>>(); for (final Action action : actions) { final PsiMethod method = action.searchActionMethod(); if (method == null || !clazz.equals(method.getContainingClass())) { continue; } final Set<PathReference> pathReferences = new HashSet<PathReference>(); final List<Result> results = action.getResults(); for (final Result result : results) { final PathReference pathReference = result.getValue(); ContainerUtil.addIfNotNull(pathReferences, pathReference); } final Set<PathReference> toStore = ContainerUtil.getOrCreate(pathReferenceMap, method, new HashSet<PathReference>()); toStore.addAll(pathReferences); pathReferenceMap.put(method, toStore); } for (final Map.Entry<PsiMethod, Set<PathReference>> entries : pathReferenceMap.entrySet()) { final NavigationGutterIconBuilder<PathReference> gutterIconBuilder = NavigationGutterIconBuilder.create(StrutsIcons.RESULT, PATH_REFERENCE_CONVERTER, PATH_REFERENCE_GOTO_RELATED_ITEM_PROVIDER) .setPopupTitle(StrutsBundle.message("annotators.action.goto.result")) .setTargets(entries.getValue()) .setTooltipTitle(StrutsBundle.message("annotators.action.goto.result.tooltip")); lineMarkerInfos.add(gutterIconBuilder.createLineMarkerInfo(entries.getKey())); } } /** * Related {@code validation.xml} files. * * @param element Class element to annotate. * @param lineMarkerInfos Current line markers. * @param clazz Class to find validation files for. */ private void installValidationTargets(final PsiElement element, final Collection<? super RelatedItemLineMarkerInfo> lineMarkerInfos, final PsiClass clazz) { final List<XmlFile> files = ValidatorManager.getInstance(element.getProject()).findValidationFilesFor(clazz); if (files.isEmpty()) { return; } final NavigationGutterIconBuilder<PsiElement> validatorBuilder = NavigationGutterIconBuilder.create(StrutsIcons.VALIDATION_CONFIG_FILE) .setTargets(files) .setPopupTitle(StrutsBundle.message("annotators.action.goto.validation")) .setTooltipTitle(StrutsBundle.message("annotators.action.goto.validation.tooltip")); lineMarkerInfos.add(validatorBuilder.createLineMarkerInfo(element)); } }