blob: 9b90a2a43969fb5273c0b02da2aaf390220d54dd [file] [log] [blame]
/*
* Copyright 2000-2014 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 com.intellij.lang.ant.dom;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.ant.AntBundle;
import com.intellij.lang.ant.AntSupport;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.references.PomService;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.refactoring.rename.BindablePsiReference;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ArrayListSet;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.StringTokenizer;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomTarget;
import com.intellij.util.xml.DomUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Eugene Zhuravlev
* Date: Aug 17, 2010
*/
class AntDomTargetReference extends AntDomReferenceBase implements BindablePsiReference{
private final ReferenceGroup myGroup;
public AntDomTargetReference(PsiElement element) {
super(element, true);
myGroup = null;
}
public AntDomTargetReference(PsiElement element, TextRange range, ReferenceGroup group) {
super(element, range, true);
myGroup = group;
group.addReference(this);
}
public PsiElement resolve() {
return ResolveCache.getInstance(getElement().getProject()).resolveWithCaching(this, MyResolver.INSTANCE, false, false);
}
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
final DomElement targetDomElement = toDomElement(element);
if (targetDomElement != null) {
final AntDomTarget pointingToTarget = targetDomElement.getParentOfType(AntDomTarget.class, false);
if (pointingToTarget != null) {
// the aim here is to receive all variants available at this particular context
final TargetResolver.Result result = doResolve(null);
if (result != null) {
final Map<String, AntDomTarget> variants = result.getVariants();
String newName = null;
if (!variants.isEmpty()) {
List<Couple<String>> prefixNamePairs = null;
for (Map.Entry<String, AntDomTarget> entry : variants.entrySet()) {
final AntDomTarget candidateTarget = entry.getValue();
if (pointingToTarget.equals(candidateTarget)) {
final String candidateName = entry.getKey();
final String candidateTargetName = candidateTarget.getName().getRawText();
if (candidateName.endsWith(candidateTargetName)) {
final String prefix = candidateName.substring(0, candidateName.length() - candidateTargetName.length());
if (prefixNamePairs == null) {
prefixNamePairs = new ArrayList<Couple<String>>(); // lazy init
}
prefixNamePairs.add(Couple.of(prefix, candidateName));
}
}
}
final String currentRefText = getCanonicalText();
for (Couple<String> pair : prefixNamePairs) {
final String prefix = pair.getFirst();
final String effectiveName = pair.getSecond();
if (currentRefText.startsWith(prefix)) {
if (newName == null || effectiveName.length() > newName.length()) {
// this candidate's prefix matches current reference text and this name is longer
// than the previous candidate, then prefer this name
newName = effectiveName;
}
}
}
}
if (newName != null) {
handleElementRename(newName);
if (myGroup != null) {
myGroup.textChanged(this, newName);
}
}
}
}
}
return getElement();
}
@Nullable
private AntDomElement getHostingAntDomElement() {
final DomElement selfElement = DomUtil.getDomElement(getElement());
if (selfElement == null) {
return null;
}
return selfElement.getParentOfType(AntDomElement.class, false);
}
@NotNull
public Object[] getVariants() {
final TargetResolver.Result result = doResolve(getCanonicalText());
if (result == null) {
return EMPTY_ARRAY;
}
final Map<String, AntDomTarget> variants = result.getVariants();
final List resVariants = new ArrayList();
final Set<String> existing = getExistingNames();
for (String s : variants.keySet()) {
if (existing.contains(s)){
continue;
}
final LookupElementBuilder builder = LookupElementBuilder.create(s).withCaseSensitivity(false);
final LookupElement element = AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE.applyPolicy(builder);
resVariants.add(element);
}
return ContainerUtil.toArray(resVariants, new Object[resVariants.size()]);
}
@Nullable
private TargetResolver.Result doResolve(@Nullable final String referenceText) {
final AntDomElement hostingElement = getHostingAntDomElement();
if (hostingElement == null) {
return null;
}
AntDomProject projectToSearchFrom;
AntDomTarget contextTarget;
if (hostingElement instanceof AntDomAnt) {
final PsiFileSystemItem antFile = ((AntDomAnt)hostingElement).getAntFilePath().getValue();
projectToSearchFrom = antFile instanceof PsiFile ? AntSupport.getAntDomProjectForceAntFile((PsiFile)antFile) : null;
contextTarget = null;
}
else {
projectToSearchFrom = hostingElement.getContextAntProject();
contextTarget = hostingElement.getParentOfType(AntDomTarget.class, false);
}
if (projectToSearchFrom == null) {
return null;
}
return TargetResolver.resolve(projectToSearchFrom, contextTarget, referenceText == null? Collections.<String>emptyList() : Collections.singletonList(referenceText));
}
private Set<String> getExistingNames() {
final AntDomElement hostingElement = getHostingAntDomElement();
if (hostingElement == null) {
return Collections.emptySet();
}
final AntDomTarget contextTarget = hostingElement.getParentOfType(AntDomTarget.class, false);
if (contextTarget == null) {
return Collections.emptySet();
}
final Set<String> existing = new ArrayListSet<String>();
final String selfName = contextTarget.getName().getStringValue();
if (selfName != null) {
existing.add(selfName);
}
final String dependsString = contextTarget.getDependsList().getRawText();
if (dependsString != null) {
final StringTokenizer tokenizer = new StringTokenizer(dependsString, ",", false);
while (tokenizer.hasMoreTokens()) {
existing.add(tokenizer.nextToken().trim());
}
}
return existing;
}
public String getUnresolvedMessagePattern() {
return AntBundle.message("cannot.resolve.target", getCanonicalText());
}
private static class MyResolver implements ResolveCache.Resolver {
static final MyResolver INSTANCE = new MyResolver();
public PsiElement resolve(@NotNull PsiReference psiReference, boolean incompleteCode) {
final TargetResolver.Result result = ((AntDomTargetReference)psiReference).doResolve(psiReference.getCanonicalText());
if (result == null) {
return null;
}
final Pair<AntDomTarget,String> pair = result.getResolvedTarget(psiReference.getCanonicalText());
final DomTarget domTarget = pair != null && pair.getFirst() != null ? DomTarget.getTarget(pair.getFirst()) : null;
return domTarget != null? PomService.convertToPsi(domTarget) : null;
}
}
public static class ReferenceGroup {
private List<AntDomTargetReference> myRefs = new ArrayList<AntDomTargetReference>();
public void addReference(AntDomTargetReference ref) {
myRefs.add(ref);
}
public void textChanged(AntDomTargetReference ref, String newText) {
Integer lengthDelta = null;
for (AntDomTargetReference r : myRefs) {
if (lengthDelta != null) {
r.setRangeInElement(r.getRangeInElement().shiftRight(lengthDelta));
}
else if (r.equals(ref)) {
final TextRange range = r.getRangeInElement();
final int oldLength = range.getLength();
lengthDelta = new Integer(newText.length() - oldLength);
r.setRangeInElement(range.grown(lengthDelta));
}
}
}
}
}