You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by ma...@apache.org on 2018/05/16 21:27:38 UTC

[incubator-netbeans] branch master updated: Maven Indexing Optimizations (#537)

This is an automated email from the ASF dual-hosted git repository.

matthiasblaesing pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 434b89a  Maven Indexing Optimizations (#537)
434b89a is described below

commit 434b89a2ad029abf11cd445b20c2e089f0b3626a
Author: Tim Boudreau <ni...@gmail.com>
AuthorDate: Wed May 16 17:27:34 2018 -0400

    Maven Indexing Optimizations (#537)
    
    * Maven indexer optimizations
    
    * Remove println from test
    
    * Actually set the ThreadLocal
    
    * Avoid array creation
    
    * Use new Apache license header, not old Oracle one
    
    * Fix other license header - was unsaved on last commit
    
    * More nuanced JDK package detection; limits path element count to 3, to balance performance requirements against absolute accuracy, since com/sun or java/ or javax/ packages are a micro-probability corner case within the Maven universe - you don't get the JDK from a Maven repository, and this is highly performance-sensitive code
    
    * Remove comment
    
    * Ensure stream is fully read
    
    * Integrating Matthias Bläsing's patch for MatchWords
    
    * Add description to FastScanner; .sha1 files not needed to index; use final fields; fix line comment typo
    
    * Remove now unneeded warning suppression
---
 .../maven/indexer/ClassDependencyIndexCreator.java | 182 +++++++++++++++------
 .../modules/maven/indexer/FastScanner.java         | 137 ++++++++++++++++
 .../netbeans/modules/maven/indexer/MatchWords.java | 111 +++++++++++++
 .../maven/indexer/NexusRepositoryIndexerImpl.java  |   3 +-
 .../indexer/ClassDependencyIndexCreatorTest.java   |  12 +-
 .../modules/maven/indexer/MatchWordsTest.java      |  42 +++++
 6 files changed, 436 insertions(+), 51 deletions(-)

diff --git a/maven.indexer/src/org/netbeans/modules/maven/indexer/ClassDependencyIndexCreator.java b/maven.indexer/src/org/netbeans/modules/maven/indexer/ClassDependencyIndexCreator.java
index 931df34..aef7b28 100644
--- a/maven.indexer/src/org/netbeans/modules/maven/indexer/ClassDependencyIndexCreator.java
+++ b/maven.indexer/src/org/netbeans/modules/maven/indexer/ClassDependencyIndexCreator.java
@@ -20,9 +20,6 @@
 package org.netbeans.modules.maven.indexer;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInput;
-import java.io.DataInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -33,11 +30,12 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.function.Predicate;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.logging.Level;
@@ -66,7 +64,6 @@ import org.netbeans.modules.classfile.ClassFile;
 import org.netbeans.modules.classfile.ClassName;
 import org.netbeans.modules.maven.indexer.api.NBVersionInfo;
 import org.netbeans.modules.maven.indexer.api.RepositoryQueries.ClassUsage;
-import org.openide.filesystems.FileUtil;
 
 /**
  * Scans classes in (local) JARs for their Java dependencies.
@@ -86,7 +83,8 @@ class ClassDependencyIndexCreator extends AbstractIndexCreator {
     /** class/in/this/Jar -> [foreign/Class, other/foreign/Nested$Class] */
     private Map<String,Set<String>> classDeps;
 
-    @Override public void populateArtifactInfo(ArtifactContext context) throws IOException {
+    @Override
+    public void populateArtifactInfo(ArtifactContext context) throws IOException {
         classDeps = null;
         ArtifactInfo ai = context.getArtifactInfo();
         if (ai.getClassifier() != null) {
@@ -100,18 +98,24 @@ class ClassDependencyIndexCreator extends AbstractIndexCreator {
             LOG.log(Level.FINER, "no artifact for {0}", ai); // not a big deal, maybe just *.pom (or *.pom + *.nbm) here
             return;
         }
+        if (jar.length() == 0) {
+            LOG.log(Level.FINER, "zero length jar for {0}", ai); // Don't try to index zero length files
+            return;
+        }
         String packaging = ai.getPackaging();
         if (packaging == null || (!packaging.equals("jar") && !isArchiveFile(jar))) {
             LOG.log(Level.FINE, "skipping artifact {0} with unrecognized packaging based on {1}", new Object[] {ai, jar});
             return;
         }
         LOG.log(Level.FINER, "reading {0}", jar);
-        Map<String, byte[]> classfiles = read(jar);
-        classDeps = new HashMap<String, Set<String>>();
-        Set<String> classes = classfiles.keySet();
-        for (Map.Entry<String, byte[]> entry : classfiles.entrySet()) {
-            addDependenciesToMap(entry.getKey(), entry.getValue(), classDeps, classes, jar);
-        }
+        classDeps = new HashMap<>();
+        read(jar, (String name, InputStream stream, Set<String> classes) -> {
+            try {
+                addDependenciesToMap(name, stream, classDeps, classes, jar);
+            } catch (IOException ex) {
+                LOG.log(Level.INFO, "Exception indexing " + jar, ex);
+            }
+        });
     }
 
     // adapted from FileUtil, since we do not want to have to use FileObject's here
@@ -213,6 +217,50 @@ class ClassDependencyIndexCreator extends AbstractIndexCreator {
         return referrers;
     }
 
+    static final Predicate<String> JDK_CLASS_TEST = new MatchWords(new String[]{
+        "apple/applescript", "apple/laf", "apple/launcher", "apple/security",
+        "com/apple/concurrent", "com/apple/eawt", "com/apple/eio", "com/apple/laf", "com/oracle/net",
+        "com/oracle/nio", "com/oracle/util", "com/oracle/webservices", "com/oracle/xmlns",
+        "com/sun/accessibility", "com/sun/activation", "com/sun/awt", "com/sun/beans", "com/sun/corba",
+        "com/sun/demo", "com/sun/image", "com/sun/imageio", "com/sun/istack", "com/sun/java",
+        "com/sun/java_cup", "com/sun/jmx", "com/sun/jndi", "com/sun/management", "com/sun/media",
+        "com/sun/naming", "com/sun/net", "com/sun/nio", "com/sun/org", "com/sun/rmi", "com/sun/rowset",
+        "com/sun/security", "com/sun/swing", "com/sun/tracing", "com/sun/xml", "java/applet", "java/awt",
+        "java/awt/color", "java/awt/datatransfer", "java/awt/dnd", "java/awt/event", "java/awt/font",
+        "java/awt/geom", "java/awt/im", "java/awt/image", "java/awt/peer", "java/awt/print",
+        "java/beans", "java/beans/beancontext", "java/io", "java/lang", "java/lang/annotation",
+        "java/lang/instrument", "java/lang/invoke", "java/lang/management", "java/lang/ref",
+        "java/lang/reflect", "java/math", "java/net", "java/nio", "java/nio/channels", "java/nio/charset",
+        "java/nio/file", "java/rmi", "java/rmi/activation", "java/rmi/dgc", "java/rmi/registry",
+        "java/rmi/server", "java/security", "java/security/acl", "java/security/cert",
+        "java/security/interfaces", "java/security/spec", "java/sql", "java/text", "java/text/spi", "java/time",
+        "java/time/chrono", "java/time/format", "java/time/temporal", "java/time/zone", "java/util",
+        "java/util/concurrent", "java/util/function", "java/util/jar", "java/util/logging",
+        "java/util/prefs", "java/util/regex", "java/util/spi", "java/util/stream", "java/util/zip",
+        "javax/accessibility", "javax/activation", "javax/activity", "javax/annotation",
+        "javax/annotation/processing", "javax/imageio", "javax/imageio/event", "javax/imageio/metadata",
+        "javax/imageio/plugins", "javax/imageio/spi", "javax/imageio/stream", "javax/jws", "javax/jws/soap",
+        "javax/lang/model", "javax/management", "javax/management/loading",
+        "javax/management/modelmbean", "javax/management/monitor", "javax/management/openmbean",
+        "javax/management/relation", "javax/management/remote", "javax/management/timer", "javax/naming",
+        "javax/naming/directory", "javax/naming/event", "javax/naming/ldap", "javax/naming/spi", "javax/net",
+        "javax/net/ssl", "javax/print", "javax/print/attribute", "javax/print/event", "javax/rmi",
+        "javax/rmi/CORBA", "javax/rmi/ssl", "javax/script", "javax/security/auth",
+        "javax/security/cert", "javax/security/sasl", "javax/smartcardio", "javax/sound/midi",
+        "javax/sound/sampled", "javax/sql", "javax/sql/rowset", "javax/swing", "javax/swing/border",
+        "javax/swing/colorchooser", "javax/swing/event", "javax/swing/filechooser", "javax/swing/plaf",
+        "javax/swing/table", "javax/swing/text", "javax/swing/tree", "javax/swing/undo", "javax/tools",
+        "javax/transaction", "javax/transaction/xa", "javax/xml", "javax/xml/bind", "javax/xml/crypto",
+        "javax/xml/datatype", "javax/xml/namespace", "javax/xml/parsers", "javax/xml/soap",
+        "javax/xml/stream", "javax/xml/transform", "javax/xml/validation", "javax/xml/ws",
+        "javax/xml/xpath", "jdk/internal/cmm", "jdk/internal/instrumentation", "jdk/internal/org",
+        "jdk/internal/util", "jdk/management/cmm", "jdk/management/resource", "jdk/net",
+        "jdk/xml/internal", "org/ietf/jgss", "org/jcp/xml", "org/omg/CORBA", "org/omg/CORBA_2_3",
+        "org/omg/CosNaming", "org/omg/Dynamic", "org/omg/DynamicAny", "org/omg/IOP", "org/omg/Messaging",
+        "org/omg/PortableInterceptor", "org/omg/PortableServer", "org/omg/SendingContext", "org/omg/stub",
+        "org/w3c/dom", "org/xml/sax"
+    });
+
     /**
      * @param referrer a referring class, as {@code pkg/Outer$Inner}
      * @param data its bytecode
@@ -220,32 +268,60 @@ class ClassDependencyIndexCreator extends AbstractIndexCreator {
      * @param siblings other referring classes in the same artifact (including this one), as {@code pkg/Outer$Inner}
      * @param jar the jar file, for diagnostics
      */
-    private static void addDependenciesToMap(String referrer, byte[] data, Map<String, Set<String>> depsMap, Set<String> siblings, File jar) throws IOException {
-        ClassLoader jre = ClassLoader.getSystemClassLoader().getParent();
+    private static void addDependenciesToMap(String referrer, InputStream data, Map<String, Set<String>> depsMap, Set<String> siblings, File jar) throws IOException {
         int shell = referrer.indexOf('$', referrer.lastIndexOf('/') + 1);
         String referrerTopLevel = shell == -1 ? referrer : referrer.substring(0, shell);
-        for (String referee : dependencies(data, referrer, jar)) {
+        for (String referee : dependencies(data, jar)) {
+            if (referrer.equals(referee)) {
+                continue;
+            }
             if (siblings.contains(referee)) {
                 continue; // in same JAR, not interesting
             }
-            try {
-                jre.loadClass(referee.replace('/', '.')); // XXX ought to cache this result
-                continue; // in JRE, not interesting
-            } catch (ClassNotFoundException x) {
+            if (JDK_CLASS_TEST.test(referee)) {
+                continue;
             }
             Set<String> referees = depsMap.get(referrerTopLevel);
             if (referees == null) {
-                referees = new TreeSet<String>();
+                referees = new HashSet<>();
                 depsMap.put(referrerTopLevel, referees);
             }
             referees.add(referee);
         }
     }
 
-    static Map<String,byte[]> read(File jar) throws IOException {
-        JarFile jf = new JarFile(jar, false);
-        try {
-            Map<String, byte[]> classfiles = new TreeMap<String, byte[]>();
+    @FunctionalInterface
+    interface JarClassEntryConsumer {
+
+        void accept(String name, InputStream classData, Set<String> siblings) throws IOException;
+    }
+
+    // XXX in unit tests, indexing is always single-threaded,
+    // in which case the byte array can be a field instead of
+    // a thread local.  Not clear if that is the case in the IDE.
+    final ThreadLocal<byte[]> BYTES = new ThreadLocal<>();
+    // A reasonable base array size that will accommodate typical
+    // class files, to avoid reallocating more than necessary
+    private static final int MIN_ARRAY_SIZE = 16384;
+
+    byte[] bytes(int size) {
+        // There is a pretty significant performance benefit
+        // to not allocating vast numbers of byte arrays
+        byte[] result = BYTES.get();
+        if (result == null || result.length < size) {
+            result = new byte[Math.max(MIN_ARRAY_SIZE, size)];
+            BYTES.set(result);
+        }
+        return result;
+    }
+
+    void read(File jar, JarClassEntryConsumer consumer) throws IOException {
+        Set<String> classNames = new HashSet<>();
+        try (JarFile jf = new JarFile(jar, false)) {
+            // XXX the original code ignores siblings by first having a list
+            // of the class names.  Getting this before processing JAR entries
+            // means iterating the zip index twice.  Not horrible, but would
+            // be nice to avoid it
             Enumeration<JarEntry> e = jf.entries();
             while (e.hasMoreElements()) {
                 JarEntry entry = e.nextElement();
@@ -254,29 +330,44 @@ class ClassDependencyIndexCreator extends AbstractIndexCreator {
                     continue;
                 }
                 String clazz = name.substring(0, name.length() - 6);
-                ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.max((int) entry.getSize(), 0));
-                InputStream is = jf.getInputStream(entry);
-                try {
-                    FileUtil.copy(is, baos);
-                } finally {
-                    is.close();
+                classNames.add(clazz);
+            }
+            e = jf.entries();
+            while (e.hasMoreElements()) {
+                JarEntry entry = e.nextElement();
+                String name = entry.getName();
+                if (!name.endsWith(".class")) {
+                    continue;
+                }
+                int size = Math.max((int) entry.getSize(), 0);
+                if (size > 0) {
+                    // Parsing is considerably faster if the data is preloaded
+                    // into a byte array, likely due to random access
+                    byte[] target = bytes(size);
+                    try (InputStream in = jf.getInputStream(entry)) {
+                        int pos = 0;
+                        int count = 0;
+                        while (count != -1 && pos < size) {
+                            count = in.read(target, pos, size - pos);
+                            pos += count == -1 ? 0 : count;
+                        }
+                    }
+                    try (InputStream in = new ByteArrayInputStream(target, 0, size)) {
+                        String clazz = name.substring(0, name.length() - 6);
+                        consumer.accept(clazz, in, classNames);
+                    }
                 }
-                classfiles.put(clazz, baos.toByteArray());
             }
-            return classfiles;
         } catch (SecurityException x) {
             throw new IOException(x);
-        } finally {
-            jf.close();
         }
     }
 
     // adapted from org.netbeans.nbbuild.VerifyClassLinkage
-    private static Set<String> dependencies(byte[] data, String clazz, File jar) throws IOException {
-        Set<String> result = new TreeSet<String>();
-        DataInputStream input = new DataInputStream(new ByteArrayInputStream(data));
-        ClassFile cf = new ClassFile(input);
-        
+    private static Collection<String> dependencies(InputStream data, File jar) throws IOException {
+        Set<String> result = new HashSet<String>();
+        ClassFile cf = new ClassFile(data);
+
         Set<ClassName> cl = cf.getAllClassNames();
         for (ClassName className : cl) {
             result.add(className.getInternalName());
@@ -284,15 +375,10 @@ class ClassDependencyIndexCreator extends AbstractIndexCreator {
         return result;
     }
 
-    private static void skip(DataInput input, int bytes) throws IOException {
-        int skipped = input.skipBytes(bytes);
-        if (skipped != bytes) {
-            throw new IOException("Truncated class file");
-        }
-    }
-
-    @Override public Collection<IndexerField> getIndexerFields() {
-        return Arrays.asList(FLD_NB_DEPENDENCY_CLASS);
+    static final List<IndexerField> INDEXER_FIELDS = Collections.singletonList(FLD_NB_DEPENDENCY_CLASS);
+    @Override
+    public Collection<IndexerField> getIndexerFields() {
+        return INDEXER_FIELDS;
     }
 
     /**
diff --git a/maven.indexer/src/org/netbeans/modules/maven/indexer/FastScanner.java b/maven.indexer/src/org/netbeans/modules/maven/indexer/FastScanner.java
new file mode 100644
index 0000000..5b15030
--- /dev/null
+++ b/maven.indexer/src/org/netbeans/modules/maven/indexer/FastScanner.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.netbeans.modules.maven.indexer;
+
+import com.google.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.maven.index.ArtifactContext;
+import org.apache.maven.index.ArtifactContextProducer;
+import org.apache.maven.index.Scanner;
+import org.apache.maven.index.ScanningRequest;
+import org.apache.maven.index.ScanningResult;
+import org.apache.maven.index.context.IndexingContext;
+
+/**
+ * Alternative to Maven's DefaultScanner which ignores files NetBeans will not
+ * be interested in indexing; and publishes scanning requests incrementally,
+ * per-directory, rather than first collecting a tree's worth of artifacts.
+ *
+ * @author Tim Boudreau
+ */
+public class FastScanner
+        implements Scanner {
+
+    private final ArtifactContextProducer artifactContextProducer;
+    private static final Logger LOG = Logger.getLogger(FastScanner.class.getName());
+
+    @Inject
+    public FastScanner(ArtifactContextProducer artifactContextProducer) {
+        this.artifactContextProducer = artifactContextProducer;
+    }
+
+    public ScanningResult scan(ScanningRequest request) {
+        request.getArtifactScanningListener().scanningStarted(request.getIndexingContext());
+        ScanningResult result = new ScanningResult(request);
+        try {
+            scanDirectory(request.getStartingDirectory().toPath(), request);
+        } catch (IOException ex) {
+            LOG.log(Level.WARNING, "Scanning failed", ex);
+        } finally {
+            request.getArtifactScanningListener().scanningFinished(request.getIndexingContext(), result);
+        }
+
+        return result;
+    }
+
+    private void scanDirectory(Path dir, ScanningRequest request) throws IOException {
+        if (dir == null) {
+            return;
+        }
+        Files.walkFileTree(dir, new FileVisitor<Path>() {
+            private final Set<Path> poms = new HashSet<>();
+            private final Set<Path> artifacts = new HashSet<>();
+
+            @Override
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+                poms.clear();
+                artifacts.clear();
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                String nm = file.getFileName().toString();
+                if (nm.endsWith(".pom")) {
+                    poms.add(file);
+                    // txt and sha1 are needed for tests to pass, but not likely useful
+                    // in NetBeans, and will impact performance
+                } else if (nm.endsWith(".jar") || nm.endsWith(".nbm") || nm.endsWith(".txt") || nm.endsWith(".xml")) {
+                    artifacts.add(file);
+                }
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+                LOG.log(Level.INFO, "Visit failed: " + file, exc);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                // Ensure JARs are procssed before POMs - see comments on
+                // DefaultScanner's nested comparator class for why
+                try {
+                    if (!artifacts.isEmpty()) {
+                        for (Path jar : artifacts) {
+                            processFile(jar.toFile(), request);
+                        }
+                    }
+                    if (!poms.isEmpty()) {
+                        for (Path pom : poms) {
+                            processFile(pom.toFile(), request);
+                        }
+                    }
+                } catch (Exception e) {
+                    LOG.log(Level.INFO, "Exception indexing " + artifacts + ", " + poms, e);
+                }
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+
+    private void processFile(File file, ScanningRequest request) {
+        IndexingContext context = request.getIndexingContext();
+        ArtifactContext ac = artifactContextProducer.getArtifactContext(context, file);
+
+        if (ac != null) {
+            request.getArtifactScanningListener().artifactDiscovered(ac);
+        }
+    }
+}
diff --git a/maven.indexer/src/org/netbeans/modules/maven/indexer/MatchWords.java b/maven.indexer/src/org/netbeans/modules/maven/indexer/MatchWords.java
new file mode 100644
index 0000000..cff7000
--- /dev/null
+++ b/maven.indexer/src/org/netbeans/modules/maven/indexer/MatchWords.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.netbeans.modules.maven.indexer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Given multiple strings, this {@link Predicate} tests positive, if one or
+ * multiple of the strings are prefixes of the string, that is tested.
+ *
+ * @author Tim Boudreau
+ */
+final class MatchWords implements Predicate<String> {
+
+    private final List<MatchState> matchers = new ArrayList<>();
+    private ThreadLocal<MatchState[]> local = new ThreadLocal<>();
+
+    MatchWords(String[] strings) {
+        for (String s : strings) {
+            matchers.add(new MatchState(s));
+        }
+    }
+
+    private MatchState[] matchers() {
+        MatchState[] result = local.get();
+        if (result == null) {
+            result = new MatchState[matchers.size()];
+            for (int i = 0; i < result.length; i++) {
+                result[i] = matchers.get(i).copy();
+            }
+            local.set(result);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean test(String t) {
+        int max = t.length();
+        MatchState[] mtchrs = matchers();
+        for (MatchState mtchr : mtchrs) {
+            mtchr.reset();
+        }
+        for (int i = 0; i < max; i++) {
+            char c = t.charAt(i);
+            for (int j = 0; j < mtchrs.length; j++) {
+                mtchrs[j].check(c);
+                if (mtchrs[j].isMatched()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static final class MatchState {
+
+        private final char[] what;
+        private int matched = 0;
+        private boolean failed = false;
+
+        MatchState(String what) {
+            this.what = what.toCharArray();
+        }
+
+        MatchState(char[] what) {
+            this.what = what;
+        }
+
+        public MatchState copy() {
+            return new MatchState(what);
+        }
+
+        private void reset() {
+            matched = 0;
+            failed = false;
+        }
+
+        boolean isMatched() {
+            return matched >= what.length;
+        }
+
+        void check(char c) {
+            if (failed || isMatched()) {
+                return;
+            }
+            if (what[matched] == c) {
+                matched++;
+            } else {
+                failed = true;
+            }
+        }
+    }
+}
diff --git a/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryIndexerImpl.java b/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryIndexerImpl.java
index 7affa26..0c46109 100644
--- a/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryIndexerImpl.java
+++ b/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryIndexerImpl.java
@@ -41,7 +41,6 @@ import java.util.zip.ZipError;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.queryparser.classic.QueryParser;
-import org.apache.maven.index.expr.SearchTyped;
 import org.codehaus.plexus.PlexusConstants;
 import org.apache.lucene.search.*;
 import org.apache.lucene.store.FSDirectory;
@@ -252,10 +251,10 @@ public class NexusRepositoryIndexerImpl implements RepositoryIndexerImplementati
                 desc.addRequirement(req);
                 embedder.addComponentDescriptor(desc);
                 indexer = embedder.lookup(Indexer.class);
-                scanner = embedder.lookup(org.apache.maven.index.Scanner.class);
                 searcher = embedder.lookup(SearchEngine.class);
                 remoteIndexUpdater = embedder.lookup(IndexUpdater.class);
                 contextProducer = embedder.lookup(ArtifactContextProducer.class);
+                scanner = new FastScanner(contextProducer);
                 inited = true;
             } catch (Exception x) {
                 Exceptions.printStackTrace(x);
diff --git a/maven.indexer/test/unit/src/org/netbeans/modules/maven/indexer/ClassDependencyIndexCreatorTest.java b/maven.indexer/test/unit/src/org/netbeans/modules/maven/indexer/ClassDependencyIndexCreatorTest.java
index a61eb2a..f404a4b 100644
--- a/maven.indexer/test/unit/src/org/netbeans/modules/maven/indexer/ClassDependencyIndexCreatorTest.java
+++ b/maven.indexer/test/unit/src/org/netbeans/modules/maven/indexer/ClassDependencyIndexCreatorTest.java
@@ -19,12 +19,17 @@
 
 package org.netbeans.modules.maven.indexer;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
 import org.netbeans.modules.maven.indexer.api.RepositoryQueries.ClassUsage;
+import org.openide.filesystems.FileUtil;
 import org.openide.util.test.JarBuilder;
 import org.openide.util.test.TestFileUtils;
 
@@ -93,7 +98,12 @@ public class ClassDependencyIndexCreatorTest extends NexusTestBase {
         File jar = TestFileUtils.writeZipFile(new File(getWorkDir(), "x.jar"),
                 // XXX failed to produce a manifest that would generate a SecurityException if loaded with verify=true
                 "pkg/Clazz.class:ABC");
-        Map<String,byte[]> content = ClassDependencyIndexCreator.read(jar);
+        Map<String, byte[]> content = new TreeMap<>();
+        new ClassDependencyIndexCreator().read(jar, (String name, InputStream classData, Set<String> siblings) -> {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            FileUtil.copy(classData, out);
+            content.put(name, out.toByteArray());
+        });
         assertEquals("[pkg/Clazz]", content.keySet().toString());
         assertEquals("[65, 66, 67]", Arrays.toString(content.get("pkg/Clazz")));
     }
diff --git a/maven.indexer/test/unit/src/org/netbeans/modules/maven/indexer/MatchWordsTest.java b/maven.indexer/test/unit/src/org/netbeans/modules/maven/indexer/MatchWordsTest.java
new file mode 100644
index 0000000..dd57182
--- /dev/null
+++ b/maven.indexer/test/unit/src/org/netbeans/modules/maven/indexer/MatchWordsTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.netbeans.modules.maven.indexer;
+
+import java.util.function.Predicate;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class MatchWordsTest {
+
+    @Test
+    public void testPositiveMatches() {
+        MatchWords mw = new MatchWords(new String[]{"javax/swing", "javax/sql"});
+        assertTrue(mw.test("javax/swing"));
+        assertTrue(mw.test("javax/sql"));
+        assertTrue(mw.test("javax/swing/test"));
+        assertTrue(mw.test("javax/sql/test/more/depth"));
+    }
+
+    @Test
+    public void testNegativeMatches() {
+        MatchWords mw = new MatchWords(new String[]{"javax/swing", "javax/sql"});
+        assertFalse(mw.test("javax/swin"));
+        assertFalse(mw.test("javax/I_SHOULD_NOT_MATCH/sql"));
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
matthiasblaesing@apache.org.

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists