From e86a0ac82ce0f867b8e93cc28f32f9e73003b876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 14 Jan 2019 02:18:13 +0100 Subject: [PATCH 1/3] Fix package exploration in jars --- .../util/fxdesigner/XPathPanelController.java | 3 - .../util/autocomplete/AstPackageExplorer.java | 129 ++++++++++++------ 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java index d91551f0e7..c323501893 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java @@ -236,9 +236,6 @@ public class XPathPanelController implements Initializable, SettingsOwner { designerRoot.getLogger().logEvent(new LogEntry(e, Category.XPATH_EVALUATION_EXCEPTION)); } - xpathResultListView.refresh(); - - } diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/AstPackageExplorer.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/AstPackageExplorer.java index 0a85c4ad70..dadb9cbc61 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/AstPackageExplorer.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/AstPackageExplorer.java @@ -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> getClasses(String packageName) { - + private static Stream> getClassesInPackage(String packageName) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); assert classLoader != null; - - Enumeration resources; + Stream resources; try { String path = packageName.replace('.', '/'); - resources = classLoader.getResources(path); + resources = enumerationAsStream(classLoader.getResources(path)); } catch (IOException e) { return Stream.empty(); } - final List> 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 getClassFileVisitor(String packageName, List> accumulator) { - return new SimpleFileVisitor() { - @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> getClasses(URL url, String packageName) throws IOException, URISyntaxException { + return getPathsInDir(url) + .stream() + .filter(path -> "class".equalsIgnoreCase(FilenameUtils.getExtension(path.toString()))) + .>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 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 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.emptyMap()); + } + } + + + // TODO move to IteratorUtil + private static Stream enumerationAsStream(Enumeration e) { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize( + new Iterator() { + @Override + public T next() { + return e.nextElement(); + } + + + @Override + public boolean hasNext() { + return e.hasMoreElements(); + } + }, + Spliterator.ORDERED), false); } From 21d0956b4c5004c7908d1c51f20d24f79aa15b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 14 Jan 2019 09:38:46 +0100 Subject: [PATCH 2/3] Add todo --- .../fxdesigner/util/autocomplete/XPathAutocompleteProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/XPathAutocompleteProvider.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/XPathAutocompleteProvider.java index 4f9cfcba18..16eddc4052 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/XPathAutocompleteProvider.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/XPathAutocompleteProvider.java @@ -66,6 +66,7 @@ public final class XPathAutocompleteProvider { Set completionTriggers = new HashSet<>(Arrays.asList("\r", "\r\n", "\n", "\t")); // allows tab/enter completion + // FIXME doesn't work on java 9 autoCompletePopup.addEventHandler(KeyEvent.KEY_TYPED, e -> { // for some reason using KeyEvent.KEY_PRESSED didn't work with the ENTER key, From ab5b20f014fd29e132f46c97371601f26c409db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 14 Jan 2019 11:50:29 +0100 Subject: [PATCH 3/3] Also fix tab/enter completion not working on JRE > 1.8 --- .../XPathAutocompleteProvider.java | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/XPathAutocompleteProvider.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/XPathAutocompleteProvider.java index 16eddc4052..57c7cd7f89 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/XPathAutocompleteProvider.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/autocomplete/XPathAutocompleteProvider.java @@ -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,27 +60,32 @@ public final class XPathAutocompleteProvider { public void initialiseAutoCompletion() { - Set completionTriggers = new HashSet<>(Arrays.asList("\r", "\r\n", "\n", "\t")); - // allows tab/enter completion - // FIXME doesn't work on java 9 - 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 changesEventStream = myCodeArea.plainTextChanges() .map(characterChanges -> {