package org.jetbrains.idea.maven.plugins.api; import com.intellij.lang.Language; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.psi.PsiReferenceProvider; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlText; import com.intellij.psi.xml.XmlTokenType; import com.intellij.util.ProcessingContext; import com.intellij.util.SmartList; import com.intellij.util.xml.DomElement; import com.intellij.util.xml.DomManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.maven.dom.model.*; import org.jetbrains.idea.maven.utils.MavenUtil; import java.util.*; /** * @author Sergey Evdokimov */ public class MavenPluginParamInfo { private static final Logger LOG = Logger.getInstance(MavenPluginParamInfo.class); /** * This map contains descriptions of all plugins. */ private static volatile Map<String, Map> myMap; public static Map<String, Map> getMap() { Map<String, Map> res = myMap; if (res == null) { res = new HashMap<>(); for (MavenPluginDescriptor pluginDescriptor : MavenPluginDescriptor.EP_NAME.getExtensions()) { if (pluginDescriptor.params == null) continue; Pair<String, String> pluginId = MavenPluginDescriptor.parsePluginId(pluginDescriptor.mavenId); for (MavenPluginDescriptor.Param param : pluginDescriptor.params) { String[] paramPath = param.name.split("/"); Map pluginsMap = res; for (int i = paramPath.length - 1; i >= 0; i--) { pluginsMap = MavenUtil.getOrCreate(pluginsMap, paramPath[i]); } ParamInfo paramInfo = new ParamInfo(pluginDescriptor.getPluginDescriptor().getPluginClassLoader(), param); Map<String, ParamInfo> goalsMap = MavenUtil.getOrCreate(pluginsMap, pluginId); String goal = pluginDescriptor.goal; assert goal == null || !goal.isEmpty(); ParamInfo oldValue = goalsMap.put(goal, paramInfo); if (oldValue != null) { LOG.error("Duplicated maven plugin parameter descriptor: " + pluginId.first + ':' + pluginId.second + " -> " + (goal != null ? "[" + goal + ']' : "") + param.name); } } } myMap = res; } return res; } public static boolean isSimpleText(@NotNull XmlText paramValue) { PsiElement prevSibling = paramValue.getPrevSibling(); if (!(prevSibling instanceof LeafPsiElement) || ((LeafPsiElement)prevSibling).getElementType() != XmlTokenType.XML_TAG_END) { return false; } PsiElement nextSibling = paramValue.getNextSibling(); if (!(nextSibling instanceof LeafPsiElement) || ((LeafPsiElement)nextSibling).getElementType() != XmlTokenType.XML_END_TAG_START) { return false; } return true; } public static ParamInfoList getParamInfoList(@NotNull XmlText paramValue) { XmlTag tag = paramValue.getParentTag(); if (tag == null) return ParamInfoList.EMPTY; return getParamInfoList(tag); } public static ParamInfoList getParamInfoList(@NotNull XmlTag paramTag) { XmlTag configurationTag = paramTag; DomElement domElement; Map m = getMap().get(paramTag.getName()); while (true) { if (m == null) return ParamInfoList.EMPTY; configurationTag = configurationTag.getParentTag(); if (configurationTag == null) return ParamInfoList.EMPTY; String tagName = configurationTag.getName(); if ("configuration".equals(tagName)) { domElement = DomManager.getDomManager(configurationTag.getProject()).getDomElement(configurationTag); if (domElement instanceof MavenDomConfiguration) { break; } if (domElement != null) return ParamInfoList.EMPTY; } m = (Map)m.get(tagName); } Map<Pair<String, String>, Map<String, ParamInfo>> pluginsMap = m; MavenDomConfiguration domCfg = (MavenDomConfiguration)domElement; MavenDomPlugin domPlugin = domCfg.getParentOfType(MavenDomPlugin.class, true); if (domPlugin == null) return ParamInfoList.EMPTY; String pluginGroupId = domPlugin.getGroupId().getStringValue(); String pluginArtifactId = domPlugin.getArtifactId().getStringValue(); Map<String, ParamInfo> goalsMap; if (pluginGroupId == null) { goalsMap = pluginsMap.get(Pair.create("org.apache.maven.plugins", pluginArtifactId)); if (goalsMap == null) { goalsMap = pluginsMap.get(Pair.create("org.codehaus.mojo", pluginArtifactId)); } } else { goalsMap = pluginsMap.get(Pair.create(pluginGroupId, pluginArtifactId)); } if (goalsMap == null) return ParamInfoList.EMPTY; DomElement parent = domCfg.getParent(); if (parent instanceof MavenDomPluginExecution) { SmartList<ParamInfo> infos = null; MavenDomGoals goals = ((MavenDomPluginExecution)parent).getGoals(); for (MavenDomGoal goal : goals.getGoals()) { ParamInfo info = goalsMap.get(goal.getStringValue()); if (info != null) { if (infos == null) { infos = new SmartList<>(); } infos.add(info); } } if (infos != null) { ParamInfo defaultInfo = goalsMap.get(null); if (defaultInfo != null) { infos.add(defaultInfo); } return new ParamInfoList(domCfg, infos); } } ParamInfo defaultInfo = goalsMap.get(null); if (defaultInfo != null) { return new ParamInfoList(domCfg, Collections.singletonList(defaultInfo)); } return ParamInfoList.EMPTY; } public static class ParamInfoList implements Iterable<ParamInfo> { private static final ParamInfoList EMPTY = new ParamInfoList(null, Collections.emptyList()); private final MavenDomConfiguration domCfg; private final List<ParamInfo> paramInfos; ParamInfoList(MavenDomConfiguration domCfg, @NotNull List<ParamInfo> paramInfos) { this.domCfg = domCfg; this.paramInfos = paramInfos; } public MavenDomConfiguration getDomCfg() { return domCfg; } @Override public Iterator<ParamInfo> iterator() { return paramInfos.iterator(); } } public static class ParamInfo { private final ClassLoader myClassLoader; private final MavenPluginDescriptor.Param myParam; private volatile boolean myLanguageInitialized; private Language myLanguageInstance; private MavenParamLanguageProvider myLanguageProvider; private volatile boolean myProviderInitialized; private volatile MavenParamReferenceProvider myProviderInstance; private ParamInfo(ClassLoader classLoader, MavenPluginDescriptor.Param param) { myClassLoader = classLoader; myParam = param; } private void ensureLanguageInit() { if (!myLanguageInitialized) { if (myParam.language != null) { assert myParam.languageProvider == null; myLanguageInstance = Language.findLanguageByID(myParam.language); } else if (myParam.languageProvider != null) { try { myLanguageProvider = (MavenParamLanguageProvider)myClassLoader.loadClass(myParam.languageProvider).newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to create language provider instance", e); } } myLanguageInitialized = true; } } @Nullable public Language getLanguage() { ensureLanguageInit(); return myLanguageInstance; } @Nullable public MavenParamLanguageProvider getLanguageProvider() { ensureLanguageInit(); return myLanguageProvider; } public MavenPluginDescriptor.Param getParam() { return myParam; } public String getLanguageInjectionPrefix() { return myParam.languageInjectionPrefix; } public String getLanguageInjectionSuffix() { return myParam.languageInjectionSuffix; } public MavenParamReferenceProvider getProviderInstance() { if (!myProviderInitialized) { MavenParamReferenceProvider res = null; if (myParam.refProvider != null) { assert myParam.values == null : myParam.name; Object instance; try { instance = myClassLoader.loadClass(myParam.refProvider).newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to create reference provider instance", e); } if (instance instanceof MavenParamReferenceProvider) { res = (MavenParamReferenceProvider)instance; } else { res = new PsiReferenceProviderWrapper((PsiReferenceProvider)instance); } } else if (myParam.values != null) { StringTokenizer st = new StringTokenizer(myParam.values, " ,;"); int n = st.countTokens(); if (n == 0) throw new RuntimeException("Incorrect value of 'values' attribute for param " + myParam.name); String[] values = new String[n]; for (int i = 0; i < n; i++) { values[i] = st.nextToken(); } res = new MavenFixedValueReferenceProvider(values); } if (res != null && myParam.soft != null) { ((MavenSoftAwareReferenceProvider)res).setSoft(myParam.soft); } myProviderInstance = res; myProviderInitialized = true; } return myProviderInstance; } } private static class PsiReferenceProviderWrapper implements MavenParamReferenceProvider, MavenSoftAwareReferenceProvider { private final PsiReferenceProvider myProvider; private PsiReferenceProviderWrapper(PsiReferenceProvider provider) { this.myProvider = provider; } @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull MavenDomConfiguration domCfg, @NotNull ProcessingContext context) { return myProvider.getReferencesByElement(element, context); } @Override public void setSoft(boolean soft) { ((MavenSoftAwareReferenceProvider)myProvider).setSoft(soft); } } }