Line
Link Here
|
0 |
-- /Users/tomslot/SUN/src/web-main/java.source/src/org/netbeans/api/java/source/ImportsProcesor.java |
0 |
++ /Users/tomslot/SUN/src/web-main/java.source/src/org/netbeans/api/java/source/SourceUtils.java |
Lines 1-7
Link Here
|
1 |
/* |
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
3 |
* |
4 |
* Copyright 2010 Sun Microsystems, Inc. All rights reserved. |
4 |
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. |
5 |
* |
5 |
* |
6 |
* The contents of this file are subject to the terms of either the GNU |
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 |
7 |
* General Public License Version 2 only ("GPL") or the Common |
Lines 21-26
Link Here
|
21 |
* your own identifying information: |
21 |
* your own identifying information: |
22 |
* "Portions Copyrighted [year] [name of copyright owner]" |
22 |
* "Portions Copyrighted [year] [name of copyright owner]" |
23 |
* |
23 |
* |
|
|
24 |
* Contributor(s): |
25 |
* |
26 |
* The Original Software is NetBeans. The Initial Developer of the Original |
27 |
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun |
28 |
* Microsystems, Inc. All Rights Reserved. |
29 |
* |
24 |
* If you wish your version of this file to be governed by only the CDDL |
30 |
* If you wish your version of this file to be governed by only the CDDL |
25 |
* or only the GPL Version 2, indicate your decision by adding |
31 |
* or only the GPL Version 2, indicate your decision by adding |
26 |
* "[Contributor] elects to include this software in this distribution |
32 |
* "[Contributor] elects to include this software in this distribution |
Lines 31-59
Link Here
|
31 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
37 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
32 |
* Version 2 license, then the option applies only if the new code is |
38 |
* Version 2 license, then the option applies only if the new code is |
33 |
* made subject to such option by the copyright holder. |
39 |
* made subject to such option by the copyright holder. |
|
|
40 |
*/ |
41 |
|
42 |
package org.netbeans.api.java.source; |
43 |
|
44 |
import java.io.File; |
45 |
import java.io.IOException; |
46 |
import java.net.MalformedURLException; |
47 |
import java.net.URI; |
48 |
import java.net.URL; |
49 |
import java.util.*; |
50 |
|
51 |
import javax.lang.model.element.*; |
52 |
import javax.lang.model.element.VariableElement; |
53 |
import javax.lang.model.type.ArrayType; |
54 |
import javax.lang.model.type.DeclaredType; |
55 |
import javax.lang.model.type.TypeKind; |
56 |
import javax.lang.model.type.TypeMirror; |
57 |
import javax.lang.model.type.WildcardType; |
58 |
import javax.lang.model.util.ElementFilter; |
59 |
|
60 |
import com.sun.source.tree.*; |
61 |
import com.sun.source.util.TreePath; |
62 |
import com.sun.source.util.TreePathScanner; |
63 |
import com.sun.source.util.Trees; |
64 |
import com.sun.tools.javac.api.JavacTaskImpl; |
65 |
import com.sun.tools.javac.code.Flags; |
66 |
import com.sun.tools.javac.code.Symbol; |
67 |
import com.sun.tools.javac.code.Symbol.*; |
68 |
import com.sun.tools.javac.code.Type; |
69 |
import com.sun.tools.javac.code.Types; |
70 |
import com.sun.tools.javac.comp.Check; |
71 |
import com.sun.tools.javac.model.JavacElements; |
72 |
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; |
73 |
import com.sun.tools.javac.util.Context; |
74 |
import java.io.UnsupportedEncodingException; |
75 |
import java.net.URISyntaxException; |
76 |
import java.net.URLEncoder; |
77 |
import java.util.concurrent.Future; |
78 |
import java.util.logging.Level; |
79 |
import java.util.logging.Logger; |
80 |
import javax.swing.SwingUtilities; |
81 |
|
82 |
import org.netbeans.api.annotations.common.NonNull; |
83 |
import org.netbeans.api.editor.mimelookup.MimeLookup; |
84 |
import org.netbeans.api.editor.mimelookup.MimePath; |
85 |
import org.netbeans.api.java.classpath.ClassPath; |
86 |
import org.netbeans.api.java.lexer.JavaTokenId; |
87 |
import org.netbeans.api.java.classpath.GlobalPathRegistry; |
88 |
import org.netbeans.api.java.queries.JavadocForBinaryQuery; |
89 |
import org.netbeans.api.java.queries.JavadocForBinaryQuery.Result; |
90 |
import org.netbeans.api.java.queries.SourceForBinaryQuery; |
91 |
import org.netbeans.api.java.source.ClasspathInfo.PathKind; |
92 |
import org.netbeans.api.java.source.JavaSource.Phase; |
93 |
import org.netbeans.api.lexer.TokenHierarchy; |
94 |
import org.netbeans.api.lexer.TokenSequence; |
95 |
import org.netbeans.modules.java.JavaDataLoader; |
96 |
import org.netbeans.modules.java.source.indexing.JavaCustomIndexer; |
97 |
import org.netbeans.modules.java.source.indexing.JavaIndex; |
98 |
import org.netbeans.modules.java.source.parsing.ClasspathInfoProvider; |
99 |
import org.netbeans.modules.java.source.parsing.FileObjects; |
100 |
import org.netbeans.modules.java.source.parsing.JavacParser; |
101 |
import org.netbeans.modules.java.source.usages.ClassIndexImpl; |
102 |
import org.netbeans.modules.java.source.usages.ClassIndexManager; |
103 |
import org.netbeans.modules.java.source.usages.ClasspathInfoAccessor; |
104 |
import org.netbeans.modules.java.source.usages.ExecutableFilesIndex; |
105 |
import org.netbeans.modules.parsing.api.ParserManager; |
106 |
import org.netbeans.modules.parsing.api.ResultIterator; |
107 |
import org.netbeans.modules.parsing.api.UserTask; |
108 |
import org.netbeans.modules.parsing.api.indexing.IndexingManager; |
109 |
import org.netbeans.modules.parsing.impl.indexing.friendapi.IndexingController; |
110 |
import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
111 |
|
112 |
import org.openide.filesystems.FileObject; |
113 |
import org.openide.filesystems.FileStateInvalidException; |
114 |
import org.openide.filesystems.FileUtil; |
115 |
import org.openide.filesystems.URLMapper; |
116 |
import org.openide.util.Exceptions; |
117 |
import org.openide.util.Lookup; |
118 |
import org.openide.util.Parameters; |
119 |
import org.openide.util.Utilities; |
120 |
|
121 |
/** |
34 |
* |
122 |
* |
35 |
* Contributor(s): |
123 |
* @author Dusan Balek |
|
|
124 |
*/ |
125 |
public class SourceUtils { |
126 |
|
127 |
private static final String PACKAGE_SUMMARY = "package-summary"; //NOI18N |
128 |
|
129 |
private SourceUtils() {} |
130 |
|
131 |
/** |
132 |
* @since 0.21 |
133 |
*/ |
134 |
public static TokenSequence<JavaTokenId> getJavaTokenSequence(final TokenHierarchy hierarchy, final int offset) { |
135 |
if (hierarchy != null) { |
136 |
TokenSequence<?> ts = hierarchy.tokenSequence(); |
137 |
while(ts != null && (offset == 0 || ts.moveNext())) { |
138 |
ts.move(offset); |
139 |
if (ts.language() == JavaTokenId.language()) |
140 |
return (TokenSequence<JavaTokenId>)ts; |
141 |
if (!ts.moveNext() && !ts.movePrevious()) |
142 |
return null; |
143 |
ts = ts.embedded(); |
144 |
} |
145 |
} |
146 |
return null; |
147 |
} |
148 |
|
149 |
public static boolean checkTypesAssignable(CompilationInfo info, TypeMirror from, TypeMirror to) { |
150 |
Context c = ((JavacTaskImpl) info.impl.getJavacTask()).getContext(); |
151 |
if (from.getKind() == TypeKind.DECLARED) { |
152 |
com.sun.tools.javac.util.List<Type> typeVars = com.sun.tools.javac.util.List.nil(); |
153 |
for (TypeMirror tm : ((DeclaredType)from).getTypeArguments()) { |
154 |
if (tm.getKind() == TypeKind.TYPEVAR) |
155 |
typeVars = typeVars.append((Type)tm); |
156 |
} |
157 |
if (!typeVars.isEmpty()) |
158 |
from = new Type.ForAll(typeVars, (Type)from); |
159 |
} else if (from.getKind() == TypeKind.WILDCARD) { |
160 |
from = Types.instance(c).upperBound((Type)from); |
161 |
} |
162 |
return Check.instance(c).checkType(null, (Type)from, (Type)to).getKind() != TypeKind.ERROR; |
163 |
} |
164 |
|
165 |
public static TypeMirror getBound(WildcardType wildcardType) { |
166 |
Type.TypeVar bound = ((Type.WildcardType)wildcardType).bound; |
167 |
return bound != null ? bound.bound : null; |
168 |
} |
169 |
|
170 |
/** |
171 |
* Returns the type element within which this member or constructor |
172 |
* is declared. Does not accept packages |
173 |
* If this is the declaration of a top-level type (a non-nested class |
174 |
* or interface), returns null. |
36 |
* |
175 |
* |
37 |
* Portions Copyrighted 2010 Sun Microsystems, Inc. |
176 |
* @return the type declaration within which this member or constructor |
|
|
177 |
* is declared, or null if there is none |
178 |
* @throws IllegalArgumentException if the provided element is a package element |
179 |
* @deprecated use {@link ElementUtilities#enclosingTypeElement(javax.lang.model.element.Element)} |
38 |
*/ |
180 |
*/ |
|
|
181 |
public static @Deprecated TypeElement getEnclosingTypeElement( Element element ) throws IllegalArgumentException { |
182 |
return ElementUtilities.enclosingTypeElementImpl(element); |
183 |
} |
39 |
|
184 |
|
40 |
package org.netbeans.api.java.source; |
185 |
public static TypeElement getOutermostEnclosingTypeElement( Element element ) { |
41 |
|
186 |
|
42 |
import javax.swing.text.Document; |
187 |
Element ec = getEnclosingTypeElement( element ); |
|
|
188 |
if (ec == null) { |
189 |
ec = element; |
190 |
} |
43 |
|
191 |
|
|
|
192 |
while( ec.getEnclosingElement().getKind().isClass() || |
193 |
ec.getEnclosingElement().getKind().isInterface() ) { |
194 |
|
195 |
ec = ec.getEnclosingElement(); |
196 |
} |
197 |
|
198 |
return (TypeElement)ec; |
199 |
} |
200 |
|
201 |
/**Resolve full qualified name in the given context. Adds import statement as necessary. |
202 |
* Returns name that resolved to a given FQN in given context (either simple name |
203 |
* or full qualified name). Handles import conflicts. |
204 |
* |
205 |
* <br><b>Note:</b> if the <code>info</code> passed to this method is not an instance of {@link WorkingCopy}, |
206 |
* missing import statement is added from a separate modification task executed asynchronously. |
207 |
* <br><b>Note:</b> after calling this method, it is not permitted to rewrite copy.getCompilationUnit(). |
208 |
* |
209 |
* @param info CompilationInfo over which the method should work |
210 |
* @param context in which the fully qualified should be resolved |
211 |
* @param fqn the fully qualified name to resolve |
212 |
* @return either a simple name or a FQN that will resolve to given fqn in given context |
213 |
*/ |
214 |
public static String resolveImport(final CompilationInfo info, final TreePath context, final String fqn) throws NullPointerException, IOException { |
215 |
if (info == null) |
216 |
throw new NullPointerException(); |
217 |
if (context == null) |
218 |
throw new NullPointerException(); |
219 |
if (fqn == null) |
220 |
throw new NullPointerException(); |
221 |
|
222 |
CompilationUnitTree cut = info.getCompilationUnit(); |
223 |
final Trees trees = info.getTrees(); |
224 |
final Scope scope = trees.getScope(context); |
225 |
String qName = fqn; |
226 |
StringBuilder sqName = new StringBuilder(); |
227 |
String sName = null; |
228 |
boolean clashing = false; |
229 |
ElementUtilities eu = info.getElementUtilities(); |
230 |
ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() { |
231 |
public boolean accept(Element e, TypeMirror type) { |
232 |
return (e.getKind().isClass() || e.getKind().isInterface()) && trees.isAccessible(scope, (TypeElement)e); |
233 |
} |
234 |
}; |
235 |
while(qName != null && qName.length() > 0) { |
236 |
int lastDot = qName.lastIndexOf('.'); |
237 |
String simple = qName.substring(lastDot < 0 ? 0 : lastDot + 1); |
238 |
if (sName == null) |
239 |
sName = simple; |
240 |
else |
241 |
sqName.insert(0, '.'); |
242 |
sqName.insert(0, simple); |
243 |
if (info.getElements().getTypeElement(qName) != null) { |
244 |
boolean matchFound = false; |
245 |
for(Element e : eu.getLocalMembersAndVars(scope, acceptor)) { |
246 |
if (simple.contentEquals(e.getSimpleName())) { |
247 |
//either a clash or already imported: |
248 |
if (qName.contentEquals(((TypeElement)e).getQualifiedName())) { |
249 |
return sqName.toString(); |
250 |
} else if (fqn == qName) { |
251 |
clashing = true; |
252 |
} |
253 |
matchFound = true; |
254 |
break; |
255 |
} |
256 |
} |
257 |
if (!matchFound) { |
258 |
for(TypeElement e : eu.getGlobalTypes(acceptor)) { |
259 |
if (simple.contentEquals(e.getSimpleName())) { |
260 |
//either a clash or already imported: |
261 |
if (qName.contentEquals(e.getQualifiedName())) { |
262 |
return sqName.toString(); |
263 |
} else if (fqn == qName) { |
264 |
clashing = true; |
265 |
} |
266 |
break; |
267 |
} |
268 |
} |
269 |
} |
270 |
} |
271 |
qName = lastDot < 0 ? null : qName.substring(0, lastDot); |
272 |
} |
273 |
if (clashing) |
274 |
return fqn; |
275 |
|
276 |
//not imported/visible so far by any means: |
277 |
String topLevelLanguageMIMEType = info.getFileObject().getMIMEType(); |
278 |
if ("text/x-java".equals(topLevelLanguageMIMEType)){ |
279 |
if (info instanceof WorkingCopy) { |
280 |
CompilationUnitTree nue = (CompilationUnitTree) ((WorkingCopy)info).getChangeSet().get(cut); |
281 |
cut = nue != null ? nue : cut; |
282 |
((WorkingCopy)info).rewrite(info.getCompilationUnit(), addImports(cut, Collections.singletonList(fqn), ((WorkingCopy)info).getTreeMaker())); |
283 |
} else { |
284 |
SwingUtilities.invokeLater(new Runnable() { |
285 |
public void run() { |
286 |
try { |
287 |
ModificationResult.runModificationTask(Collections.singletonList(info.getSnapshot().getSource()), new UserTask() { |
288 |
@Override |
289 |
public void run(ResultIterator resultIterator) throws Exception { |
290 |
WorkingCopy copy = WorkingCopy.get(resultIterator.getParserResult()); |
291 |
copy.toPhase(Phase.ELEMENTS_RESOLVED); |
292 |
copy.rewrite(copy.getCompilationUnit(), addImports(copy.getCompilationUnit(), Collections.singletonList(fqn), copy.getTreeMaker())); |
293 |
} |
294 |
}).commit(); |
295 |
} catch (Exception e) { |
296 |
Exceptions.printStackTrace(e); |
297 |
} |
298 |
} |
299 |
}); |
300 |
} |
301 |
} else { // embedded java, look up the handler for the top level language |
302 |
Lookup lookup = MimeLookup.getLookup(MimePath.get(topLevelLanguageMIMEType)); |
303 |
Lookup.Result result = lookup.lookup(new Lookup.Template(ImportsProcesor.class)); |
304 |
Collection<ImportsProcesor> instances = result.allInstances(); |
305 |
|
306 |
for (ImportsProcesor importsProcesor : instances) { |
307 |
importsProcesor.addImport(info.getDocument(), fqn); |
308 |
} |
309 |
|
310 |
} |
311 |
TypeElement te = info.getElements().getTypeElement(fqn); |
312 |
if (te != null) { |
313 |
JCCompilationUnit unit = (JCCompilationUnit) info.getCompilationUnit(); |
314 |
unit.namedImportScope = unit.namedImportScope.dupUnshared(); |
315 |
unit.namedImportScope.enterIfAbsent((Symbol) te); |
316 |
} |
317 |
return sName; |
318 |
} |
319 |
|
44 |
/** |
320 |
/** |
45 |
* Interface that allows to hook custom code to the action of inserting import |
|
|
46 |
* when code completion is called within embedded Java code |
47 |
* |
321 |
* |
48 |
* @author Tomasz.Slota@Sun.COM |
322 |
* |
49 |
*/ |
323 |
*/ |
50 |
public interface ImportsProcesor { |
324 |
private static CompilationUnitTree addImports(CompilationUnitTree cut, List<String> toImport, TreeMaker make) |
|
|
325 |
throws IOException { |
326 |
// do not modify the list given by the caller (may be reused or immutable). |
327 |
toImport = new ArrayList<String>(toImport); |
328 |
Collections.sort(toImport); |
329 |
|
330 |
List<ImportTree> imports = new ArrayList<ImportTree>(cut.getImports()); |
331 |
int currentToImport = toImport.size() - 1; |
332 |
int currentExisting = imports.size() - 1; |
333 |
|
334 |
while (currentToImport >= 0 && currentExisting >= 0) { |
335 |
String currentToImportText = toImport.get(currentToImport); |
336 |
|
337 |
while (currentExisting >= 0 && (imports.get(currentExisting).isStatic() || imports.get(currentExisting).getQualifiedIdentifier().toString().compareTo(currentToImportText) > 0)) |
338 |
currentExisting--; |
339 |
|
340 |
if (currentExisting >= 0) { |
341 |
imports.add(currentExisting+1, make.Import(make.Identifier(currentToImportText), false)); |
342 |
currentToImport--; |
343 |
} |
344 |
} |
345 |
// we are at the head of import section and we still have some imports |
346 |
// to add, put them to the very beginning |
347 |
while (currentToImport >= 0) { |
348 |
String importText = toImport.get(currentToImport); |
349 |
imports.add(0, make.Import(make.Identifier(importText), false)); |
350 |
currentToImport--; |
351 |
} |
352 |
// return a copy of the unit with changed imports section |
353 |
return make.CompilationUnit(cut.getPackageName(), imports, cut.getTypeDecls(), cut.getSourceFile()); |
354 |
} |
355 |
|
51 |
/** |
356 |
/** |
52 |
* Handle request to add unresolved import |
357 |
* Returns a {@link FileObject} in which the Element is defined. |
53 |
* (top-level-language specific way) |
358 |
* @param element for which the {@link FileObject} should be located |
|
|
359 |
* @param cpInfo the classpaths context |
360 |
* @return the defining {@link FileObject} or null if it cannot be |
361 |
* found |
54 |
* |
362 |
* |
55 |
* @param doc |
363 |
* @deprecated use {@link getFile(ElementHandle, ClasspathInfo)} |
56 |
* @param fullyQualifiedClassName |
|
|
57 |
*/ |
364 |
*/ |
58 |
void addImport(Document doc, String fullyQualifiedClassName); |
365 |
public static FileObject getFile (Element element, final ClasspathInfo cpInfo) { |
|
|
366 |
Parameters.notNull("element", element); //NOI18N |
367 |
Parameters.notNull("cpInfo", cpInfo); //NOI18N |
368 |
|
369 |
Element prev = element.getKind() == ElementKind.PACKAGE ? element : null; |
370 |
while (element.getKind() != ElementKind.PACKAGE) { |
371 |
prev = element; |
372 |
element = element.getEnclosingElement(); |
59 |
} |
373 |
} |
|
|
374 |
final ElementKind kind = prev.getKind(); |
375 |
if (!(kind.isClass() || kind.isInterface() || kind == ElementKind.PACKAGE)) { |
376 |
return null; |
377 |
} |
378 |
final ElementHandle<? extends Element> handle = ElementHandle.create(prev); |
379 |
return getFile (handle, cpInfo); |
380 |
} |
381 |
|
382 |
/** |
383 |
* Returns a {@link FileObject} of the source file in which the handle is declared. |
384 |
* @param handle to find the {@link FileObject} for |
385 |
* @param cpInfo classpaths for resolving handle |
386 |
* @return {@link FileObject} or null when the source file cannot be found |
387 |
*/ |
388 |
public static FileObject getFile (final ElementHandle<? extends Element> handle, final ClasspathInfo cpInfo) { |
389 |
Parameters.notNull("handle", handle); |
390 |
Parameters.notNull("cpInfo", cpInfo); |
391 |
try { |
392 |
boolean pkg = handle.getKind() == ElementKind.PACKAGE; |
393 |
String[] signature = handle.getSignature(); |
394 |
assert signature.length >= 1; |
395 |
ClassPath cp = ClassPathSupport.createProxyClassPath( |
396 |
new ClassPath[] { |
397 |
cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE), |
398 |
createClassPath(cpInfo,ClasspathInfo.PathKind.OUTPUT), |
399 |
createClassPath(cpInfo,ClasspathInfo.PathKind.BOOT), |
400 |
createClassPath(cpInfo,ClasspathInfo.PathKind.COMPILE), |
401 |
}); |
402 |
String pkgName, className = null; |
403 |
if (pkg) { |
404 |
pkgName = FileObjects.convertPackage2Folder(signature[0]); |
405 |
} |
406 |
else { |
407 |
int index = signature[0].lastIndexOf('.'); //NOI18N |
408 |
if (index<0) { |
409 |
pkgName = ""; //NOI18N |
410 |
className = signature[0]; |
411 |
} |
412 |
else { |
413 |
pkgName = FileObjects.convertPackage2Folder(signature[0].substring(0,index)); |
414 |
className = signature[0].substring(index+1); |
415 |
} |
416 |
} |
417 |
List<FileObject> fos = cp.findAllResources(pkgName); |
418 |
for (FileObject fo : fos) { |
419 |
FileObject root = cp.findOwnerRoot(fo); |
420 |
assert root != null; |
421 |
FileObject[] sourceRoots = SourceForBinaryQuery.findSourceRoots(root.getURL()).getRoots(); |
422 |
ClassPath sourcePath = ClassPathSupport.createClassPath(sourceRoots); |
423 |
LinkedList<FileObject> folders = new LinkedList<FileObject>(sourcePath.findAllResources(pkgName)); |
424 |
if (pkg) { |
425 |
return folders.isEmpty() ? fo : folders.get(0); |
426 |
} |
427 |
else { |
428 |
boolean caseSensitive = isCaseSensitive (); |
429 |
String sourceFileName = getSourceFileName (className); |
430 |
folders.addFirst(fo); |
431 |
for (FileObject folder : folders) { |
432 |
FileObject[] children = folder.getChildren(); |
433 |
for (FileObject child : children) { |
434 |
if (((caseSensitive && child.getName().equals (sourceFileName)) || |
435 |
(!caseSensitive && child.getName().equalsIgnoreCase (sourceFileName))) && |
436 |
(child.isData() && JavaDataLoader.JAVA_EXTENSION.equalsIgnoreCase(child.getExt()))) { |
437 |
return child; |
438 |
} |
439 |
} |
440 |
} |
441 |
FileObject foundFo; |
442 |
if (sourceRoots.length == 0) { |
443 |
foundFo = findSource (signature[0],root); |
444 |
} |
445 |
else { |
446 |
foundFo = findSource (signature[0],sourceRoots); |
447 |
} |
448 |
if (foundFo != null) { |
449 |
return foundFo; |
450 |
} |
451 |
} |
452 |
} |
453 |
} catch (IOException e) { |
454 |
Exceptions.printStackTrace(e); |
455 |
} |
456 |
return null; |
457 |
} |
458 |
|
459 |
private static FileObject findSource (final String binaryName, final FileObject... fos) throws IOException { |
460 |
final ClassIndexManager cim = ClassIndexManager.getDefault(); |
461 |
try { |
462 |
return cim.readLock(new ClassIndexManager.ExceptionAction<FileObject>() { |
463 |
|
464 |
public FileObject run() throws IOException, InterruptedException { |
465 |
for (FileObject fo : fos) { |
466 |
ClassIndexImpl ci = cim.getUsagesQuery(fo.getURL()); |
467 |
if (ci != null) { |
468 |
String sourceName = ci.getSourceName(binaryName); |
469 |
if (sourceName != null) { |
470 |
FileObject result = fo.getFileObject(sourceName); |
471 |
if (result != null) { |
472 |
return result; |
473 |
} |
474 |
} |
475 |
} |
476 |
} |
477 |
return null; |
478 |
} |
479 |
}); |
480 |
} catch (InterruptedException e) { |
481 |
//Never thrown |
482 |
Exceptions.printStackTrace(e); |
483 |
return null; |
484 |
} |
485 |
} |
486 |
|
487 |
/** |
488 |
* Finds {@link URL} of a javadoc page for given element when available. This method |
489 |
* uses {@link JavadocForBinaryQuery} to find the javadoc page for the give element. |
490 |
* For {@link PackageElement} it returns the package-summary.html for given package. |
491 |
* @param element to find the Javadoc for |
492 |
* @param cpInfo classpaths used to resolve |
493 |
* @return the URL of the javadoc page or null when the javadoc is not available. |
494 |
*/ |
495 |
@org.netbeans.api.annotations.common.SuppressWarnings(value={"DMI_COLLECTION_OF_URLS"}/*,justification="URLs have never host part"*/) //NOI18N |
496 |
public static URL getJavadoc (final Element element, final ClasspathInfo cpInfo) { |
497 |
if (element == null || cpInfo == null) { |
498 |
throw new IllegalArgumentException ("Cannot pass null as an argument of the SourceUtils.getJavadoc"); //NOI18N |
499 |
} |
500 |
|
501 |
ClassSymbol clsSym = null; |
502 |
String pkgName; |
503 |
String pageName; |
504 |
boolean buildFragment = false; |
505 |
if (element.getKind() == ElementKind.PACKAGE) { |
506 |
List<? extends Element> els = element.getEnclosedElements(); |
507 |
for (Element e :els) { |
508 |
if (e.getKind().isClass() || e.getKind().isInterface()) { |
509 |
clsSym = (ClassSymbol) e; |
510 |
break; |
511 |
} |
512 |
} |
513 |
if (clsSym == null) { |
514 |
return null; |
515 |
} |
516 |
pkgName = FileObjects.convertPackage2Folder(((PackageElement)element).getQualifiedName().toString()); |
517 |
pageName = PACKAGE_SUMMARY; |
518 |
} |
519 |
else { |
520 |
Element e = element; |
521 |
StringBuilder sb = new StringBuilder(); |
522 |
while(e.getKind() != ElementKind.PACKAGE) { |
523 |
if (e.getKind().isClass() || e.getKind().isInterface()) { |
524 |
if (sb.length() > 0) |
525 |
sb.insert(0, '.'); |
526 |
sb.insert(0, e.getSimpleName()); |
527 |
if (clsSym == null) |
528 |
clsSym = (ClassSymbol)e; |
529 |
} |
530 |
e = e.getEnclosingElement(); |
531 |
} |
532 |
if (clsSym == null) |
533 |
return null; |
534 |
pkgName = FileObjects.convertPackage2Folder(((PackageElement)e).getQualifiedName().toString()); |
535 |
pageName = sb.toString(); |
536 |
buildFragment = element != clsSym; |
537 |
} |
538 |
|
539 |
if (clsSym.completer != null) { |
540 |
clsSym.complete(); |
541 |
} |
542 |
|
543 |
URL sourceRoot = null; |
544 |
Set<URL> binaries = new HashSet<URL>(); |
545 |
try { |
546 |
if (clsSym.classfile != null) { |
547 |
FileObject fo = URLMapper.findFileObject(clsSym.classfile.toUri().toURL()); |
548 |
StringTokenizer tk = new StringTokenizer(pkgName,"/"); //NOI18N |
549 |
for (int i=0 ;fo != null && i<=tk.countTokens(); i++) { |
550 |
fo = fo.getParent(); |
551 |
} |
552 |
if (fo != null) { |
553 |
URL url = fo.getURL(); |
554 |
sourceRoot = JavaIndex.getSourceRootForClassFolder(url); |
555 |
if (sourceRoot == null) { |
556 |
binaries.add(url); |
557 |
} else { |
558 |
// sourceRoot may be a class root in reality |
559 |
binaries.add(sourceRoot); |
560 |
} |
561 |
} |
562 |
} |
563 |
if (sourceRoot != null) { |
564 |
FileObject sourceFo = URLMapper.findFileObject(sourceRoot); |
565 |
if (sourceFo != null) { |
566 |
ClassPath exec = ClassPath.getClassPath(sourceFo, ClassPath.EXECUTE); |
567 |
ClassPath compile = ClassPath.getClassPath(sourceFo, ClassPath.COMPILE); |
568 |
ClassPath source = ClassPath.getClassPath(sourceFo, ClassPath.SOURCE); |
569 |
if (exec == null) { |
570 |
exec = compile; |
571 |
compile = null; |
572 |
} |
573 |
if (exec != null && source != null) { |
574 |
Set<URL> roots = new HashSet<URL>(); |
575 |
for (ClassPath.Entry e : exec.entries()) { |
576 |
roots.add(e.getURL()); |
577 |
} |
578 |
if (compile != null) { |
579 |
for (ClassPath.Entry e : compile.entries()) { |
580 |
roots.remove(e.getURL()); |
581 |
} |
582 |
} |
583 |
List<FileObject> sourceRoots = Arrays.asList(source.getRoots()); |
584 |
out: for (URL e : roots) { |
585 |
FileObject[] res = SourceForBinaryQuery.findSourceRoots(e).getRoots(); |
586 |
for (FileObject fo : res) { |
587 |
if (sourceRoots.contains(fo)) { |
588 |
binaries.add(e); |
589 |
continue out; |
590 |
} |
591 |
} |
592 |
} |
593 |
} |
594 |
} |
595 |
} |
596 |
for (URL binary : binaries) { |
597 |
Result javadocResult = JavadocForBinaryQuery.findJavadoc(binary); |
598 |
URL[] result = javadocResult.getRoots(); |
599 |
for (int cntr = 0; cntr < result.length; cntr++) { |
600 |
if (!result[cntr].toExternalForm().endsWith("/")) { // NOI18N |
601 |
Logger.getLogger(SourceUtils.class.getName()).log(Level.WARNING, "JavadocForBinaryQuery.Result: {0} returned non-folder URL: {1}, ignoring", new Object[] {javadocResult.getClass(), result[cntr].toExternalForm()}); |
602 |
result[cntr] = null; |
603 |
} |
604 |
} |
605 |
ClassPath cp = ClassPathSupport.createClassPath(result); |
606 |
FileObject fo = cp.findResource(pkgName); |
607 |
if (fo != null) { |
608 |
for (FileObject child : fo.getChildren()) { |
609 |
if (pageName.equals(child.getName()) && FileObjects.HTML.equalsIgnoreCase(child.getExt())) { |
610 |
URL url = child.getURL(); |
611 |
CharSequence fragment = null; |
612 |
if (url != null && buildFragment) { |
613 |
fragment = getFragment(element); |
614 |
} |
615 |
if (fragment != null && fragment.length() > 0) { |
616 |
try { |
617 |
// Javadoc fragments may contain chars that must be escaped to comply with RFC 2396. |
618 |
// Unfortunately URLEncoder escapes almost everything but |
619 |
// spaces replaces with '+' char which is wrong so it is |
620 |
// replaced with "%20"escape sequence here. |
621 |
String encodedfragment = URLEncoder.encode(fragment.toString(), "UTF-8"); // NOI18N |
622 |
encodedfragment = encodedfragment.replace("+", "%20"); // NOI18N |
623 |
return new URI(url.toExternalForm() + '#' + encodedfragment).toURL(); |
624 |
} catch (URISyntaxException ex) { |
625 |
Exceptions.printStackTrace(ex); |
626 |
} catch (UnsupportedEncodingException ex) { |
627 |
Exceptions.printStackTrace(ex); |
628 |
} catch (MalformedURLException ex) { |
629 |
Exceptions.printStackTrace(ex); |
630 |
} |
631 |
} |
632 |
return url; |
633 |
} |
634 |
} |
635 |
} |
636 |
} |
637 |
|
638 |
} catch (MalformedURLException e) { |
639 |
Exceptions.printStackTrace(e); |
640 |
} |
641 |
catch (FileStateInvalidException e) { |
642 |
Exceptions.printStackTrace(e); |
643 |
} |
644 |
return null; |
645 |
} |
646 |
|
647 |
private static CharSequence getFragment(Element e) { |
648 |
StringBuilder sb = new StringBuilder(); |
649 |
if (!e.getKind().isClass() && !e.getKind().isInterface()) { |
650 |
if (e.getKind() == ElementKind.CONSTRUCTOR) { |
651 |
sb.append(e.getEnclosingElement().getSimpleName()); |
652 |
} else { |
653 |
sb.append(e.getSimpleName()); |
654 |
} |
655 |
if (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR) { |
656 |
ExecutableElement ee = (ExecutableElement)e; |
657 |
sb.append('('); //NOI18N |
658 |
for (Iterator<? extends VariableElement> it = ee.getParameters().iterator(); it.hasNext();) { |
659 |
VariableElement param = it.next(); |
660 |
appendType(sb, param.asType(), ee.isVarArgs() && !it.hasNext()); |
661 |
if (it.hasNext()) |
662 |
sb.append(", "); |
663 |
} |
664 |
sb.append(')'); //NOI18N |
665 |
} |
666 |
} |
667 |
return sb; |
668 |
} |
669 |
|
670 |
private static void appendType(StringBuilder sb, TypeMirror type, boolean varArg) { |
671 |
switch (type.getKind()) { |
672 |
case ARRAY: |
673 |
appendType(sb, ((ArrayType)type).getComponentType(), false); |
674 |
sb.append(varArg ? "..." : "[]"); //NOI18N |
675 |
break; |
676 |
case DECLARED: |
677 |
sb.append(((TypeElement)((DeclaredType)type).asElement()).getQualifiedName()); |
678 |
break; |
679 |
default: |
680 |
sb.append(type); |
681 |
} |
682 |
} |
683 |
|
684 |
/** |
685 |
* Tests whether the initial scan is in progress. |
686 |
*/ |
687 |
public static boolean isScanInProgress () { |
688 |
return IndexingManager.getDefault().isIndexing(); |
689 |
} |
690 |
|
691 |
/** |
692 |
* Waits for the end of the initial scan, this helper method |
693 |
* is designed for tests which require to wait for end of initial scan. |
694 |
* @throws InterruptedException is thrown when the waiting thread is interrupted. |
695 |
* @deprecated use {@link JavaSource#runWhenScanFinished} |
696 |
*/ |
697 |
public static void waitScanFinished () throws InterruptedException { |
698 |
try { |
699 |
class T extends UserTask implements ClasspathInfoProvider { |
700 |
private final ClassPath EMPTY_PATH = ClassPathSupport.createClassPath(new URL[0]); |
701 |
private final ClasspathInfo cpinfo = ClasspathInfo.create(EMPTY_PATH, EMPTY_PATH, EMPTY_PATH); |
702 |
@Override |
703 |
public void run(ResultIterator resultIterator) throws Exception { |
704 |
// no-op |
705 |
} |
706 |
|
707 |
public ClasspathInfo getClasspathInfo() { |
708 |
return cpinfo; |
709 |
} |
710 |
} |
711 |
Future<Void> f = ParserManager.parseWhenScanFinished(JavacParser.MIME_TYPE, new T()); |
712 |
if (!f.isDone()) { |
713 |
f.get(); |
714 |
} |
715 |
} catch (Exception ex) { |
716 |
} |
717 |
} |
718 |
|
719 |
|
720 |
/** |
721 |
* Returns the dependent source path roots for given source root. |
722 |
* It returns all the open project source roots which have either |
723 |
* direct or transitive dependency on the given source root. |
724 |
* @param root to find the dependent roots for |
725 |
* @return {@link Set} of {@link URL}s containing at least the |
726 |
* incoming root, never returns null. |
727 |
* @since 0.10 |
728 |
*/ |
729 |
@org.netbeans.api.annotations.common.SuppressWarnings(value={"DMI_COLLECTION_OF_URLS"}/*,justification="URLs have never host part"*/) //NOI18N |
730 |
public static Set<URL> getDependentRoots (final URL root) { |
731 |
final Map<URL, List<URL>> deps = IndexingController.getDefault().getRootDependencies(); |
732 |
return getDependentRootsImpl (root, deps); |
733 |
} |
734 |
|
735 |
|
736 |
@org.netbeans.api.annotations.common.SuppressWarnings(value={"DMI_COLLECTION_OF_URLS"}/*,justification="URLs have never host part"*/) //NOI18N |
737 |
static Set<URL> getDependentRootsImpl (final URL root, final Map<URL, List<URL>> deps) { |
738 |
//Create inverse dependencies |
739 |
final Map<URL, List<URL>> inverseDeps = new HashMap<URL, List<URL>> (); |
740 |
for (Map.Entry<URL,List<URL>> entry : deps.entrySet()) { |
741 |
final URL u1 = entry.getKey(); |
742 |
final List<URL> l1 = entry.getValue(); |
743 |
for (URL u2 : l1) { |
744 |
List<URL> l2 = inverseDeps.get(u2); |
745 |
if (l2 == null) { |
746 |
l2 = new ArrayList<URL>(); |
747 |
inverseDeps.put (u2,l2); |
748 |
} |
749 |
l2.add (u1); |
750 |
} |
751 |
} |
752 |
//Collect dependencies |
753 |
final Set<URL> result = new HashSet<URL>(); |
754 |
final LinkedList<URL> todo = new LinkedList<URL> (); |
755 |
todo.add (root); |
756 |
while (!todo.isEmpty()) { |
757 |
final URL u = todo.removeFirst(); |
758 |
if (!result.contains(u)) { |
759 |
result.add (u); |
760 |
final List<URL> ideps = inverseDeps.get(u); |
761 |
if (ideps != null) { |
762 |
todo.addAll (ideps); |
763 |
} |
764 |
} |
765 |
} |
766 |
//Filter non opened projects |
767 |
Set<ClassPath> cps = GlobalPathRegistry.getDefault().getPaths(ClassPath.SOURCE); |
768 |
Set<URL> toRetain = new HashSet<URL>(); |
769 |
for (ClassPath cp : cps) { |
770 |
for (ClassPath.Entry e : cp.entries()) { |
771 |
toRetain.add(e.getURL()); |
772 |
} |
773 |
} |
774 |
result.retainAll(toRetain); |
775 |
return result; |
776 |
} |
777 |
|
778 |
//Helper methods |
779 |
|
780 |
/** |
781 |
* Returns classes declared in the given source file which have the main method. |
782 |
* @param fo source file |
783 |
* @return the classes containing main method |
784 |
* @throws IllegalArgumentException when file does not exist or is not a java source file. |
785 |
*/ |
786 |
public static Collection<ElementHandle<TypeElement>> getMainClasses (final FileObject fo) { |
787 |
if (fo == null || !fo.isValid() || fo.isVirtual()) { |
788 |
throw new IllegalArgumentException (); |
789 |
} |
790 |
final JavaSource js = JavaSource.forFileObject(fo); |
791 |
if (js == null) { |
792 |
throw new IllegalArgumentException (); |
793 |
} |
794 |
try { |
795 |
final List<ElementHandle<TypeElement>> result = new LinkedList<ElementHandle<TypeElement>>(); |
796 |
js.runUserActionTask(new Task<CompilationController>() { |
797 |
public void run(final CompilationController control) throws Exception { |
798 |
if (control.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED).compareTo (JavaSource.Phase.ELEMENTS_RESOLVED)>=0) { |
799 |
new TreePathScanner<Void,Void> () { |
800 |
public @Override Void visitMethod(MethodTree node, Void p) { |
801 |
ExecutableElement method = (ExecutableElement) control.getTrees().getElement(getCurrentPath()); |
802 |
if (method != null && SourceUtils.isMainMethod(method) && isAccessible(method.getEnclosingElement())) { |
803 |
result.add (ElementHandle.create((TypeElement)method.getEnclosingElement())); |
804 |
} |
805 |
return null; |
806 |
} |
807 |
}.scan(control.getCompilationUnit(), null); |
808 |
} |
809 |
} |
810 |
|
811 |
private boolean isAccessible (Element element) { |
812 |
ElementKind kind = element.getKind(); |
813 |
while (kind != ElementKind.PACKAGE) { |
814 |
if (!kind.isClass() && !kind.isInterface()) { |
815 |
return false; |
816 |
} |
817 |
Set<Modifier> modifiers = ((TypeElement)element).getModifiers(); |
818 |
Element parent = element.getEnclosingElement(); |
819 |
if (parent.getKind() != ElementKind.PACKAGE && !modifiers.contains(Modifier.STATIC)) { |
820 |
return false; |
821 |
} |
822 |
element = parent; |
823 |
kind = element.getKind(); |
824 |
} |
825 |
return true; |
826 |
} |
827 |
|
828 |
}, true); |
829 |
return result; |
830 |
} catch (IOException ioe) { |
831 |
Exceptions.printStackTrace(ioe); |
832 |
return Collections.<ElementHandle<TypeElement>>emptySet(); |
833 |
} |
834 |
} |
835 |
|
836 |
/** |
837 |
* Returns true when the class contains main method. |
838 |
* @param qualifiedName the fully qualified name of class |
839 |
* @param cpInfo the classpath used to resolve the class |
840 |
* @return true when the class contains a main method |
841 |
*/ |
842 |
public static boolean isMainClass (final String qualifiedName, ClasspathInfo cpInfo) { |
843 |
if (qualifiedName == null || cpInfo == null) { |
844 |
throw new IllegalArgumentException (); |
845 |
} |
846 |
final boolean[] result = new boolean[]{false}; |
847 |
JavaSource js = JavaSource.create(cpInfo); |
848 |
try { |
849 |
js.runUserActionTask(new Task<CompilationController>() { |
850 |
|
851 |
public void run(CompilationController control) throws Exception { |
852 |
TypeElement type = ((JavacElements)control.getElements()).getTypeElementByBinaryName(qualifiedName); |
853 |
if (type == null) { |
854 |
return; |
855 |
} |
856 |
List<? extends ExecutableElement> methods = ElementFilter.methodsIn(type.getEnclosedElements()); |
857 |
for (ExecutableElement method : methods) { |
858 |
if (SourceUtils.isMainMethod(method)) { |
859 |
result[0] = true; |
860 |
break; |
861 |
} |
862 |
} |
863 |
} |
864 |
|
865 |
}, true); |
866 |
} catch (IOException ioe) { |
867 |
Exceptions.printStackTrace(ioe); |
868 |
} |
869 |
return result[0]; |
870 |
} |
871 |
|
872 |
/** |
873 |
* Returns true if the method is a main method |
874 |
* @param method to be checked |
875 |
* @return true when the method is a main method |
876 |
*/ |
877 |
public static boolean isMainMethod (final ExecutableElement method) { |
878 |
if (!"main".contentEquals(method.getSimpleName())) { //NOI18N |
879 |
return false; |
880 |
} |
881 |
long flags = ((Symbol.MethodSymbol)method).flags(); //faster |
882 |
if (((flags & Flags.PUBLIC) == 0) || ((flags & Flags.STATIC) == 0)) { |
883 |
return false; |
884 |
} |
885 |
if (method.getReturnType().getKind() != TypeKind.VOID) { |
886 |
return false; |
887 |
} |
888 |
List<? extends VariableElement> params = method.getParameters(); |
889 |
if (params.size() != 1) { |
890 |
return false; |
891 |
} |
892 |
TypeMirror param = params.get(0).asType(); |
893 |
if (param.getKind() != TypeKind.ARRAY) { |
894 |
return false; |
895 |
} |
896 |
ArrayType array = (ArrayType) param; |
897 |
TypeMirror compound = array.getComponentType(); |
898 |
if (compound.getKind() != TypeKind.DECLARED) { |
899 |
return false; |
900 |
} |
901 |
if (!"java.lang.String".contentEquals(((TypeElement)((DeclaredType)compound).asElement()).getQualifiedName())) { //NOI18N |
902 |
return false; |
903 |
} |
904 |
return true; |
905 |
} |
906 |
|
907 |
/** |
908 |
* Returns classes declared under the given source roots which have the main method. |
909 |
* @param sourceRoots the source roots |
910 |
* @return the classes containing the main methods |
911 |
* Currently this method is not optimized and may be slow |
912 |
*/ |
913 |
public static Collection<ElementHandle<TypeElement>> getMainClasses (final FileObject[] sourceRoots) { |
914 |
final List<ElementHandle<TypeElement>> result = new LinkedList<ElementHandle<TypeElement>> (); |
915 |
for (final FileObject root : sourceRoots) { |
916 |
try { |
917 |
final File rootFile = FileUtil.toFile(root); |
918 |
ClassPath bootPath = ClassPath.getClassPath(root, ClassPath.BOOT); |
919 |
ClassPath compilePath = ClassPath.getClassPath(root, ClassPath.COMPILE); |
920 |
ClassPath srcPath = ClassPathSupport.createClassPath(new FileObject[] {root}); |
921 |
ClasspathInfo cpInfo = ClasspathInfo.create(bootPath, compilePath, srcPath); |
922 |
JavaSource js = JavaSource.create(cpInfo); |
923 |
js.runUserActionTask(new Task<CompilationController>() { |
924 |
public void run(CompilationController control) throws Exception { |
925 |
final URL rootURL = root.getURL(); |
926 |
Iterable<? extends URL> mainClasses = ExecutableFilesIndex.DEFAULT.getMainClasses(rootURL); |
927 |
List<ElementHandle<TypeElement>> classes = new LinkedList<ElementHandle<TypeElement>>(); |
928 |
for (URL mainClass : mainClasses) { |
929 |
File mainFo = new File (URI.create(mainClass.toExternalForm())); |
930 |
if (mainFo.exists()) { |
931 |
classes.addAll(JavaCustomIndexer.getRelatedTypes(mainFo, rootFile)); |
932 |
} |
933 |
} |
934 |
for (ElementHandle<TypeElement> cls : classes) { |
935 |
TypeElement te = cls.resolve(control); |
936 |
if (te != null) { |
937 |
Iterable<? extends ExecutableElement> methods = ElementFilter.methodsIn(te.getEnclosedElements()); |
938 |
for (ExecutableElement method : methods) { |
939 |
if (isMainMethod(method)) { |
940 |
if (isIncluded(cls, control.getClasspathInfo())) { |
941 |
result.add (cls); |
942 |
} |
943 |
break; |
944 |
} |
945 |
} |
946 |
} |
947 |
} |
948 |
} |
949 |
}, false); |
950 |
} catch (IOException ioe) { |
951 |
Exceptions.printStackTrace(ioe); |
952 |
return Collections.<ElementHandle<TypeElement>>emptySet(); |
953 |
} |
954 |
} |
955 |
return result; |
956 |
} |
957 |
|
958 |
private static boolean isIncluded (final ElementHandle<TypeElement> element, final ClasspathInfo cpInfo) { |
959 |
FileObject fobj = getFile (element,cpInfo); |
960 |
if (fobj == null) { |
961 |
//Not source |
962 |
return true; |
963 |
} |
964 |
ClassPath sourcePath = cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE); |
965 |
for (ClassPath.Entry e : sourcePath.entries()) { |
966 |
FileObject root = e.getRoot (); |
967 |
if (root != null && FileUtil.isParentOf(root,fobj)) { |
968 |
return e.includes(fobj); |
969 |
} |
970 |
} |
971 |
return true; |
972 |
} |
973 |
|
974 |
private static boolean isCaseSensitive () { |
975 |
return ! new File ("a").equals (new File ("A")); //NOI18N |
976 |
} |
977 |
|
978 |
private static String getSourceFileName (String classFileName) { |
979 |
int index = classFileName.indexOf('$'); //NOI18N |
980 |
return index == -1 ? classFileName : classFileName.substring(0,index); |
981 |
} |
982 |
|
983 |
/** |
984 |
* @since 0.24 |
985 |
*/ |
986 |
public static WildcardType resolveCapturedType(TypeMirror type) { |
987 |
if (type instanceof Type.CapturedType) { |
988 |
return ((Type.CapturedType) type).wildcard; |
989 |
} else { |
990 |
return null; |
991 |
} |
992 |
} |
993 |
|
994 |
// --------------- Helper methods of getFile () ----------------------------- |
995 |
private static ClassPath createClassPath (ClasspathInfo cpInfo, PathKind kind) throws MalformedURLException { |
996 |
return ClasspathInfoAccessor.getINSTANCE().getCachedClassPath(cpInfo, kind); |
997 |
} |
998 |
|
999 |
// --------------- End of getFile () helper methods ------------------------------ |
1000 |
|
1001 |
private static final int MAX_LEN = 6; |
1002 |
/** |
1003 |
* Utility method for generating method parameter names based on incoming |
1004 |
* class name when source is unavailable. |
1005 |
* <p/> |
1006 |
* This method uses both subjective heuristics to follow common patterns |
1007 |
* for common JDK classes, acronym creation for bicapitalized names, and |
1008 |
* vowel and repeated character elision if that fails, to generate |
1009 |
* readable, programmer-friendly method names. |
1010 |
* |
1011 |
* @param typeName The fqn of the parameter class |
1012 |
* @param used A set of names that have already been used for parameters |
1013 |
* and should not be reused, to avoid creating uncompilable code |
1014 |
* @return A programmer-friendly parameter name (i.e. not arg0, arg1...) |
1015 |
*/ |
1016 |
static @NonNull String generateReadableParameterName (@NonNull String typeName, @NonNull Set<String> used) { |
1017 |
boolean arr = typeName.indexOf ("[") > 0 || typeName.endsWith("..."); //NOI18N |
1018 |
typeName = trimToSimpleName (typeName); |
1019 |
String result = typeName.toLowerCase(); |
1020 |
//First, do some common, sane substitutions that are common java parlance |
1021 |
if ( typeName.endsWith ( "Listener" ) ) { //NOI18N |
1022 |
result = Character.toLowerCase(typeName.charAt(0)) + "l"; //NOI18N |
1023 |
} else if ( "Object".equals (typeName)) { //NOI18N |
1024 |
result = "o"; //NOI18N |
1025 |
} else if ("Class".equals(typeName)) { //NOI18N |
1026 |
result = "type"; //NOI18N |
1027 |
} else if ( "InputStream".equals(typeName)) { //NOI18N |
1028 |
result = "in"; //NOI18N |
1029 |
} else if ( "OutputStream".equals(typeName)) { |
1030 |
result = "out"; //NOI18N |
1031 |
} else if ( "Runnable".equals(typeName)) { |
1032 |
result = "r"; //NOI18N |
1033 |
} else if ( "Lookup".equals(typeName)) { |
1034 |
result = "lkp"; //NOI18N |
1035 |
} else if ( typeName.endsWith ( "Stream" )) { //NOI18N |
1036 |
result = "stream"; //NOI18N |
1037 |
} else if ( typeName.endsWith ("Writer")) { //NOI18N |
1038 |
result = "writer"; //NOI18N |
1039 |
} else if ( typeName.endsWith ("Reader")) { //NOI18N |
1040 |
result = "reader"; //NOI18N |
1041 |
} else if ( typeName.endsWith ( "Panel" )) { //NOI18N |
1042 |
result = "pnl"; //NOI18N |
1043 |
} else if ( typeName.endsWith ( "Action" )) { //NOI18N |
1044 |
result = "action"; //NOI18N |
1045 |
} |
1046 |
//Now see if we've made a large and unwieldy variable - people |
1047 |
//usually prefer reasonably short but legible arguments |
1048 |
if ( result.length () > MAX_LEN ) { |
1049 |
//See if we can turn, say, NoClassDefFoundError into "ncdfe" |
1050 |
result = tryToMakeAcronym ( typeName ); |
1051 |
//No luck? We've probably got one long word like Component or Runnable |
1052 |
if (result.length() > MAX_LEN) { |
1053 |
//First, strip out vowels - people easily figure out words |
1054 |
//missing vowels - common in abbreviations and spam mails |
1055 |
result = elideVowelsAndRepetitions(result); |
1056 |
if (result.length() > MAX_LEN) { |
1057 |
//Still too long? Give up and give them a 1 character var name |
1058 |
result = new StringBuilder().append( |
1059 |
result.charAt(0)).toString().toLowerCase(); |
1060 |
} |
1061 |
} |
1062 |
} |
1063 |
//Make sure we haven't killed everything - if so, use a generic version |
1064 |
if ( result.trim ().length () == 0 ) { |
1065 |
result = "value"; //NOI18N |
1066 |
} |
1067 |
//If it's an array, pluralize it (english language style - but better than nothing) |
1068 |
if (arr) { |
1069 |
result += "s"; //NOI18N |
1070 |
} |
1071 |
//Now make sure it's legal; if not, make it a single letter |
1072 |
if ( isPrimitiveTypeName ( result ) || !Utilities.isJavaIdentifier ( result ) ) { |
1073 |
StringBuilder sb = new StringBuilder(); |
1074 |
sb.append (result.charAt(0)); |
1075 |
result = sb.toString(); |
1076 |
} |
1077 |
//Now make sure we're not duplicating a variable name we already used |
1078 |
String test = result; |
1079 |
int revs = 0; |
1080 |
while ( used.contains ( test ) ) { |
1081 |
revs++; |
1082 |
test = result + revs; |
1083 |
} |
1084 |
result = test; |
1085 |
used.add ( result ); |
1086 |
return result; |
1087 |
} |
1088 |
|
1089 |
/** |
1090 |
* Trims to the simple class name and removes and generics |
1091 |
* |
1092 |
* @param typeName The class name |
1093 |
* @return A simplified class name |
1094 |
*/ |
1095 |
private static String trimToSimpleName (String typeName) { |
1096 |
String result = typeName; |
1097 |
int ix = result.indexOf ("<"); //NOI18N |
1098 |
if (ix > 0 && ix != typeName.length() - 1) { |
1099 |
result = typeName.substring(0, ix); |
1100 |
} |
1101 |
if (result.endsWith ("...")) { //NOI18N |
1102 |
result = result.substring (0, result.length() - 3); |
1103 |
} |
1104 |
ix = result.lastIndexOf ("$"); //NOI18N |
1105 |
if (ix > 0 && ix != result.length() - 1) { |
1106 |
result = result.substring(ix + 1); |
1107 |
} else { |
1108 |
ix = result.lastIndexOf("."); //NOI18N |
1109 |
if (ix > 0 && ix != result.length() - 1) { |
1110 |
result = result.substring(ix + 1); |
1111 |
} |
1112 |
} |
1113 |
ix = result.indexOf ( "[" ); //NOI18N |
1114 |
if ( ix > 0 ) { |
1115 |
result = result.substring ( 0, ix ); |
1116 |
} |
1117 |
return result; |
1118 |
} |
1119 |
|
1120 |
/** |
1121 |
* Removes vowels and repeated letters. This is used to generate names |
1122 |
* where the class name a single long word - e.g. abbreviate |
1123 |
* Runnable to rnbl |
1124 |
* @param name The name |
1125 |
* @return A shortened version of it |
1126 |
*/ |
1127 |
private static String elideVowelsAndRepetitions (String name) { |
1128 |
char[] chars = name.toCharArray(); |
1129 |
StringBuilder sb = new StringBuilder(); |
1130 |
char last = 0; |
1131 |
char lastUsed = 0; |
1132 |
for (int i = 0; i < chars.length; i++) { |
1133 |
char c = chars[i]; |
1134 |
if (Character.isDigit(c)) { |
1135 |
continue; |
1136 |
} |
1137 |
if (i == 0 || Character.isUpperCase(c)) { |
1138 |
if (lastUsed != c) { |
1139 |
sb.append (c); |
1140 |
lastUsed = c; |
1141 |
} |
1142 |
} else if (c != last && !isVowel(c)) { |
1143 |
if (lastUsed != c) { |
1144 |
sb.append (c); |
1145 |
lastUsed = c; |
1146 |
} |
1147 |
} |
1148 |
last = c; |
1149 |
} |
1150 |
return sb.toString(); |
1151 |
} |
1152 |
|
1153 |
private static boolean isVowel(char c) { |
1154 |
return Arrays.binarySearch(VOWELS, c) >= 0; |
1155 |
} |
1156 |
|
1157 |
/** |
1158 |
* Vowels in various indo-european-based languages |
1159 |
*/ |
1160 |
private static char[] VOWELS = new char[] { |
1161 |
//IMPORTANT: This array is sorted. If you add to it, |
1162 |
//add in the correct place or Arrays.binarySearch will break on it |
1163 |
'\u0061', '\u0065', '\u0069', '\u006f', '\u0075', '\u0079', '\u00e9', '\u00ea', //NOI18N |
1164 |
'\u00e8', '\u00e1', '\u00e2', '\u00e6', '\u00e0', '\u03b1', '\u00e3', //NOI18N |
1165 |
'\u00e5', '\u00e4', '\u00eb', '\u00f3', '\u00f4', '\u0153', '\u00f2', //NOI18N |
1166 |
'\u03bf', '\u00f5', '\u00f6', '\u00ed', '\u00ee', '\u00ec', '\u03b9', //NOI18N |
1167 |
'\u00ef', '\u00fa', '\u00fb', '\u00f9', '\u03d2', '\u03c5', '\u00fc', //NOI18N |
1168 |
'\u0430', '\u043e', '\u044f', '\u0438', '\u0439', '\u0435', '\u044b', //NOI18N |
1169 |
'\u044d', '\u0443', '\u044e', }; |
1170 |
|
1171 |
//PENDING: The below would be much prettier; whether it survives |
1172 |
//cross-platform encoding issues in hg is another question; the hg diff generated |
1173 |
//was incorrect |
1174 |
/* |
1175 |
'a', 'e', 'i', 'o', 'u', 'y', 'à', 'á', //NOI18N |
1176 |
'â', 'ã', 'ä', 'å', 'æ', 'è', 'é', //NOI18N |
1177 |
'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ò', //NOI18N |
1178 |
'ó', 'ô', 'õ', 'ö', 'ù', 'ú', 'û', //NOI18N |
1179 |
'ü', 'œ', 'α', 'ι', 'ο', 'υ', 'ϒ', //NOI18N |
1180 |
'а', 'е', 'и', 'й', 'о', 'у', 'ы', //NOI18N |
1181 |
'э', 'ю', 'я'}; //NOI18N |
1182 |
*/ |
1183 |
/** |
1184 |
* Determine if a string matches a java primitive type. Used in generating reasonable variable names. |
1185 |
*/ |
1186 |
private static boolean isPrimitiveTypeName (String typeName) { |
1187 |
return ( |
1188 |
//Whoa, ascii art! |
1189 |
"void".equals ( typeName ) || //NOI18N |
1190 |
"int".equals ( typeName ) || //NOI18N |
1191 |
"long".equals ( typeName ) || //NOI18N |
1192 |
"float".equals ( typeName ) || //NOI18N |
1193 |
"double".equals ( typeName ) || //NOI18N |
1194 |
"short".equals ( typeName ) || //NOI18N |
1195 |
"char".equals ( typeName ) || //NOI18N |
1196 |
"boolean".equals ( typeName ) ); //NOI18N |
1197 |
} |
1198 |
|
1199 |
/** |
1200 |
* Try to create an acronym-style variable name from a string - i.e., |
1201 |
* "JavaDataObject" becomes "jdo". |
1202 |
*/ |
1203 |
private static String tryToMakeAcronym (String s) { |
1204 |
char[] c = s.toCharArray (); |
1205 |
StringBuilder sb = new StringBuilder (); |
1206 |
for ( int i = 0; i < c.length; i++ ) { |
1207 |
if ( Character.isUpperCase (c[i])) { |
1208 |
sb.append ( c[ i ] ); |
1209 |
} |
1210 |
} |
1211 |
if ( sb.length () > 1 ) { |
1212 |
return sb.toString ().toLowerCase (); |
1213 |
} else { |
1214 |
return s.toLowerCase(); |
1215 |
} |
1216 |
} |
1217 |
} |