allow new expressions qualifications to be shortened anyway
[idea/community.git] / java / java-impl / src / com / intellij / psi / impl / source / codeStyle / ReferenceAdjuster.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.psi.impl.source.codeStyle;
17
18 import com.intellij.lang.ASTNode;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.psi.*;
21 import com.intellij.psi.codeStyle.CodeStyleSettings;
22 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
23 import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
24 import com.intellij.psi.impl.source.SourceJavaCodeReference;
25 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
26 import com.intellij.psi.impl.source.jsp.jspJava.JspClass;
27 import com.intellij.psi.impl.source.tree.*;
28 import com.intellij.psi.jsp.JspFile;
29 import com.intellij.psi.tree.IElementType;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import java.util.ArrayList;
34
35 public class ReferenceAdjuster {
36   private final boolean myUseFqClassnamesInJavadoc;
37   private final boolean myUseFqClassNames;
38
39   public ReferenceAdjuster(boolean useFqInJavadoc, boolean useFqInCode) {
40     myUseFqClassnamesInJavadoc = useFqInJavadoc;
41     myUseFqClassNames = useFqInCode;
42   }
43
44   public ReferenceAdjuster(Project project) {
45     this(CodeStyleSettingsManager.getSettings(project));
46   }
47
48   public ReferenceAdjuster(CodeStyleSettings settings) {
49     this(settings.USE_FQ_CLASS_NAMES_IN_JAVADOC, settings.USE_FQ_CLASS_NAMES);
50   }
51
52   public TreeElement process(TreeElement element, boolean addImports, boolean uncompleteCode) {
53     IElementType elementType = element.getElementType();
54     if (elementType == JavaElementType.JAVA_CODE_REFERENCE || elementType == JavaElementType.REFERENCE_EXPRESSION) {
55       if (elementType == JavaElementType.JAVA_CODE_REFERENCE || element.getTreeParent().getElementType() == JavaElementType.REFERENCE_EXPRESSION || uncompleteCode) {
56         final PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)SourceTreeToPsiMap.treeElementToPsi(element);
57         final PsiReferenceParameterList parameterList = ref.getParameterList();
58         if (parameterList != null) {
59           final PsiTypeElement[] typeParameters = parameterList.getTypeParameterElements();
60           for (PsiTypeElement typeParameter : typeParameters) {
61             process((TreeElement)SourceTreeToPsiMap.psiElementToTree(typeParameter), addImports, uncompleteCode);
62           }
63         }
64
65         boolean rightKind = true;
66         if (elementType == JavaElementType.JAVA_CODE_REFERENCE) {
67           int kind = ((PsiJavaCodeReferenceElementImpl)element).getKind();
68           rightKind = kind == PsiJavaCodeReferenceElementImpl.CLASS_NAME_KIND || kind == PsiJavaCodeReferenceElementImpl.CLASS_OR_PACKAGE_NAME_KIND;
69         }
70
71         if (rightKind) {
72           boolean isInsideDocComment = TreeUtil.findParent(element, JavaDocElementType.DOC_COMMENT) != null;
73           boolean isShort = !((SourceJavaCodeReference)element).isQualified();
74           if (!makeFQ(isInsideDocComment)) {
75             if (isShort) return element; // short name already, no need to change
76           }
77           PsiElement refElement;
78           if (!uncompleteCode) {
79             refElement = ref.resolve();
80           }
81           else {
82             PsiResolveHelper helper = JavaPsiFacade.getInstance(element.getManager().getProject()).getResolveHelper();
83             refElement = helper.resolveReferencedClass(((SourceJavaCodeReference)element).getClassNameText(), SourceTreeToPsiMap.treeElementToPsi(element)
84             );
85           }
86           if (refElement instanceof PsiClass) {
87             if (makeFQ(isInsideDocComment)) {
88               String qName = ((PsiClass)refElement).getQualifiedName();
89               if (qName == null) return element;
90               PsiImportHolder file = (PsiImportHolder) SourceTreeToPsiMap.treeElementToPsi(element).getContainingFile();
91               if (file instanceof PsiJavaFile && ImportHelper.isImplicitlyImported(qName, (PsiJavaFile) file)) {
92                 if (isShort) return element;
93                 return (TreeElement)makeShortReference((CompositeElement)element, (PsiClass)refElement, addImports);
94               }
95               if (file instanceof PsiJavaFile) {
96                 String thisPackageName = ((PsiJavaFile)file).getPackageName();
97                 if (ImportHelper.hasPackage(qName, thisPackageName)) {
98                   if (!isShort) {
99                     return (TreeElement)makeShortReference((CompositeElement)element, (PsiClass)refElement, addImports);
100                   }
101                 }
102               }
103               return (TreeElement)replaceReferenceWithFQ(element, (PsiClass)refElement);
104             }
105             else {
106               int oldLength = element.getTextLength();
107               TreeElement treeElement = (TreeElement)makeShortReference((CompositeElement)element, (PsiClass)refElement, addImports);
108               if (treeElement.getTextLength() == oldLength && ((PsiClass)refElement).getContainingClass() != null) {
109                 PsiElement qualifier = ref.getQualifier();
110                 if (qualifier instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)qualifier).resolve() instanceof PsiClass) {
111                   process((TreeElement)qualifier.getNode(), addImports, uncompleteCode);
112                 }
113               }
114               return treeElement;
115             }
116           }
117         }
118       }
119     }
120
121     for (TreeElement child = element.getFirstChildNode(); child != null; child = child.getTreeNext()) {
122       child = process(child, addImports, uncompleteCode);
123     }
124
125     return element;
126   }
127
128   private boolean makeFQ(boolean isInsideDocComment) {
129     if (isInsideDocComment) {
130       return myUseFqClassnamesInJavadoc;
131     }
132     else {
133       return myUseFqClassNames;
134     }
135   }
136
137   public void processRange(TreeElement element, int startOffset, int endOffset) {
138     ArrayList<ASTNode> array = new ArrayList<ASTNode>();
139     addReferencesInRange(array, element, startOffset, endOffset);
140     for (ASTNode ref : array) {
141       if (SourceTreeToPsiMap.treeElementToPsi(ref).isValid()) {
142         process((TreeElement)ref, true, true);
143       }
144     }
145   }
146
147   private static void addReferencesInRange(ArrayList<ASTNode> array, TreeElement parent, int startOffset, int endOffset) {
148     if (parent.getElementType() == JavaElementType.JAVA_CODE_REFERENCE || parent.getElementType() == JavaElementType.REFERENCE_EXPRESSION) {
149       array.add(parent);
150       return;
151     }
152
153     if (parent.getPsi() instanceof PsiFile && JspPsiUtil.isInJspFile(parent.getPsi())) {
154       final JspFile jspFile = JspPsiUtil.getJspFile(parent.getPsi());
155       JspClass jspClass = (JspClass) jspFile.getJavaClass();
156       addReferencesInRange(array, (TreeElement)jspClass.getNode(), startOffset, endOffset);
157       return;
158     }
159
160     addReferencesInRangeForComposite(array, parent, startOffset, endOffset);
161   }
162
163   private static void addReferencesInRangeForComposite(final ArrayList<ASTNode> array,
164                                                        final TreeElement parent,
165                                                        final int startOffset,
166                                                        final int endOffset) {
167     int offset = 0;
168     for (TreeElement child = parent.getFirstChildNode(); child != null; child = child.getTreeNext()) {
169       int length = child.getTextLength();
170
171       if (startOffset <= offset + length && offset <= endOffset) {
172         final IElementType type = child.getElementType();
173
174         if (type == JavaElementType.JAVA_CODE_REFERENCE || type == JavaElementType.REFERENCE_EXPRESSION) {
175           array.add(child);
176         } else {
177           addReferencesInRangeForComposite(array, child, startOffset - offset, endOffset - offset);
178         }
179       }
180       offset += length;
181     }
182   }
183
184   private static ASTNode makeShortReference(@NotNull CompositeElement reference, @NotNull PsiClass refClass, boolean addImports) {
185     @NotNull final PsiJavaCodeReferenceElement psiReference = (PsiJavaCodeReferenceElement)reference.getPsi();
186     final PsiQualifiedReference reference1 = getClassReferenceToShorten(refClass, addImports, psiReference);
187     if (reference1 != null) replaceReferenceWithShort(reference1);
188     return reference;
189   }
190
191   @Nullable
192   public static PsiQualifiedReference getClassReferenceToShorten(@NotNull final PsiClass refClass,
193                                                                  final boolean addImports,
194                                                                  @NotNull final PsiQualifiedReference reference) {
195     PsiClass parentClass = refClass.getContainingClass();
196     if (parentClass != null) {
197       JavaPsiFacade facade = JavaPsiFacade.getInstance(parentClass.getProject());
198       final PsiResolveHelper resolveHelper = facade.getResolveHelper();
199       if (resolveHelper.isAccessible(refClass, reference, null) && isSafeToShortenReference(reference.getReferenceName(), reference, refClass)) {
200         return reference;
201       }
202
203       if (!CodeStyleSettingsManager.getSettings(reference.getProject()).INSERT_INNER_CLASS_IMPORTS) {
204         final PsiElement qualifier = reference.getQualifier();
205         if (qualifier instanceof PsiQualifiedReference) {
206           return getClassReferenceToShorten(parentClass, addImports, (PsiQualifiedReference)qualifier);
207         }
208         return null;
209       }
210     }
211
212     if (addImports && !((PsiImportHolder) reference.getContainingFile()).importClass(refClass)) return null;
213     if (!isSafeToShortenReference(reference, refClass)) return null;
214     return reference;
215   }
216
217   private static boolean isSafeToShortenReference(@NotNull PsiElement psiReference, @NotNull PsiClass refClass) {
218     return isSafeToShortenReference(refClass.getName(), psiReference, refClass);
219   }
220
221   private static boolean isSafeToShortenReference(final String referenceText, final PsiElement psiReference, final PsiClass refClass) {
222     final PsiManager manager = refClass.getManager();
223     final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
224     final PsiResolveHelper helper = facade.getResolveHelper();
225     if (manager.areElementsEquivalent(refClass, helper.resolveReferencedClass(referenceText, psiReference))) {
226       PsiElement parent = psiReference.getParent();
227       if (parent instanceof PsiJavaCodeReferenceElement && parent.getParent() instanceof PsiNewExpression) return true;
228       return helper.resolveReferencedVariable(referenceText, psiReference) == null;
229     }
230     return false;
231   }
232
233   @NotNull
234   private static ASTNode replaceReferenceWithShort(PsiQualifiedReference reference) {
235     final ASTNode node = reference.getNode();
236     assert node != null;
237     dequalifyImpl((CompositeElement)node);
238     return node;
239   }
240
241   private static void dequalifyImpl(@NotNull CompositeElement reference) {
242     final ASTNode qualifier = reference.findChildByRole(ChildRole.QUALIFIER);
243     if (qualifier != null) {
244       reference.deleteChildInternal(qualifier);
245     }
246   }
247
248   private static ASTNode replaceReferenceWithFQ(ASTNode reference, PsiClass refClass) {
249       ((SourceJavaCodeReference)reference).fullyQualify(refClass);
250     return reference;
251   }
252 }
253