/* * Copyright 2000-2010 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.napile.idea.thermit.dom; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.napile.idea.thermit.ThermitBundle; import org.napile.idea.thermit.ThermitSupport; import com.intellij.codeInsight.lookup.AutoCompletionPolicy; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; 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; /** * @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<Pair<String, 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<Pair<String, String>>(); // lazy init } prefixNamePairs.add(new Pair<String, String>(prefix, candidateName)); } } } final String currentRefText = getCanonicalText(); for(Pair<String, 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 ? ThermitSupport.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 ThermitBundle.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)); } } } } }