WI-10 Support CSS linked with via parametrized link
[fedora-idea.git] / platform / lang-impl / src / com / intellij / psi / impl / source / resolve / reference / impl / providers / FileReference.java
blobd2ba8d1ad574603bdc44327772b28e532804d400
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.psi.impl.source.resolve.reference.impl.providers;
19 import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
20 import com.intellij.codeInsight.daemon.QuickFixProvider;
21 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
22 import com.intellij.codeInspection.LocalQuickFix;
23 import com.intellij.codeInspection.LocalQuickFixProvider;
24 import com.intellij.lang.LangBundle;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Comparing;
27 import com.intellij.openapi.util.Iconable;
28 import com.intellij.openapi.util.TextRange;
29 import com.intellij.openapi.vfs.VfsUtil;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.psi.*;
32 import com.intellij.psi.impl.PsiManagerImpl;
33 import com.intellij.psi.impl.source.resolve.ResolveCache;
34 import com.intellij.psi.impl.source.resolve.reference.impl.CachingReference;
35 import com.intellij.psi.search.PsiElementProcessor;
36 import com.intellij.psi.search.PsiFileSystemItemProcessor;
37 import com.intellij.refactoring.rename.BindablePsiReference;
38 import com.intellij.util.ArrayUtil;
39 import com.intellij.util.CommonProcessors;
40 import com.intellij.util.FilteringProcessor;
41 import com.intellij.util.IncorrectOperationException;
42 import gnu.trove.THashSet;
43 import gnu.trove.TObjectHashingStrategy;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
47 import javax.swing.*;
48 import java.net.URI;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.HashSet;
52 import java.util.List;
54 /**
55 * @author cdr
57 public class FileReference implements FileReferenceOwner, PsiPolyVariantReference,
58 QuickFixProvider<FileReference>, LocalQuickFixProvider,
59 EmptyResolveMessageProvider, BindablePsiReference {
60 public static final FileReference[] EMPTY = new FileReference[0];
62 private final int myIndex;
63 private TextRange myRange;
64 private final String myText;
65 @NotNull private final FileReferenceSet myFileReferenceSet;
67 public FileReference(@NotNull final FileReferenceSet fileReferenceSet, TextRange range, int index, String text) {
68 myFileReferenceSet = fileReferenceSet;
69 myIndex = index;
70 myRange = range;
71 myText = text;
74 public FileReference(final FileReference original) {
75 this(original.myFileReferenceSet, original.myRange, original.myIndex, original.myText);
78 @NotNull
79 protected Collection<PsiFileSystemItem> getContexts() {
80 final FileReference contextRef = getContextReference();
81 if (contextRef == null) {
82 return myFileReferenceSet.getDefaultContexts();
84 ResolveResult[] resolveResults = contextRef.multiResolve(false);
85 ArrayList<PsiFileSystemItem> result = new ArrayList<PsiFileSystemItem>();
86 for (ResolveResult resolveResult : resolveResults) {
87 if (resolveResult.getElement() != null) {
88 result.add((PsiFileSystemItem)resolveResult.getElement());
91 return result;
94 @NotNull
95 public ResolveResult[] multiResolve(final boolean incompleteCode) {
96 final PsiManager manager = getElement().getManager();
97 if (manager instanceof PsiManagerImpl) {
98 return ((PsiManagerImpl)manager).getResolveCache().resolveWithCaching(this, MyResolver.INSTANCE, false, false);
100 return innerResolve();
103 protected ResolveResult[] innerResolve() {
104 return innerResolve(getFileReferenceSet().isCaseSensitive());
107 protected ResolveResult[] innerResolve(boolean caseSensitive) {
108 final String referenceText = getText();
109 final TextRange range = getRangeInElement();
110 if (range.isEmpty()) {
111 final PsiElement element = getElement();
112 final String s = element.getText();
113 if (s.length() > range.getEndOffset() && s.charAt(range.getEndOffset()) == '#') {
114 return new ResolveResult[] { new PsiElementResolveResult(element.getContainingFile())};
117 final Collection<PsiFileSystemItem> contexts = getContexts();
118 final Collection<ResolveResult> result = new HashSet<ResolveResult>(contexts.size());
119 for (final PsiFileSystemItem context : contexts) {
120 if (context != null) {
121 innerResolveInContext(referenceText, context, result, caseSensitive);
124 final int resultCount = result.size();
125 return resultCount > 0 ? result.toArray(new ResolveResult[resultCount]) : ResolveResult.EMPTY_ARRAY;
128 protected void innerResolveInContext(@NotNull final String text, @NotNull final PsiFileSystemItem context, final Collection<ResolveResult> result,
129 final boolean caseSensitive) {
130 if (text.length() == 0 && !myFileReferenceSet.isEndingSlashNotAllowed() && isLast() || ".".equals(text) || "/".equals(text)) {
131 result.add(new PsiElementResolveResult(context));
133 else if ("..".equals(text)) {
134 final PsiFileSystemItem resolved = context.getParent();
135 if (resolved != null) {
136 result.add(new PsiElementResolveResult(resolved));
139 else {
140 final int separatorIndex = text.indexOf('/');
141 if (separatorIndex >= 0) {
142 final List<ResolveResult> resolvedContexts = new ArrayList<ResolveResult>();
143 if (separatorIndex == 0 /*starts with slash*/ && "/".equals(context.getName())) {
144 resolvedContexts.add(new PsiElementResolveResult(context));
146 else {
147 innerResolveInContext(text.substring(0, separatorIndex), context, resolvedContexts, caseSensitive);
149 final String restOfText = text.substring(separatorIndex + 1);
150 for (ResolveResult contextVariant : resolvedContexts) {
151 final PsiFileSystemItem item = (PsiFileSystemItem)contextVariant.getElement();
152 if (item != null) {
153 innerResolveInContext(restOfText, item, result, caseSensitive);
157 else {
158 final String decoded = decode(text);
159 if (decoded != null) {
160 processVariants(context, new PsiFileSystemItemProcessor() {
161 public boolean acceptItem(String name, boolean isDirectory) {
162 return caseSensitive ? decoded.equals(name) : decoded.compareToIgnoreCase(name) == 0;
165 public boolean execute(PsiFileSystemItem element) {
166 result.add(new PsiElementResolveResult(getOriginalFile(element)));
167 return false;
175 @Nullable
176 private String decode(final String text) {
177 // strip http get parameters
178 String _text = text;
179 if (text.indexOf('?') >= 0) {
180 _text = text.substring(0, text.lastIndexOf('?'));
183 if (myFileReferenceSet.isUrlEncoded()) {
184 try {
185 return new URI(_text).getPath();
187 catch (Exception e) {
188 return text;
192 return _text;
195 public Object[] getVariants() {
196 final String s = getText();
197 if (s != null && s.equals("/")) {
198 return ArrayUtil.EMPTY_OBJECT_ARRAY;
201 final CommonProcessors.CollectUniquesProcessor<PsiElement> collector = new CommonProcessors.CollectUniquesProcessor<PsiElement>();
202 final PsiElementProcessor<PsiFileSystemItem> processor = new PsiElementProcessor<PsiFileSystemItem>() {
203 public boolean execute(PsiFileSystemItem fileSystemItem) {
204 return new FilteringProcessor<PsiElement>(myFileReferenceSet.createCondition(), collector).process(getOriginalFile(fileSystemItem));
207 for (PsiFileSystemItem context : getContexts()) {
208 for (final PsiElement child : context.getChildren()) {
209 if (child instanceof PsiFileSystemItem) {
210 processor.execute((PsiFileSystemItem)child);
214 final THashSet<PsiElement> set = new THashSet<PsiElement>(collector.getResults(), new TObjectHashingStrategy<PsiElement>() {
215 public int computeHashCode(final PsiElement object) {
216 if (object instanceof PsiNamedElement) {
217 final String name = ((PsiNamedElement)object).getName();
218 if (name != null) {
219 return name.hashCode();
222 return object.hashCode();
225 public boolean equals(final PsiElement o1, final PsiElement o2) {
226 if (o1 instanceof PsiNamedElement && o2 instanceof PsiNamedElement) {
227 return Comparing.equal(((PsiNamedElement)o1).getName(), ((PsiNamedElement)o2).getName());
229 return o1.equals(o2);
232 final PsiElement[] candidates = set.toArray(new PsiElement[set.size()]);
234 final Object[] variants = new Object[candidates.length];
235 for (int i = 0; i < candidates.length; i++) {
236 variants[i] = createLookupItem(candidates[i]);
239 if (myFileReferenceSet.isUrlEncoded()) {
240 for (int i = 0; i < candidates.length; i++) {
241 final PsiElement element = candidates[i];
242 if (element instanceof PsiNamedElement) {
243 final PsiNamedElement psiElement = (PsiNamedElement)element;
244 String name = psiElement.getName();
245 final String encoded = encode(name);
246 if (!encoded.equals(name)) {
247 final Icon icon = psiElement.getIcon(Iconable.ICON_FLAG_READ_STATUS | Iconable.ICON_FLAG_VISIBILITY);
248 variants[i] = FileInfoManager.getFileLookupItem(candidates[i], encoded, icon);
253 return variants;
256 protected Object createLookupItem(PsiElement candidate) {
257 return FileInfoManager.getFileLookupItem(candidate);
260 protected static PsiFileSystemItem getOriginalFile(PsiFileSystemItem fileSystemItem) {
261 final VirtualFile file = fileSystemItem.getVirtualFile();
262 if (file != null && !file.isDirectory()) {
263 final PsiManager psiManager = fileSystemItem.getManager();
264 if (psiManager != null) {
265 final PsiFile psiFile = psiManager.findFile(file);
266 if (psiFile != null) {
267 fileSystemItem = psiFile;
271 return fileSystemItem;
274 private static String encode(final String name) {
275 try {
276 return new URI(null, null, name, null).toString();
278 catch (Exception e) {
279 return name;
283 protected static void processVariants(final PsiFileSystemItem context, final PsiFileSystemItemProcessor processor) {
284 context.processChildren(processor);
287 @Nullable
288 private FileReference getContextReference() {
289 return myIndex > 0 ? myFileReferenceSet.getReference(myIndex - 1) : null;
292 public PsiElement getElement() {
293 return myFileReferenceSet.getElement();
296 public PsiFileSystemItem resolve() {
297 ResolveResult[] resolveResults = multiResolve(false);
298 return resolveResults.length == 1 ? (PsiFileSystemItem)resolveResults[0].getElement() : null;
301 @Nullable
302 public PsiFileSystemItem innerSingleResolve(final boolean caseSensitive) {
303 final ResolveResult[] resolveResults = innerResolve(caseSensitive);
304 return resolveResults.length == 1 ? (PsiFileSystemItem)resolveResults[0].getElement() : null;
307 public boolean isReferenceTo(PsiElement element) {
308 if (!(element instanceof PsiFileSystemItem)) return false;
310 final PsiFileSystemItem item = resolve();
311 return item != null && FileReferenceHelperRegistrar.areElementsEquivalent(item, (PsiFileSystemItem)element);
314 public TextRange getRangeInElement() {
315 return myRange;
318 public String getCanonicalText() {
319 return myText;
322 public String getText() {
323 return myText;
326 public boolean isSoft() {
327 return myFileReferenceSet.isSoft();
330 public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
331 final ElementManipulator<PsiElement> manipulator = CachingReference.getManipulator(getElement());
332 if (manipulator != null) {
333 myFileReferenceSet.setElement(manipulator.handleContentChange(getElement(), getRangeInElement(), newElementName));
334 //Correct ranges
335 int delta = newElementName.length() - myRange.getLength();
336 myRange = new TextRange(getRangeInElement().getStartOffset(), getRangeInElement().getStartOffset() + newElementName.length());
337 FileReference[] references = myFileReferenceSet.getAllReferences();
338 for (int idx = myIndex + 1; idx < references.length; idx++) {
339 references[idx].myRange = references[idx].myRange.shiftRight(delta);
341 return myFileReferenceSet.getElement();
343 throw new IncorrectOperationException("Manipulator for this element is not defined: " + getElement());
346 public PsiElement bindToElement(@NotNull final PsiElement element, final boolean absolute) throws IncorrectOperationException {
347 if (!(element instanceof PsiFileSystemItem)) throw new IncorrectOperationException("Cannot bind to element, should be instanceof PsiFileSystemItem: " + element);
349 final PsiFileSystemItem fileSystemItem = (PsiFileSystemItem)element;
350 VirtualFile dstVFile = fileSystemItem.getVirtualFile();
351 if (dstVFile == null) throw new IncorrectOperationException("Cannot bind to non-physical element:" + element);
353 PsiFile file = getElement().getContainingFile();
354 if (file.getContext() != null) file = file.getContext().getContainingFile(); // use host file!
355 final VirtualFile curVFile = file.getVirtualFile();
356 if (curVFile == null) throw new IncorrectOperationException("Cannot bind from non-physical element:" + file);
358 final Project project = element.getProject();
360 String newName;
362 if (absolute) {
363 PsiFileSystemItem root = null;
364 PsiFileSystemItem dstItem = null;
365 for (final FileReferenceHelper helper : FileReferenceHelperRegistrar.getHelpers()) {
366 if (!helper.isMine(project, dstVFile)) continue;
367 PsiFileSystemItem _dstItem = helper.getPsiFileSystemItem(project, dstVFile);
368 if (_dstItem != null) {
369 PsiFileSystemItem _root = helper.findRoot(project, dstVFile);
370 if (_root != null) {
371 root = _root;
372 dstItem = _dstItem;
373 break;
377 if (root == null) return element;
378 final String relativePath = PsiFileSystemItemUtil.getRelativePath(root, dstItem);
379 if (relativePath == null) {
380 return element;
382 newName = myFileReferenceSet.absoluteUrlNeedsStartSlash() ? "/" + relativePath : relativePath ;
384 } else { // relative path
385 PsiFileSystemItem curItem = null;
386 PsiFileSystemItem dstItem = null;
387 final FileReferenceHelper helper = FileReferenceHelperRegistrar.getNotNullHelper(file);
389 final Collection<PsiFileSystemItem> contexts = helper.getContexts(project, curVFile);
390 switch (contexts.size()) {
391 case 0:
392 break;
393 default:
394 for (PsiFileSystemItem context : contexts) {
395 final VirtualFile contextFile = context.getVirtualFile();
396 assert contextFile != null;
397 if (VfsUtil.isAncestor(contextFile, dstVFile, true)) {
398 final String path = VfsUtil.getRelativePath(dstVFile, contextFile, '/');
399 if (path != null) {
400 return rename(path);
404 case 1:
405 PsiFileSystemItem _dstItem = helper.getPsiFileSystemItem(project, dstVFile);
406 PsiFileSystemItem _curItem = helper.getPsiFileSystemItem(project, curVFile);
407 if (_dstItem != null && _curItem != null) {
408 curItem = _curItem;
409 dstItem = _dstItem;
410 break;
413 checkNotNull(curItem, curVFile, dstVFile);
414 assert curItem != null;
415 if (curItem.equals(dstItem)) {
416 if (getCanonicalText().equals(dstItem.getName())) {
417 return getElement();
419 return ElementManipulators.getManipulator(getElement()).handleContentChange(getElement(), getRangeInElement(), file.getName());
421 newName = PsiFileSystemItemUtil.getRelativePath(curItem, dstItem);
422 if (newName == null) {
423 return element;
427 if (myFileReferenceSet.isUrlEncoded()) {
428 newName = encode(newName);
431 return rename(newName);
434 /* Happens when it's been moved to another folder */
435 public PsiElement bindToElement(@NotNull final PsiElement element) throws IncorrectOperationException {
436 return bindToElement(element, myFileReferenceSet.isAbsolutePathReference());
439 protected PsiElement rename(final String newName) throws IncorrectOperationException {
440 final TextRange range = new TextRange(myFileReferenceSet.getStartInElement(), getRangeInElement().getEndOffset());
441 final ElementManipulator<PsiElement> manipulator = CachingReference.getManipulator(getElement());
442 if (manipulator == null) {
443 throw new IncorrectOperationException("Manipulator not defined for: " + getElement());
445 return manipulator.handleContentChange(getElement(), range, newName);
448 private static void checkNotNull(final Object o, final VirtualFile curVFile, final VirtualFile dstVFile) throws IncorrectOperationException {
449 if (o == null) {
450 throw new IncorrectOperationException("Cannot find path between files; src = " + curVFile.getPresentableUrl() + "; dst = " + dstVFile.getPresentableUrl());
454 public void registerQuickfix(HighlightInfo info, FileReference reference) {
455 for (final FileReferenceHelper helper : getHelpers()) {
456 helper.registerFixes(info, reference);
460 protected static FileReferenceHelper[] getHelpers() {
461 return FileReferenceHelperRegistrar.getHelpers();
464 public int getIndex() {
465 return myIndex;
468 public String getUnresolvedMessagePattern() {
469 final StringBuilder builder = new StringBuilder(LangBundle.message("error.cannot.resolve"));
470 builder.append(" ").append(isLast() ? LangBundle.message("terms.file") : LangBundle.message("terms.directory"));
471 builder.append(" ''{0}''");
472 return builder.toString();
475 public final boolean isLast() {
476 return myIndex == myFileReferenceSet.getAllReferences().length - 1;
479 @NotNull
480 public FileReferenceSet getFileReferenceSet() {
481 return myFileReferenceSet;
484 public LocalQuickFix[] getQuickFixes() {
485 final List<LocalQuickFix> result = new ArrayList<LocalQuickFix>();
486 for (final FileReferenceHelper helper : getHelpers()) {
487 result.addAll(helper.registerFixes(null, this));
489 return result.toArray(new LocalQuickFix[result.size()]);
492 public FileReference getLastFileReference() {
493 return myFileReferenceSet.getLastReference();
496 static class MyResolver implements ResolveCache.PolyVariantResolver<FileReference> {
497 static final MyResolver INSTANCE = new MyResolver();
499 public ResolveResult[] resolve(FileReference ref, boolean incompleteCode) {
500 return ref.innerResolve(ref.getFileReferenceSet().isCaseSensitive());