-
Notifications
You must be signed in to change notification settings - Fork 432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
import groovysh Completion works for all jar files on CLASSPATH #153
Conversation
Hello Thibault, |
Note that while the delay on my PC is negligeable, it might considerably slow down startup on slower machines. There should be some room for improvements for matching strings, and else maybe a command line option could disable the class searching. the same goes for memory consumption, as groovysh would hold a lot of classnames in memory. For my purposes of learning Groovy, having great completion outweights the disadvantages |
hi! thanks for the contribution, you're very welcome :-) I had a look at the code and the following enhancements came to my mind:
class ReflectionCompletorTests extends GroovyTestCase {
Groovysh shell
ReflectionCompletor reflectionCompletor
@Override protected void setUp() throws Exception {
shell = new Groovysh()
reflectionCompletor = new ReflectionCompletor(shell, ClassNameCompletor.classNames)
}
void testPreimportedClassNamesContainGroovyPreimportPackages() {
assert 'String' in reflectionCompletor.preimportedClassNames
assert 'FileInputStream' in reflectionCompletor.preimportedClassNames
assert 'BigDecimal' in reflectionCompletor.preimportedClassNames
assert 'BigInteger' in reflectionCompletor.preimportedClassNames
assert 'ArrayList' in reflectionCompletor.preimportedClassNames
assert 'InetAddress' in reflectionCompletor.preimportedClassNames
assert 'Closure' in reflectionCompletor.preimportedClassNames
assert 'AntBuilder' in reflectionCompletor.preimportedClassNames
}
void testInvalidImportMustNotThrowCompilationException() {
assert -1 == reflectionCompletor.complete("import java.", 11, [])
}
void testFindMatchingVariables() {
shell.interp.context.variables << [test: 42]
def candidates = []
reflectionCompletor.complete("tes", 2, candidates)
assert candidates == ['test']
}
} Of course, you only have to write tests for the new/changed functionality.
This (or something) like that would be the completer code to get all package from the current GroovyClassLoader: class ImportPackageCompletor extends SimpleCompletor {
ImportPackageCompletor(GroovyClassLoader groovyClassLoader) throws IOException {
this(groovyClassLoader, null)
}
ImportPackageCompletor(GroovyClassLoader groovyClassLoader, SimpleCompletorFilter filter)
throws IOException {
super(getPackageNames(groovyClassLoader), filter)
setDelimiter(".")
}
static String[] getPackageNames(GroovyClassLoader groovyClassLoader) throws IOException {
def urls = new HashSet<URL>()
for (ClassLoader loader = groovyClassLoader; loader != null; loader = loader.parent) {
if (!(loader instanceof URLClassLoader)) {
continue
}
urls.addAll(loader.URLs)
}
def systemClasses = [String.class, javax.swing.JFrame.class] as Class[]
systemClasses.each { Class systemClass ->
def classURL = systemClass.getResource("/${systemClass.name.replace('.', '/')}.class")
if (classURL != null) {
def uc = classURL.openConnection()
if (uc instanceof JarURLConnection) {
urls.add(((JarURLConnection) uc).getJarFileURL())
}
}
}
return new URLClassLoader(urls as URL[]).packages*.name
}
} and the test case: class ImportPackageCompletorTests extends GroovyTestCase {
void testGetAllPackages() {
final classLoader = new GroovyShell().classLoader
def packageNames = ImportPackageCompletor.getPackageNames(classLoader) as List
assert 'groovy.lang' in packageNames
assert 'groovy.util' in packageNames
assert 'java.util' in packageNames
assert 'java.math' in packageNames
assert 'javax.accessibility' in packageNames
}
} ImportPackageCompletor would have to be extended to complete to class names within an already specified package or to .*. I hope that wasn't too much information, if you need help or if i should implement certain parts just reply here or give me a ping on twitter/adn @asteingr. |
Thanks for the feedback. I am already writing unit tests, and will try to do integrate your comments. One problem I could not easily solve was to provide valid root packages for import. So when you type: And for that, I'd need to load all classes I guess, to infer the root packages. But I see the problem with memory consumption, so I'll make the default without this completion, and maybe a preference that can eagerly load packages. I will ping you once I have a better state. |
Hm, I notice that java.math. is not part of: Seems wrong to me. |
It's not the entire java.math package that is included. it's just java.math.bigdecimal and java.math.biginteger - see the comment above this line in ResolveVisitor: // note: BigInteger and BigDecimal are also imported by default
public static final String[] DEFAULT_IMPORTS = {"java.lang.", "java.io.", "java.net.", "java.util.", "groovy.lang.", "groovy.util."}; |
Ad root package: it's just a first thought, but why isn't it sufficient to load just the packages on the class path (like the example above shows). One why to structure the package names would be in a tree structure. So when "import " is typed, all names of the first level (after root) can be suggested. The choices will decrease along the way down to a concrete leave. |
Re root packages: To determine which root packages are available, i still have to go through all jars in the classpath. Sure I can do so and just store root packages, but then the next time the user presses tab, I would have to go through the jars again. Maybe I can store a map from the first 3 levels of packages to jarfile, so that the number of jars to go through are less... |
This line loads all available packages from the given URLs as shown in the code sample above: return new URLClassLoader(urls as URL[]).packages*.name True, we need to find out about all packages, but we'll do that only once. The thing is, the number of packages (~180 in my tests) and the memory consumption for this information is much smaller than keeping all class names (~48.000 in my tests) around. |
Hi, so I tried doing most of what you said, or at least what I understood of it. I did not splitup ReflectionCompletor because of all the other stuff that happens in there in my other pull request. For maintenance and testing, things in there can still well be moved into smaller and static methods. I copied the code from above and from JLine ClassCompletor into class PackageHelper to be reused by multiple Completors. So I stroe in memory only package names in their base form, and look up classes on demand looking only into the respective jars. Not sure whether all the logic I used there was correct. Unit tests are not complete, and i want to add an Preference option to disable all classlookup. But maybe you can have a look still. |
Thanks! I will have a look at your changes when I am back from a client. |
In particular look at how you like the lazy loading. To be honest, it On Mon, Apr 8, 2013 at 8:17 AM, Andre Steingress
|
keep as instance var so that other completors may benefit from search result
Example: groovy:000> Sys<tab> Before: no completion Afer: completes to System (from java.lang.System) Also: groovy:000> import java.awt.* groovy:000> Sha<tab> now completes to Shape
Wildcard imported classname completion works
My Latest and greatest is at my branch: https://github.com/tkruse/groovy-core/tree/fix_and_imports, which is on top of pull request #150. Many more unit tests added now. But it is too bothersome for me to backport that branch to master, I really wish someone just picket up #150 (or most of of it anyway). So anyway, if you review code, rather look at the fix_and_imports branch. i could do a pull request for it as well, but on github it will show as 60 commits or so because tit includes #150 |
closing this until #150 is resolved, will rebase then. |
@andresteingress:
This should give me all packages inside log4j, but instead it gives me the System packages. I checked the URL was right. In Classloader.java it reads:
This however works:
So findClass() is properly implemented, but URLClassLoader.packages never contains the packages of the URL. Do you have another idea of how to list just the packages? |
merge with caution, I am new to Groovy, and Classloading
I don't know how well this will generally work, but I think it will be a huge Improvement over the status quo