/*
* 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));
}
}
}
}
}