forked from phoedos/pmd
Merge pull request #1580 from oowekyala/designer-fix-jar-exploration
[ui] Fix package exploration in jars
This commit is contained in:
@ -261,9 +261,6 @@ public class XPathPanelController extends AbstractController {
|
||||
designerRoot.getLogger().logEvent(new LogEntry(e, Category.XPATH_EVALUATION_EXCEPTION));
|
||||
}
|
||||
|
||||
xpathResultListView.refresh();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,21 +7,24 @@ package net.sourceforge.pmd.util.fxdesigner.util.autocomplete;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystemNotFoundException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
@ -39,8 +42,7 @@ class AstPackageExplorer implements NodeNameFinder {
|
||||
|
||||
AstPackageExplorer(Language language) {
|
||||
availableNodeNames =
|
||||
getClasses("net.sourceforge.pmd.lang."
|
||||
+ language.getTerseName() + ".ast")
|
||||
getClassesInPackage("net.sourceforge.pmd.lang." + language.getTerseName() + ".ast")
|
||||
.filter(clazz -> clazz.getSimpleName().startsWith("AST"))
|
||||
.filter(clazz -> !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()))
|
||||
.map(m -> m.getSimpleName().substring("AST".length()))
|
||||
@ -54,60 +56,103 @@ class AstPackageExplorer implements NodeNameFinder {
|
||||
return availableNodeNames;
|
||||
}
|
||||
|
||||
// TODO move to some global Util
|
||||
|
||||
|
||||
/** Finds the classes in the given package by looking in the classpath directories. */
|
||||
private static Stream<Class<?>> getClasses(String packageName) {
|
||||
|
||||
private static Stream<Class<?>> getClassesInPackage(String packageName) {
|
||||
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
assert classLoader != null;
|
||||
|
||||
|
||||
Enumeration<URL> resources;
|
||||
Stream<URL> resources;
|
||||
|
||||
try {
|
||||
String path = packageName.replace('.', '/');
|
||||
resources = classLoader.getResources(path);
|
||||
resources = enumerationAsStream(classLoader.getResources(path));
|
||||
} catch (IOException e) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
final List<Class<?>> result = new ArrayList<>();
|
||||
|
||||
while (resources.hasMoreElements()) {
|
||||
URL resource = resources.nextElement();
|
||||
return resources.flatMap(resource -> {
|
||||
try {
|
||||
Files.walkFileTree(new File(resource.getFile()).toPath(),
|
||||
EnumSet.noneOf(FileVisitOption.class),
|
||||
1,
|
||||
getClassFileVisitor(packageName, result));
|
||||
|
||||
} catch (IOException e) {
|
||||
// continue
|
||||
return getClasses(resource, packageName);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
return result.stream();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static FileVisitor<Path> getClassFileVisitor(String packageName, List<Class<?>> accumulator) {
|
||||
return new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) {
|
||||
final String extension = FilenameUtils.getExtension(file.toString());
|
||||
if ("class".equalsIgnoreCase(extension)) {
|
||||
try {
|
||||
accumulator.add(Class.forName(packageName + "." + FilenameUtils.getBaseName(file.getFileName().toString())));
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
/** Maps paths to classes. */
|
||||
private static Stream<Class<?>> getClasses(URL url, String packageName) throws IOException, URISyntaxException {
|
||||
return getPathsInDir(url)
|
||||
.stream()
|
||||
.filter(path -> "class".equalsIgnoreCase(FilenameUtils.getExtension(path.toString())))
|
||||
.<Class<?>>map(path -> {
|
||||
try {
|
||||
return Class.forName(packageName + "." + FilenameUtils.getBaseName(path.getFileName().toString()));
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
|
||||
private static List<Path> getPathsInDir(URL url) throws URISyntaxException, IOException {
|
||||
|
||||
URI uri = url.toURI();
|
||||
|
||||
if ("jar".equals(uri.getScheme())) {
|
||||
// we have to do this to look inside a jar
|
||||
try (FileSystem fs = getFileSystem(uri)) {
|
||||
// we have to cut out the path to the jar + '!'
|
||||
// to get a path that's relative to the root of the jar filesystem
|
||||
// This is equivalent to a packageName.replace('.', '/') but more reusable
|
||||
String schemeSpecific = uri.getSchemeSpecificPart();
|
||||
String fsRelativePath = schemeSpecific.substring(schemeSpecific.indexOf('!') + 1);
|
||||
return Files.walk(fs.getPath(fsRelativePath), 1)
|
||||
.collect(Collectors.toList()); // buffer everything, before closing the filesystem
|
||||
|
||||
}
|
||||
} else {
|
||||
try (Stream<Path> paths = Files.walk(new File(url.getFile()).toPath(), 1)) {
|
||||
return paths.collect(Collectors.toList()); // buffer everything, before closing the original stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static FileSystem getFileSystem(URI uri) throws IOException {
|
||||
|
||||
try {
|
||||
return FileSystems.getFileSystem(uri);
|
||||
} catch (FileSystemNotFoundException e) {
|
||||
return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO move to IteratorUtil
|
||||
private static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
|
||||
return StreamSupport.stream(
|
||||
Spliterators.spliteratorUnknownSize(
|
||||
new Iterator<T>() {
|
||||
@Override
|
||||
public T next() {
|
||||
return e.nextElement();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return e.hasMoreElements();
|
||||
}
|
||||
},
|
||||
Spliterator.ORDERED), false);
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,11 +5,8 @@
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.autocomplete;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -63,26 +60,32 @@ public final class XPathAutocompleteProvider {
|
||||
|
||||
public void initialiseAutoCompletion() {
|
||||
|
||||
Set<String> completionTriggers = new HashSet<>(Arrays.asList("\r", "\r\n", "\n", "\t"));
|
||||
|
||||
// allows tab/enter completion
|
||||
autoCompletePopup.addEventHandler(KeyEvent.KEY_TYPED, e -> {
|
||||
EventStreams.eventsOf(autoCompletePopup, KeyEvent.ANY)
|
||||
.filter(e -> !e.isConsumed())
|
||||
.filter(e ->
|
||||
// For some reason this has to be asymmetric
|
||||
// Delivered events vary between JREs, as well as their properties
|
||||
// This is the common denominator I found for JREs 8..10
|
||||
|
||||
// for some reason using KeyEvent.KEY_PRESSED didn't work with the ENTER key,
|
||||
// which is why we use KeyEvent.KEY_TYPED
|
||||
if (!e.isConsumed() && completionTriggers.contains(e.getCharacter())) {
|
||||
int focusIdx = getFocusIdx();
|
||||
if (focusIdx == -1) {
|
||||
focusIdx = 0;
|
||||
}
|
||||
// Only KEY_RELEASED events are delivered for ENTER
|
||||
e.getEventType().equals(KeyEvent.KEY_RELEASED) && e.getCode() == KeyCode.ENTER
|
||||
// All KEY_TYPED, KEY_PRESSED, and KEY_RELEASED are delivered for TAB,
|
||||
// but we have to handle it before it inserts a \t so we catch KEY_PRESSED
|
||||
|| e.getEventType().equals(KeyEvent.KEY_PRESSED) && e.getCode() == KeyCode.TAB
|
||||
|
||||
if (focusIdx < autoCompletePopup.getItems().size()) {
|
||||
autoCompletePopup.getItems().get(focusIdx).getOnAction().handle(new ActionEvent());
|
||||
}
|
||||
e.consume();
|
||||
}
|
||||
)
|
||||
.subscribe(e -> {
|
||||
int focusIdx = getFocusIdx();
|
||||
if (focusIdx == -1) {
|
||||
focusIdx = 0;
|
||||
}
|
||||
|
||||
});
|
||||
if (focusIdx < autoCompletePopup.getItems().size()) {
|
||||
autoCompletePopup.getItems().get(focusIdx).getOnAction().handle(new ActionEvent());
|
||||
}
|
||||
e.consume();
|
||||
});
|
||||
|
||||
EventStream<Integer> changesEventStream = myCodeArea.plainTextChanges()
|
||||
.map(characterChanges -> {
|
||||
|
Reference in New Issue
Block a user