Added
Link Here
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
5 |
* |
6 |
* The contents of this file are subject to the terms of either the GNU |
7 |
* General Public License Version 2 only ("GPL") or the Common |
8 |
* Development and Distribution License("CDDL") (collectively, the |
9 |
* "License"). You may not use this file except in compliance with the |
10 |
* License. You can obtain a copy of the License at |
11 |
* http://www.netbeans.org/cddl-gplv2.html |
12 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
13 |
* specific language governing permissions and limitations under the |
14 |
* License. When distributing the software, include this License Header |
15 |
* Notice in each file and include the License file at |
16 |
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this |
17 |
* particular file as subject to the "Classpath" exception as provided |
18 |
* by Sun in the GPL Version 2 section of the License file that |
19 |
* accompanied this code. If applicable, add the following below the |
20 |
* License Header, with the fields enclosed by brackets [] replaced by |
21 |
* your own identifying information: |
22 |
* "Portions Copyrighted [year] [name of copyright owner]" |
23 |
* |
24 |
* Contributor(s): |
25 |
* |
26 |
* Portions Copyrighted 2009 Sun Microsystems, Inc. |
27 |
*/ |
28 |
package org.netbeans.modules.java.hints; |
29 |
|
30 |
import com.sun.source.tree.CompilationUnitTree; |
31 |
import com.sun.source.tree.ExpressionTree; |
32 |
import com.sun.source.tree.ImportTree; |
33 |
import com.sun.source.tree.MethodInvocationTree; |
34 |
import com.sun.source.tree.Tree.Kind; |
35 |
import com.sun.source.util.TreePath; |
36 |
import java.util.Collections; |
37 |
import java.util.EnumSet; |
38 |
import java.util.List; |
39 |
import java.util.Set; |
40 |
import java.util.concurrent.atomic.AtomicBoolean; |
41 |
import javax.lang.model.element.Element; |
42 |
import javax.lang.model.element.ElementKind; |
43 |
import javax.lang.model.element.Modifier; |
44 |
import javax.lang.model.element.TypeElement; |
45 |
import javax.lang.model.type.TypeMirror; |
46 |
import javax.lang.model.util.Types; |
47 |
import org.netbeans.api.java.source.CompilationInfo; |
48 |
import org.netbeans.api.java.source.ElementUtilities; |
49 |
import org.netbeans.api.java.source.JavaSource; |
50 |
import org.netbeans.api.java.source.JavaSource.Phase; |
51 |
import org.netbeans.api.java.source.SourceUtils; |
52 |
import org.netbeans.api.java.source.Task; |
53 |
import org.netbeans.api.java.source.TreeMaker; |
54 |
import org.netbeans.api.java.source.TreePathHandle; |
55 |
import org.netbeans.api.java.source.WorkingCopy; |
56 |
import org.netbeans.modules.java.hints.spi.AbstractHint; |
57 |
import org.netbeans.spi.editor.hints.ChangeInfo; |
58 |
import org.netbeans.spi.editor.hints.ErrorDescription; |
59 |
import org.netbeans.spi.editor.hints.ErrorDescriptionFactory; |
60 |
import org.netbeans.spi.editor.hints.Fix; |
61 |
import org.openide.util.NbBundle; |
62 |
import static org.netbeans.modules.editor.java.Utilities.getElementName; |
63 |
|
64 |
/** |
65 |
* Hint offering to convert a qualified static method into a static import. e.g. |
66 |
* <code>Math.abs(-1)</code> -> <code>abs(-1)</code>. |
67 |
* |
68 |
* @author Sam Halliday |
69 |
* @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=89258">RFE 89258</a> |
70 |
*/ |
71 |
public class StaticImport extends AbstractHint { |
72 |
|
73 |
private final AtomicBoolean cancel = new AtomicBoolean(); |
74 |
|
75 |
public StaticImport() { |
76 |
super(true, false, HintSeverity.CURRENT_LINE_WARNING); |
77 |
} |
78 |
|
79 |
@Override |
80 |
public String getDescription() { |
81 |
return NbBundle.getMessage(StaticImport.class, "DSC_StaticImport"); |
82 |
} |
83 |
|
84 |
public Set<Kind> getTreeKinds() { |
85 |
return EnumSet.of(Kind.METHOD_INVOCATION); |
86 |
} |
87 |
|
88 |
public List<ErrorDescription> run(CompilationInfo info, TreePath treePath) { |
89 |
if (treePath.getLeaf().getKind() != Kind.METHOD_INVOCATION) { |
90 |
return null; |
91 |
} |
92 |
cancel.set(false); |
93 |
MethodInvocationTree tree = (MethodInvocationTree) treePath.getLeaf(); |
94 |
ExpressionTree identifier = tree.getMethodSelect(); |
95 |
if (identifier.getKind() != Kind.MEMBER_SELECT) { |
96 |
return null; |
97 |
} |
98 |
Element e = info.getTrees().getElement(new TreePath(treePath, identifier)); |
99 |
if (e == null || !e.getModifiers().contains(Modifier.STATIC)) { |
100 |
return null; |
101 |
} |
102 |
Element enclosingEl = e.getEnclosingElement(); |
103 |
if (enclosingEl == null) { |
104 |
return null; |
105 |
} |
106 |
// XXX is there a better way to ignore error cases |
107 |
String sn = e.getSimpleName().toString(); |
108 |
if (!isValidStaticMethod(info, getElementName(enclosingEl, true).toString(), sn)) { |
109 |
return null; |
110 |
} |
111 |
// TODO ignore case where source code is less than Java 1.5 |
112 |
Element klass = info.getTrees().getElement(getContainingClass(treePath)); |
113 |
String fqn = null; |
114 |
String fqn1 = getMethodFqn(e); |
115 |
if (!isSubTypeOrInnerOfSubType(info, klass, enclosingEl) && !isStaticallyImported(info, fqn1)) { |
116 |
if (hasMethodNameClash(info, klass, sn) || hasStaticImportSimpleNameClash(info, sn)) { |
117 |
return null; |
118 |
} |
119 |
fqn = fqn1; |
120 |
} |
121 |
List<Fix> fixes = Collections.<Fix>singletonList(new FixImpl(TreePathHandle.create(treePath, info), fqn)); |
122 |
String desc = NbBundle.getMessage(AddOverrideAnnotation.class, "HINT_StaticImport"); |
123 |
int start = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), identifier); |
124 |
int end = (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), identifier); |
125 |
ErrorDescription ed = ErrorDescriptionFactory.createErrorDescription(getSeverity().toEditorSeverity(), desc, fixes, info.getFileObject(), start, end); |
126 |
if (cancel.get()) { |
127 |
return null; |
128 |
} |
129 |
return Collections.singletonList(ed); |
130 |
} |
131 |
|
132 |
public String getId() { |
133 |
return StaticImport.class.getName(); |
134 |
} |
135 |
|
136 |
public String getDisplayName() { |
137 |
return NbBundle.getMessage(StaticImport.class, "DSC_StaticImport"); |
138 |
} |
139 |
|
140 |
public void cancel() { |
141 |
cancel.set(true); |
142 |
} |
143 |
|
144 |
public static final class FixImpl implements Fix, Task<WorkingCopy> { |
145 |
|
146 |
private final TreePathHandle handle; |
147 |
private final String fqn; |
148 |
|
149 |
/** |
150 |
* @param handle to the METHOD_INVOCATION |
151 |
* @param fqn to statically import, or null to not perform any imports |
152 |
*/ |
153 |
public FixImpl(TreePathHandle handle, String fqn) { |
154 |
this.handle = handle; |
155 |
this.fqn = fqn; |
156 |
} |
157 |
|
158 |
public String getText() { |
159 |
if (fqn == null) { |
160 |
return NbBundle.getMessage(StaticImport.class, "HINT_StaticImport"); |
161 |
} else { |
162 |
return NbBundle.getMessage(StaticImport.class, "HINT_StaticImport2", fqn); |
163 |
} |
164 |
} |
165 |
|
166 |
public ChangeInfo implement() throws Exception { |
167 |
JavaSource js = JavaSource.forFileObject(handle.getFileObject()); |
168 |
js.runModificationTask(this).commit(); |
169 |
return null; |
170 |
} |
171 |
|
172 |
public void run(WorkingCopy copy) throws Exception { |
173 |
if (copy.toPhase(Phase.RESOLVED).compareTo(Phase.RESOLVED) < 0) { |
174 |
return; |
175 |
} |
176 |
TreePath treePath = handle.resolve(copy); |
177 |
if (treePath == null || treePath.getLeaf().getKind() != Kind.METHOD_INVOCATION) { |
178 |
return; |
179 |
} |
180 |
MethodInvocationTree tree = (MethodInvocationTree) treePath.getLeaf(); |
181 |
ExpressionTree identifier = tree.getMethodSelect(); |
182 |
Element e = copy.getTrees().getElement(new TreePath(treePath, identifier)); |
183 |
if (e == null || !e.getModifiers().contains(Modifier.STATIC) || identifier.getKind() != Kind.MEMBER_SELECT) { |
184 |
return; |
185 |
} |
186 |
TreeMaker make = copy.getTreeMaker(); |
187 |
copy.rewrite(identifier, make.Identifier(e.getSimpleName())); |
188 |
if (fqn == null) { |
189 |
return; |
190 |
} |
191 |
CompilationUnitTree cut = copy.getCompilationUnit(); |
192 |
CompilationUnitTree nue = SourceUtils.addStaticImports(cut, Collections.singletonList(fqn), make); |
193 |
copy.rewrite(cut, nue); |
194 |
} |
195 |
} |
196 |
|
197 |
// returns true if a METHOD is enclosed in element with simple name sn |
198 |
private static boolean hasMethodWithSimpleName(CompilationInfo info, Element element, final String sn) { |
199 |
Iterable<? extends Element> members = |
200 |
info.getElementUtilities().getMembers(element.asType(), new ElementUtilities.ElementAcceptor() { |
201 |
|
202 |
public boolean accept(Element e, TypeMirror type) { |
203 |
if (e.getKind() == ElementKind.METHOD && e.getSimpleName().toString().equals(sn)) { |
204 |
return true; |
205 |
} |
206 |
return false; |
207 |
} |
208 |
}); |
209 |
return members.iterator().hasNext(); |
210 |
} |
211 |
|
212 |
/** |
213 |
* @param info |
214 |
* @param simpleName of static method. |
215 |
* @return true if a static import exists with the same simple name. |
216 |
* Caveat, expect false positives on protected and default visibility methods from wildcard static imports. |
217 |
*/ |
218 |
public static boolean hasStaticImportSimpleNameClash(CompilationInfo info, String simpleName) { |
219 |
for (ImportTree i : info.getCompilationUnit().getImports()) { |
220 |
if (!i.isStatic()) { |
221 |
continue; |
222 |
} |
223 |
String q = i.getQualifiedIdentifier().toString(); |
224 |
if (q.endsWith(".*")) { //NOI18N |
225 |
TypeElement ie = info.getElements().getTypeElement(q.substring(0, q.length() - 2)); |
226 |
if (ie == null) { |
227 |
continue; |
228 |
} |
229 |
for (Element enclosed : ie.getEnclosedElements()) { |
230 |
Set<Modifier> modifiers = enclosed.getModifiers(); |
231 |
if (enclosed.getKind() != ElementKind.METHOD || !modifiers.contains(Modifier.STATIC) || modifiers.contains(Modifier.PRIVATE)) { |
232 |
continue; |
233 |
} |
234 |
String sn1 = enclosed.getSimpleName().toString(); |
235 |
if (simpleName.equals(sn1)) { |
236 |
return true; |
237 |
} |
238 |
} |
239 |
} else { |
240 |
int endIndex = q.lastIndexOf("."); //NOI18N |
241 |
if (endIndex == -1 || endIndex >= q.length() - 1) { |
242 |
continue; |
243 |
} |
244 |
if (q.substring(endIndex).equals(simpleName)) { |
245 |
return true; |
246 |
} |
247 |
} |
248 |
} |
249 |
return false; |
250 |
} |
251 |
|
252 |
/** |
253 |
* @param info |
254 |
* @param t1 |
255 |
* @param t3 |
256 |
* @return true iff the first type (or its containing class in the case of inner classes) |
257 |
* is a subtype of the second. |
258 |
* @see Types#isSubtype(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror) |
259 |
*/ |
260 |
private static boolean isSubTypeOrInnerOfSubType(CompilationInfo info, Element t1, Element t2) { |
261 |
boolean isSubtype = info.getTypes().isSubtype(t1.asType(), t2.asType()); |
262 |
boolean isInnerClass = t1.getEnclosingElement().getKind() == ElementKind.CLASS; |
263 |
return isSubtype || (isInnerClass && info.getTypes().isSubtype(t1.getEnclosingElement().asType(), t2.asType())); |
264 |
} |
265 |
|
266 |
/** |
267 |
* @param info |
268 |
* @param klass the element for a CLASS |
269 |
* @param member the STATIC, MEMBER_SELECT Element for a MethodInvocationTree |
270 |
* @return true if member has a simple name which would clash with local or inherited |
271 |
* methods in klass (which may be an inner or static class). |
272 |
*/ |
273 |
public static boolean hasMethodNameClash(CompilationInfo info, Element klass, String simpleName) { |
274 |
assert klass != null; |
275 |
assert klass.getKind() == ElementKind.CLASS; |
276 |
|
277 |
// check the members and inherited members of the klass |
278 |
if (hasMethodWithSimpleName(info, klass, simpleName)) { |
279 |
return true; |
280 |
} |
281 |
Element klassEnclosing = klass.getEnclosingElement(); |
282 |
return (klassEnclosing != null && klassEnclosing.getKind() == ElementKind.CLASS && hasMethodWithSimpleName(info, klassEnclosing, simpleName)); |
283 |
} |
284 |
|
285 |
/** |
286 |
* @param e |
287 |
* @return the FQN for a METHOD Element |
288 |
*/ |
289 |
public static String getMethodFqn(Element e) { |
290 |
// TODO: or alternatively, upgrade getElementName to handled METHOD |
291 |
assert e.getKind() == ElementKind.METHOD; |
292 |
return getElementName(e.getEnclosingElement(), true) + "." + e.getSimpleName(); |
293 |
} |
294 |
|
295 |
/** |
296 |
* @param tp |
297 |
* @return the first path which is a CLASS or null if none found |
298 |
*/ |
299 |
public static TreePath getContainingClass(TreePath tp) { |
300 |
while (tp != null && tp.getLeaf().getKind() != Kind.CLASS) { |
301 |
tp = tp.getParentPath(); |
302 |
} |
303 |
return tp; |
304 |
} |
305 |
|
306 |
// return true if the fqn already has a static import |
307 |
private static boolean isStaticallyImported(CompilationInfo info, String fqn) { |
308 |
for (ImportTree i : info.getCompilationUnit().getImports()) { |
309 |
if (!i.isStatic()) { |
310 |
continue; |
311 |
} |
312 |
String q = i.getQualifiedIdentifier().toString(); |
313 |
if (q.endsWith(".*") && fqn.startsWith(q.substring(0, q.length() - 1))) { //NOI18N |
314 |
return true; |
315 |
} |
316 |
if (q.equals(fqn)) { |
317 |
return true; |
318 |
} |
319 |
} |
320 |
return false; |
321 |
} |
322 |
|
323 |
/** |
324 |
* @param info |
325 |
* @param fqn of the containing class |
326 |
* @param simpleName of the method |
327 |
* @return true if {@code fqn.simpleName} represents a valid static method |
328 |
*/ |
329 |
public static boolean isValidStaticMethod(CompilationInfo info, String fqn, String simpleName) { |
330 |
TypeElement ie = info.getElements().getTypeElement(fqn); |
331 |
if (ie == null) { |
332 |
return false; |
333 |
} |
334 |
for (Element enclosed : ie.getEnclosedElements()) { |
335 |
Set<Modifier> modifiers = enclosed.getModifiers(); |
336 |
if (enclosed.getKind() != ElementKind.METHOD || !modifiers.contains(Modifier.STATIC) || modifiers.contains(Modifier.PRIVATE)) { |
337 |
continue; |
338 |
} |
339 |
String sn1 = enclosed.getSimpleName().toString(); |
340 |
if (simpleName.equals(sn1)) { |
341 |
return true; |
342 |
} |
343 |
} |
344 |
return false; |
345 |
} |
346 |
} |