Skip to content

Commit

Permalink
improve the responsiveness of the class name completion UI #383
Browse files Browse the repository at this point in the history
  • Loading branch information
jstrachan committed Jul 15, 2013
1 parent afecbd4 commit f7027aa
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 14 deletions.
@@ -1,5 +1,6 @@
package io.hawt.introspect;

import io.hawt.introspect.support.ClassScanner;
import io.hawt.util.MBeanSupport;
import io.hawt.util.Strings;
import org.slf4j.Logger;
Expand All @@ -18,8 +19,6 @@ public class Introspector extends MBeanSupport implements IntrospectorMXBean {
private static final transient Logger LOG = LoggerFactory.getLogger(Introspector.class);
private static Introspector singleton;

private String configDir;
private String version;
private ClassScanner classScanner = new ClassScanner();

public static Introspector getSingleton() {
Expand All @@ -33,6 +32,8 @@ public static Introspector getSingleton() {
@Override
public void init() throws Exception {
Introspector.singleton = this;
// lets force a preload of the class name cache
findClassNames("", null);
super.init();

}
Expand Down
@@ -0,0 +1,35 @@
/**
* Copyright (C) 2013 the original author or authors.
* See the notice.md file distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.hawt.introspect.support;

import java.util.SortedSet;
import java.util.TreeSet;

/**
*/
public class CacheValue {
private SortedSet<String> classNames = new TreeSet<String>();

public SortedSet<String> getClassNames() {
return classNames;
}

public void setClassNames(SortedSet<String> classNames) {
this.classNames = classNames;
}
}
Expand Up @@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.hawt.introspect;
package io.hawt.introspect.support;

import java.net.URL;

Expand Down
Expand Up @@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.hawt.introspect;
package io.hawt.introspect.support;

import io.hawt.util.Strings;
import org.slf4j.Logger;
Expand All @@ -36,6 +36,7 @@
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

Expand All @@ -48,6 +49,8 @@ public class ClassScanner {

private final ClassLoader[] classLoaders;

private WeakHashMap<String,CacheValue> cache = new WeakHashMap<String, CacheValue>();
private WeakHashMap<Package,CacheValue> packageCache = new WeakHashMap<Package, CacheValue>();

public static ClassScanner newInstance() {
return new ClassScanner(Thread.currentThread().getContextClassLoader(), ClassScanner.class.getClassLoader());
Expand All @@ -72,20 +75,45 @@ public SortedSet<String> findClassNames(String search, Integer limit) {
*/
public SortedSet<String> findClassNamesInPackages(String search, Integer limit, Package... packages) {
SortedSet<String> answer = new TreeSet<String>();
Set<Class<?>> classes = new HashSet<Class<?>>();
Map<String, ClassResource> urlSet = new HashMap<String, ClassResource>();
SortedSet<String> classes = new TreeSet<String>();

for (Package aPackage : packages) {
addPackageResources(aPackage, urlSet);
CacheValue cacheValue = packageCache.get(aPackage);
if (cacheValue == null) {
cacheValue = createPackageCacheValue(aPackage);
packageCache.put(aPackage, cacheValue);
}
classes.addAll(cacheValue.getClassNames());
}
for (ClassResource classResource : urlSet.values()) {
addClassesForPackage(classResource, search, limit, classes);

/*
for (Map.Entry<String, ClassResource> entry : entries) {
String key = entry.getKey();
ClassResource classResource = entry.getValue();
CacheValue cacheValue = cache.get(key);
if (cacheValue == null) {
cacheValue = createCacheValue(key, classResource);
cache.put(key, cacheValue);
}
classes.addAll(cacheValue.getClassNames());
//addClassesForPackage(classResource, search, limit, classes);
}
for (Class<?> aClass : classes) {
answer.add(aClass.getName());
*/

if (withinLimit(limit, answer)) {
for (String aClass : classes) {
if (classNameMatches(aClass, search)) {
answer.add(aClass);
if (!withinLimit(limit, answer)) {
break;
}
}
}
}
return answer;
}


/**
* Returns all the classes found in a sorted map
*/
Expand Down Expand Up @@ -148,6 +176,84 @@ protected void addPackageResources(Package aPackage, Map<String, ClassResource>
}
}


private CacheValue createPackageCacheValue(Package aPackage) {
Map<String, ClassResource> urlSet = new HashMap<String, ClassResource>();
addPackageResources(aPackage, urlSet);

CacheValue answer = new CacheValue();
SortedSet<String> classNames = answer.getClassNames();
Set<Map.Entry<String, ClassResource>> entries = urlSet.entrySet();
for (Map.Entry<String, ClassResource> entry : entries) {
String key = entry.getKey();
ClassResource classResource = entry.getValue();
CacheValue cacheValue = cache.get(key);
if (cacheValue == null) {
cacheValue = createCacheValue(key, classResource);
cache.put(key, cacheValue);
}
classNames.addAll(cacheValue.getClassNames());
}
return answer;
}

protected CacheValue createCacheValue(String key, ClassResource classResource) {
CacheValue answer = new CacheValue();
SortedSet<String> classNames = answer.getClassNames();
String packageName = classResource.getPackageName();
URL resource = classResource.getResource();
if (resource != null) {
String resourceText = resource.toString();
LOG.debug("Searching resource " + resource);
if (resourceText.startsWith("jar:")) {
processJarClassNames(classResource, classNames);
} else {
processDirectoryClassNames(new File(resource.getPath()), packageName, classNames);
}
}
return answer;
}

protected void processDirectoryClassNames(File directory, String packageName, Set<String> classes) {
String[] fileNames = directory.list();
for (String fileName : fileNames) {
String packagePrefix = Strings.isNotBlank(packageName) ? packageName + '.' : packageName;
if (fileName.endsWith(".class")) {
String className = packagePrefix + fileName.substring(0, fileName.length() - 6);
classes.add(className);
}
File subdir = new File(directory, fileName);
if (subdir.isDirectory()) {
processDirectoryClassNames(subdir, packagePrefix + fileName, classes);
}
}
}

protected void processJarClassNames(ClassResource classResource, Set<String> classes) {
URL resource = classResource.getResource();
String packageName = classResource.getPackageName();
String relativePath = getPackageRelativePath(packageName);
String jarPath = getJarPath(resource);
JarFile jarFile;
try {
jarFile = new JarFile(jarPath);
} catch (IOException e) {
LOG.debug("IOException reading JAR '" + jarPath + ". Reason: " + e, e);
return;
}
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.endsWith(".class") && entryName.startsWith(relativePath) && entryName.length() > (relativePath.length() + 1)) {
String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", "");
classes.add(className);
}
}
}



protected void addClassesForPackage(ClassResource classResource, String filter, Integer limit, Set<Class<?>> classes) {
String packageName = classResource.getPackageName();
URL resource = classResource.getResource();
Expand All @@ -161,6 +267,7 @@ protected void addClassesForPackage(ClassResource classResource, String filter,
}
}
}

protected void processDirectory(File directory, String packageName, Set<Class<?>> classes, String filter, Integer limit) {
String[] fileNames = directory.list();
for (String fileName : fileNames) {
Expand Down Expand Up @@ -268,14 +375,14 @@ protected String getPackageRelativePath(String packageName) {
}

/**
* Returns true if we are within the limit value for the number of found classes
* Returns true if we are within the limit value for the number of results in the collection
*/
protected boolean withinLimit(Integer limit, Collection<Class<?>> classes) {
protected boolean withinLimit(Integer limit, Collection<?> collection) {
if (limit == null) {
return true;
} else {
int value = limit.intValue();
return value <= 0 || value > classes.size();
return value <= 0 || value > collection.size();
}
}
}
2 changes: 2 additions & 0 deletions hawtio-core/src/test/java/io/hawt/introspect/Main.java
Expand Up @@ -17,6 +17,8 @@
*/
package io.hawt.introspect;

import io.hawt.introspect.support.ClassScanner;

import java.util.SortedMap;

/**
Expand Down

0 comments on commit f7027aa

Please sign in to comment.