blob: bebd44da248c7a3d5d2038f617716ad32383cb27 [file] [log] [blame]
/*
* 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);
}
}