| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * 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 org.jetbrains.idea.maven.dom.references; |
| |
| import com.intellij.codeInsight.lookup.LookupElement; |
| import com.intellij.codeInsight.lookup.LookupElementBuilder; |
| import com.intellij.lang.properties.IProperty; |
| import com.intellij.lang.properties.PropertiesLanguage; |
| import com.intellij.lang.properties.psi.PropertiesFile; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.xml.XmlDocument; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.psi.xml.XmlTagChild; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.PlatformIcons; |
| import com.intellij.util.Processor; |
| import com.intellij.util.xml.DomElement; |
| import com.intellij.util.xml.DomUtil; |
| import com.intellij.xml.XmlElementDescriptor; |
| import com.intellij.xml.XmlNSDescriptor; |
| import gnu.trove.THashSet; |
| import icons.MavenIcons; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.idea.maven.dom.MavenDomProjectProcessorUtils; |
| import org.jetbrains.idea.maven.dom.MavenDomUtil; |
| import org.jetbrains.idea.maven.dom.MavenSchemaProvider; |
| import org.jetbrains.idea.maven.dom.model.MavenDomConfiguration; |
| import org.jetbrains.idea.maven.dom.model.MavenDomProfile; |
| import org.jetbrains.idea.maven.dom.model.MavenDomProjectModel; |
| import org.jetbrains.idea.maven.dom.model.MavenDomSettingsModel; |
| import org.jetbrains.idea.maven.execution.MavenRunner; |
| import org.jetbrains.idea.maven.execution.MavenRunnerSettings; |
| import org.jetbrains.idea.maven.model.MavenId; |
| import org.jetbrains.idea.maven.plugins.api.MavenPluginDescriptor; |
| import org.jetbrains.idea.maven.project.MavenProject; |
| import org.jetbrains.idea.maven.utils.MavenUtil; |
| import org.jetbrains.idea.maven.vfs.MavenPropertiesVirtualFileSystem; |
| |
| import javax.swing.*; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| public class MavenPropertyPsiReference extends MavenPsiReference { |
| public static final String TIMESTAMP_PROP = "maven.build.timestamp"; |
| |
| @Nullable |
| protected final MavenDomProjectModel myProjectDom; |
| protected final MavenProject myMavenProject; |
| private final boolean mySoft; |
| |
| public MavenPropertyPsiReference(MavenProject mavenProject, PsiElement element, String text, TextRange range, boolean isSoft) { |
| super(element, text, range); |
| myMavenProject = mavenProject; |
| mySoft = isSoft; |
| myProjectDom = MavenDomUtil.getMavenDomProjectModel(myProject, mavenProject.getFile()); |
| } |
| |
| @Nullable |
| public PsiElement resolve() { |
| PsiElement result = doResolve(); |
| if (result == null) { |
| if (MavenDomUtil.isMavenFile(getElement())) { |
| result = tryResolveToActivationSection(); |
| if (result == null) return null; |
| } |
| } |
| |
| if (result instanceof XmlTag) { |
| XmlTagChild[] children = ((XmlTag)result).getValue().getChildren(); |
| if (children.length != 1 || !(children[0] instanceof Navigatable)) return result; |
| return new MavenPsiElementWrapper(result, (Navigatable)children[0]); |
| } |
| |
| return result; |
| } |
| |
| private PsiElement tryResolveToActivationSection() { |
| XmlTag xmlTag = PsiTreeUtil.getParentOfType(getElement(), XmlTag.class); |
| while (xmlTag != null) { |
| if (xmlTag.getName().equals("profile")) { |
| XmlTag activation = xmlTag.findFirstSubTag("activation"); |
| if (activation != null) { |
| for (XmlTag propertyTag : activation.findSubTags("property")) { |
| XmlTag nameTag = propertyTag.findFirstSubTag("name"); |
| if (nameTag != null) { |
| if (nameTag.getValue().getTrimmedText().equals(myText)) { |
| return nameTag; |
| } |
| } |
| } |
| } |
| break; |
| } |
| |
| xmlTag = xmlTag.getParentTag(); |
| } |
| |
| return null; |
| } |
| |
| // See org.apache.maven.project.interpolation.AbstractStringBasedModelInterpolator.createValueSources() |
| @Nullable |
| protected PsiElement doResolve() { |
| boolean hasPrefix = false; |
| String unprefixed = myText; |
| |
| if (myText.startsWith("pom.")) { |
| unprefixed = myText.substring("pom.".length()); |
| hasPrefix = true; |
| } |
| else if (myText.startsWith("project.")) { |
| unprefixed = myText.substring("project.".length()); |
| hasPrefix = true; |
| } |
| |
| MavenProject mavenProject = myMavenProject; |
| |
| while (unprefixed.startsWith("parent.")) { |
| if (unprefixed.equals("parent.groupId") || unprefixed.equals("parent.artifactId") || unprefixed.equals("parent.version") |
| || unprefixed.equals("parent.relativePath")) { |
| break; |
| } |
| |
| MavenId parentId = mavenProject.getParentId(); |
| if (parentId == null) return null; |
| |
| mavenProject = myProjectsManager.findProject(parentId); |
| if (mavenProject == null) return null; |
| |
| unprefixed = unprefixed.substring("parent.".length()); |
| } |
| |
| if (unprefixed.equals("basedir") || (hasPrefix && mavenProject == myMavenProject && unprefixed.equals("baseUri"))) { |
| return getBaseDir(mavenProject); |
| } |
| |
| if (myText.equals(TIMESTAMP_PROP)) { |
| return myElement; |
| } |
| |
| if (hasPrefix) { |
| MavenDomProjectModel domProjectModel = MavenDomUtil.getMavenDomProjectModel(myProject, mavenProject.getFile()); |
| if (domProjectModel != null) { |
| PsiElement res = resolveModelProperty(domProjectModel, unprefixed, new HashSet<DomElement>()); |
| if (res != null) { |
| return res; |
| } |
| } |
| } |
| |
| // todo resolve properties from config. |
| MavenRunnerSettings runnerSettings = MavenRunner.getInstance(myProject).getSettings(); |
| if (runnerSettings.getMavenProperties().containsKey(myText) || runnerSettings.getVmOptions().contains("-D" + myText + '=')) { |
| return myElement; |
| } |
| if (MavenUtil.getPropertiesFromMavenOpts().containsKey(myText)) { |
| return myElement; |
| } |
| |
| MavenDomProfile profile = DomUtil.findDomElement(myElement, MavenDomProfile.class); |
| if (profile != null) { |
| PsiElement result = MavenDomProjectProcessorUtils.findProperty(profile.getProperties(), myText); |
| if (result != null) return result; |
| } |
| |
| MavenDomConfiguration pluginCfg = DomUtil.findDomElement(myElement, MavenDomConfiguration.class); |
| if (pluginCfg != null) { |
| boolean notFound = MavenPluginDescriptor.processDescriptors(new Processor<MavenPluginDescriptor>() { |
| @Override |
| public boolean process(MavenPluginDescriptor descriptor) { |
| if (descriptor.properties != null) { |
| for (MavenPluginDescriptor.ModelProperty property : descriptor.properties) { |
| if (property.insideConfigurationOnly && property.name.equals(myText)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| }, pluginCfg); |
| |
| if (!notFound) { |
| return myElement; |
| } |
| } |
| |
| if (myProjectDom != null) { |
| PsiElement result = MavenDomProjectProcessorUtils.searchProperty(myText, myProjectDom, myProject); |
| if (result != null) return result; |
| } |
| |
| if ("java.home".equals(myText)) { |
| PsiElement element = resolveToCustomSystemProperty("java.home", MavenUtil.getModuleJreHome(myProjectsManager, mavenProject)); |
| if (element != null) { |
| return element; |
| } |
| } |
| |
| if ("java.version".equals(myText)) { |
| PsiElement element = resolveToCustomSystemProperty("java.version", MavenUtil.getModuleJavaVersion(myProjectsManager, mavenProject)); |
| if (element != null) { |
| return element; |
| } |
| } |
| |
| MavenPropertiesVirtualFileSystem mavenPropertiesVirtualFileSystem = MavenPropertiesVirtualFileSystem.getInstance(); |
| |
| IProperty property = mavenPropertiesVirtualFileSystem.findSystemProperty(myProject, myText); |
| if (property != null) return property.getPsiElement(); |
| |
| if (myText.startsWith("env.")) { |
| property = mavenPropertiesVirtualFileSystem.findEnvProperty(myProject, myText.substring("env.".length())); |
| if (property != null) return property.getPsiElement(); |
| } |
| |
| String textWithEnv = "env." + myText; |
| |
| property = mavenPropertiesVirtualFileSystem.findSystemProperty(myProject, textWithEnv); |
| if (property != null) return property.getPsiElement(); |
| |
| property = mavenPropertiesVirtualFileSystem.findEnvProperty(myProject, textWithEnv); |
| if (property != null) return property.getPsiElement(); |
| |
| if (!hasPrefix) { |
| MavenDomProjectModel domProjectModel = MavenDomUtil.getMavenDomProjectModel(myProject, mavenProject.getFile()); |
| if (domProjectModel != null) { |
| PsiElement res = resolveModelProperty(domProjectModel, unprefixed, new HashSet<DomElement>()); |
| if (res != null) { |
| return res; |
| } |
| } |
| } |
| |
| if (mavenProject.getProperties().containsKey(myText)) { |
| return myElement; |
| } |
| |
| if (myText.startsWith("settings.")) { |
| return resolveSettingsModelProperty(); |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private PsiElement resolveToCustomSystemProperty(@NotNull String propertyName, @Nullable String propertyValue) { |
| if (propertyValue == null) return null; |
| |
| PsiFile propFile = PsiFileFactory.getInstance(myProject).createFileFromText("SystemProperties.properties", PropertiesLanguage.INSTANCE, |
| propertyName + '=' + propertyValue); |
| |
| return ((PropertiesFile)propFile).getProperties().get(0).getPsiElement(); |
| } |
| |
| private PsiDirectory getBaseDir(@NotNull MavenProject mavenProject) { |
| return PsiManager.getInstance(myProject).findDirectory(mavenProject.getDirectoryFile()); |
| } |
| |
| @Nullable |
| private PsiElement resolveSettingsModelProperty() { |
| if (!schemaHasProperty(MavenSchemaProvider.MAVEN_SETTINGS_SCHEMA_URL, myText)) return null; |
| |
| for (VirtualFile each : myProjectsManager.getGeneralSettings().getEffectiveSettingsFiles()) { |
| MavenDomSettingsModel settingsDom = MavenDomUtil.getMavenDomModel(myProject, each, MavenDomSettingsModel.class); |
| if (settingsDom == null) continue; |
| PsiElement result = MavenDomUtil.findTag(settingsDom, myText); |
| if (result != null) return result; |
| } |
| return myElement; |
| } |
| |
| @Nullable |
| private PsiElement resolveModelProperty(@NotNull MavenDomProjectModel projectDom, |
| @NotNull final String path, |
| @NotNull final Set<DomElement> recursionGuard) { |
| if (!recursionGuard.add(projectDom)) return null; |
| |
| String pathWithProjectPrefix = "project." + path; |
| |
| if (!MavenModelClassesProperties.isPathValid(MavenModelClassesProperties.MAVEN_PROJECT_CLASS, path) |
| && !MavenModelClassesProperties.isPathValid(MavenModelClassesProperties.MAVEN_MODEL_CLASS, path)) { |
| if (!schemaHasProperty(MavenSchemaProvider.MAVEN_PROJECT_SCHEMA_URL, pathWithProjectPrefix)) return null; |
| } |
| |
| PsiElement result = MavenDomUtil.findTag(projectDom, pathWithProjectPrefix); |
| if (result != null) return result; |
| |
| if (pathWithProjectPrefix.equals("project.groupId") || pathWithProjectPrefix.equals("project.version")) { |
| return MavenDomUtil.findTag(projectDom, "project.parent." + path); |
| } |
| |
| result = new MavenDomProjectProcessorUtils.DomParentProjectFileProcessor<PsiElement>(myProjectsManager) { |
| protected PsiElement doProcessParent(VirtualFile parentFile) { |
| MavenDomProjectModel parentProjectDom = MavenDomUtil.getMavenDomProjectModel(myProject, parentFile); |
| if (parentProjectDom == null) return null; |
| return resolveModelProperty(parentProjectDom, path, recursionGuard); |
| } |
| }.process(projectDom); |
| if (result != null) return result; |
| |
| return myElement; |
| } |
| |
| private boolean schemaHasProperty(String schema, final String property) { |
| return processSchema(schema, new SchemaProcessor<Boolean>() { |
| @Nullable |
| public Boolean process(@NotNull String eachProperty, XmlElementDescriptor descriptor) { |
| if (eachProperty.equals(property)) return true; |
| return null; |
| } |
| }) != null; |
| } |
| |
| @Override |
| public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { |
| return ElementManipulators.getManipulator(myElement).handleContentChange(myElement, myRange, newElementName); |
| } |
| |
| @NotNull |
| public Object[] getVariants() { |
| List<Object> result = new ArrayList<Object>(); |
| collectVariants(result, new THashSet<String>()); |
| return ArrayUtil.toObjectArray(result); |
| } |
| |
| protected void collectVariants(final List<Object> result, Set<String> variants) { |
| int prefixLength = 0; |
| if (myText.startsWith("pom.")) { |
| prefixLength = "pom.".length(); |
| } |
| else if (myText.startsWith("project.")) { |
| prefixLength = "project.".length(); |
| } |
| |
| MavenProject mavenProject = myMavenProject; |
| while (myText.startsWith("parent.", prefixLength)) { |
| MavenId parentId = mavenProject.getParentId(); |
| if (parentId == null) return; |
| |
| mavenProject = myProjectsManager.findProject(parentId); |
| if (mavenProject == null) return; |
| |
| prefixLength += "parent.".length(); |
| } |
| |
| final String prefix = prefixLength == 0 ? null : myText.substring(0, prefixLength); |
| |
| PsiDirectory baseDir = getBaseDir(mavenProject); |
| addVariant(result, "basedir", baseDir, prefix, MavenIcons.MavenLogo); |
| if (prefix == null) { |
| result.add(createLookupElement(baseDir, "project.baseUri", MavenIcons.MavenLogo)); |
| result.add(createLookupElement(baseDir, "pom.baseUri", MavenIcons.MavenLogo)); |
| result.add(LookupElementBuilder.create(TIMESTAMP_PROP).withIcon(MavenIcons.MavenLogo)); |
| } |
| |
| processSchema(MavenSchemaProvider.MAVEN_PROJECT_SCHEMA_URL, new SchemaProcessor<Object>() { |
| @Override |
| public Object process(@NotNull String property, XmlElementDescriptor descriptor) { |
| if (property.startsWith("project.")) { |
| addVariant(result, property.substring("project.".length()), descriptor, prefix, MavenIcons.MavenLogo); |
| } |
| return null; |
| } |
| }); |
| |
| processSchema(MavenSchemaProvider.MAVEN_SETTINGS_SCHEMA_URL, new SchemaProcessor<Object>(){ |
| @Override |
| public Object process(@NotNull String property, XmlElementDescriptor descriptor) { |
| result.add(createLookupElement(descriptor, property, MavenIcons.MavenLogo)); |
| return null; |
| } |
| }); |
| |
| collectPropertiesVariants(result, variants); |
| collectSystemEnvProperties(MavenPropertiesVirtualFileSystem.SYSTEM_PROPERTIES_FILE, null, result, variants); |
| collectSystemEnvProperties(MavenPropertiesVirtualFileSystem.ENV_PROPERTIES_FILE, "env.", result, variants); |
| |
| MavenRunnerSettings runnerSettings = MavenRunner.getInstance(myProject).getSettings(); |
| for (String prop : runnerSettings.getMavenProperties().keySet()) { |
| if (variants.add(prefix)) { |
| result.add(LookupElementBuilder.create(prop).withIcon(PlatformIcons.PROPERTY_ICON)); |
| } |
| } |
| for (String prop : MavenUtil.getPropertiesFromMavenOpts().keySet()) { |
| if (variants.add(prop)) { |
| result.add(LookupElementBuilder.create(prop).withIcon(PlatformIcons.PROPERTY_ICON)); |
| } |
| } |
| |
| for (Object key : myMavenProject.getProperties().keySet()) { |
| if (key instanceof String) { |
| String property = (String)key; |
| if (variants.add(property)) { |
| result.add(LookupElementBuilder.create(property).withIcon(PlatformIcons.PROPERTY_ICON)); |
| } |
| } |
| } |
| |
| MavenDomConfiguration pluginCfg = DomUtil.findDomElement(myElement, MavenDomConfiguration.class); |
| if (pluginCfg != null) { |
| MavenPluginDescriptor.processDescriptors(new Processor<MavenPluginDescriptor>() { |
| @Override |
| public boolean process(MavenPluginDescriptor descriptor) { |
| if (descriptor.properties != null) { |
| for (MavenPluginDescriptor.ModelProperty property : descriptor.properties) { |
| if (property.insideConfigurationOnly) { |
| result.add(LookupElementBuilder.create(property.name).withIcon(PlatformIcons.PROPERTY_ICON)); |
| } |
| } |
| } |
| return true; |
| } |
| }, pluginCfg); |
| } |
| } |
| |
| private static void addVariant(List<Object> result, String name, @NotNull Object element, @Nullable String prefix, @NotNull Icon icon) { |
| String nameWithPrefix; |
| if (prefix == null) { |
| nameWithPrefix = name; |
| result.add(createLookupElement(element, "pom." + name, icon)); |
| result.add(createLookupElement(element, "project." + name, icon)); |
| } |
| else { |
| nameWithPrefix = prefix + name; |
| } |
| |
| result.add(createLookupElement(element, nameWithPrefix, icon)); |
| } |
| |
| private void collectPropertiesVariants(final List<Object> result, Set<String> variants) { |
| if (myProjectDom != null) { |
| for (XmlTag xmlTag : MavenDomProjectProcessorUtils.collectProperties(myProjectDom, myProject)) { |
| String propertyName = xmlTag.getName(); |
| if (variants.add(propertyName)) { |
| result.add(createLookupElement(xmlTag, propertyName, PlatformIcons.PROPERTY_ICON)); |
| } |
| } |
| } |
| } |
| |
| private void collectSystemEnvProperties(String propertiesFileName, @Nullable String prefix, List<Object> result, Set<String> variants) { |
| VirtualFile virtualFile = MavenPropertiesVirtualFileSystem.getInstance().findFileByPath(propertiesFileName); |
| PropertiesFile file = MavenDomUtil.getPropertiesFile(myProject, virtualFile); |
| collectPropertiesFileVariants(file, prefix, result, variants); |
| } |
| |
| protected static void collectPropertiesFileVariants(@Nullable PropertiesFile file, @Nullable String prefix, List<Object> result, Set<String> variants) { |
| if (file == null) return; |
| |
| for (IProperty each : file.getProperties()) { |
| String name = each.getKey(); |
| if (name != null) { |
| if (prefix != null) name = prefix + name; |
| |
| if (variants.add(name)) { |
| result.add(createLookupElement(each, name, PlatformIcons.PROPERTY_ICON)); |
| } |
| } |
| } |
| } |
| |
| private static LookupElement createLookupElement(@NotNull Object element, @NotNull String name, @Nullable Icon icon) { |
| return LookupElementBuilder.create(element, name) |
| .withIcon(icon) |
| .withPresentableText(name); |
| } |
| |
| @Nullable |
| private <T> T processSchema(String schema, SchemaProcessor<T> processor) { |
| VirtualFile file = MavenSchemaProvider.getSchemaFile(schema); |
| PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); |
| if (!(psiFile instanceof XmlFile)) return null; |
| |
| XmlFile xmlFile = (XmlFile)psiFile; |
| XmlDocument document = xmlFile.getDocument(); |
| XmlNSDescriptor desc = (XmlNSDescriptor)document.getMetaData(); |
| XmlElementDescriptor[] descriptors = desc.getRootElementsDescriptors(document); |
| return doProcessSchema(descriptors, null, processor, new THashSet<XmlElementDescriptor>()); |
| } |
| |
| private static <T> T doProcessSchema(XmlElementDescriptor[] descriptors, |
| String prefix, |
| SchemaProcessor<T> processor, |
| Set<XmlElementDescriptor> recursionGuard) { |
| for (XmlElementDescriptor each : descriptors) { |
| if (isCollection(each)) continue; |
| if (!recursionGuard.add(each)) continue; |
| |
| try { |
| String name = each.getName(); |
| if (prefix != null) name = prefix + "." + name; |
| |
| T result = processor.process(name, each); |
| if (result != null) return result; |
| |
| result = doProcessSchema(each.getElementsDescriptors(null), name, processor, recursionGuard); |
| if (result != null) return result; |
| } |
| finally { |
| recursionGuard.remove(each); |
| } |
| } |
| |
| return null; |
| } |
| |
| private static boolean isCollection(XmlElementDescriptor each) { |
| XmlTag declaration = (XmlTag)each.getDeclaration(); |
| if (declaration != null) { |
| XmlTag complexType = declaration.findFirstSubTag("xs:complexType"); |
| if (complexType != null) { |
| if (complexType.findFirstSubTag("xs:sequence") != null) return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean isSoft() { |
| return mySoft; |
| } |
| |
| private interface SchemaProcessor<T> { |
| @Nullable |
| T process(@NotNull String property, XmlElementDescriptor descriptor); |
| } |
| |
| } |