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
;
49 import java
.util
.ArrayList
;
50 import java
.util
.Collection
;
51 import java
.util
.HashSet
;
52 import java
.util
.List
;
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
;
74 public FileReference(final FileReference original
) {
75 this(original
.myFileReferenceSet
, original
.myRange
, original
.myIndex
, original
.myText
);
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());
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
));
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
));
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();
153 innerResolveInContext(restOfText
, item
, result
, caseSensitive
);
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
)));
176 private String
decode(final String text
) {
177 // strip http get parameters
179 if (text
.indexOf('?') >= 0) {
180 _text
= text
.substring(0, text
.lastIndexOf('?'));
183 if (myFileReferenceSet
.isUrlEncoded()) {
185 return new URI(_text
).getPath();
187 catch (Exception e
) {
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();
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
);
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
) {
276 return new URI(null, null, name
, null).toString();
278 catch (Exception e
) {
283 protected static void processVariants(final PsiFileSystemItem context
, final PsiFileSystemItemProcessor processor
) {
284 context
.processChildren(processor
);
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;
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() {
318 public String
getCanonicalText() {
322 public String
getText() {
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
));
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();
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
);
377 if (root
== null) return element
;
378 final String relativePath
= PsiFileSystemItemUtil
.getRelativePath(root
, dstItem
);
379 if (relativePath
== null) {
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()) {
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
, '/');
405 PsiFileSystemItem _dstItem
= helper
.getPsiFileSystemItem(project
, dstVFile
);
406 PsiFileSystemItem _curItem
= helper
.getPsiFileSystemItem(project
, curVFile
);
407 if (_dstItem
!= null && _curItem
!= null) {
413 checkNotNull(curItem
, curVFile
, dstVFile
);
414 assert curItem
!= null;
415 if (curItem
.equals(dstItem
)) {
416 if (getCanonicalText().equals(dstItem
.getName())) {
419 return ElementManipulators
.getManipulator(getElement()).handleContentChange(getElement(), getRangeInElement(), file
.getName());
421 newName
= PsiFileSystemItemUtil
.getRelativePath(curItem
, dstItem
);
422 if (newName
== null) {
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
{
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() {
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;
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());