/*
 * 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.api.java.classpath;

import java.io.File;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.FileOutputStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.jar.JarOutputStream;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import org.netbeans.junit.Log;
import org.netbeans.junit.NbTestCase;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.java.classpath.ClassPathImplementation;
import org.netbeans.spi.java.classpath.ClassPathFactory;
import org.netbeans.spi.java.classpath.FilteringPathResourceImplementation;
import org.netbeans.spi.java.classpath.PathResourceImplementation;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.BaseUtilities;
import org.openide.util.Utilities;
import org.openide.util.test.MockPropertyChangeListener;

public class ClassPathTest extends NbTestCase {

    public ClassPathTest(String testName) {
        super(testName);
    }

    protected @Override void setUp() throws Exception {
        super.setUp();
        clearWorkDir();
    }

    private File getBaseDir() throws Exception {
        return FileUtil.normalizeFile(getWorkDir());
    }

    /**
     * Tests ClassPath.getResourceName ();
     */
    public void testGetResourceName() throws Exception {
        File f = getBaseDir();        
        f = new File(f.getPath()+"/w.e.i.r.d/f  o  l  d  e  r");
        f.mkdirs();
        File f2 = new File(f, "org/netbeans/test");
        f2.mkdirs();
        File f3 = new File(f2, "Main.java");
        f3.createNewFile();
        File f4 = new File(f2, ".dotname");
        f4.createNewFile();
        File f5 = new File(f2, ".dotname.dotext");
        f5.createNewFile();

        FileObject cpRoot = FileUtil.toFileObject(f);
        FileObject cpItem = FileUtil.toFileObject(f2);
        FileObject clazz = FileUtil.toFileObject(f3);
        FileObject dotname = FileUtil.toFileObject(f4);
        FileObject dotnameDotext = FileUtil.toFileObject(f5);
        ClassPath cp = ClassPathSupport.createClassPath(new FileObject[]{cpRoot});
        String pkg = cp.getResourceName(cpItem);
        assertEquals("org/netbeans/test", pkg);
        
        pkg = cp.getResourceName(cpItem, '.', true);
        assertEquals("org.netbeans.test", pkg);
        
        pkg = cp.getResourceName(cpItem, '.', false);
        assertEquals("org.netbeans.test", pkg);
        
        pkg = cp.getResourceName(cpItem, '#', true);
        assertEquals("org#netbeans#test", pkg);
        
        pkg = cp.getResourceName(cpItem, '#', false);
        assertEquals("org#netbeans#test", pkg);
        
        pkg = cp.getResourceName(clazz);
        assertEquals("org/netbeans/test/Main.java", pkg);
        
        pkg = cp.getResourceName(clazz, '.', true);
        assertEquals("org.netbeans.test.Main.java", pkg);
        
        pkg = cp.getResourceName(clazz, '.', false);
        assertEquals("org.netbeans.test.Main", pkg);
        
        pkg = cp.getResourceName(clazz, '@', true);
        assertEquals("org@netbeans@test@Main.java", pkg);
        
        pkg = cp.getResourceName(clazz, '@', false);
        assertEquals("org@netbeans@test@Main", pkg);

        //bug #152427
        pkg = cp.getResourceName(dotname, '/', false);
        assertEquals("org/netbeans/test/.dotname", pkg);
        pkg = cp.getResourceName(dotnameDotext, '/', false);
        assertEquals("org/netbeans/test/.dotname", pkg);
    }
    
    /**
     * Tests ClassPath.findAllResources(), ClassPath.findResoruce(), 
     * ClassPath.contains (), ClassPath.findOwnerRoot(),
     * ClassPath.isResourceVisible ()
     */
    public void testGetResource () throws Exception {
        File root_1 = new File (getBaseDir(),"root_1");
        root_1.mkdir();
        File root_2 = new File (getBaseDir(),"root_2");
        root_2.mkdir();
        FileObject[] roots = new FileObject [] {
            FileUtil.toFileObject(root_1),
            FileUtil.toFileObject(root_2),
        };
        
        FileObject tmp = roots[0].createFolder("org");
        tmp = tmp.createFolder("me");
        FileObject testFo_1 = tmp.createData("Foo","txt");
        tmp = roots[1].createFolder("org");
        tmp = tmp.createFolder("me");
        FileObject testFo_2 = tmp.createData("Foo","txt");        
        ClassPath cp = ClassPathSupport.createClassPath(roots);        
        
        //findResource
        assertTrue(cp.findResource ("org/me/Foo.txt")==testFo_1);
        assertTrue (cp.findResource("org/me/None.txt")==null);
        
        //findAllResources
        List res = cp.findAllResources ("org/me/Foo.txt");
        assertTrue (res.size() == 2);
        assertTrue (res.contains(testFo_1));
        assertTrue (res.contains(testFo_2));
        
        //contains
        assertTrue (cp.contains (testFo_1));
        assertTrue (cp.contains (testFo_2));
        assertFalse (cp.contains (roots[0].getParent()));
        
        //findOwnerRoot
        assertTrue (cp.findOwnerRoot(testFo_1)==roots[0]);
        assertTrue (cp.findOwnerRoot(testFo_2)==roots[1]);

        /*
        //isResourceVisible
        assertTrue (cp.isResourceVisible(testFo_1));
        assertFalse (cp.isResourceVisible(testFo_2));
         */
        
        cp = null;
        roots[0].delete();
        roots[1].delete();
    }
    
    /**
     * Test ClassPath.getRoots(), ClassPath.addPropertyChangeListener (),
     * ClassPath.entries () and classpath SPI.
     */
    public void testListening() throws Exception {
        // XXX unreliable, would be improved by usage of TestFileUtils methods:

        File root_1 = new File (getBaseDir(),"root_1");
        root_1.mkdir();
        File root_2 = new File (getBaseDir(),"root_2");
        root_2.mkdir();
        File root_3 = new File (getBaseDir(),"root_3.jar");
        JarOutputStream out = new JarOutputStream ( new FileOutputStream (root_3));
        try {            
            out.putNextEntry(new ZipEntry("test.txt"));
            out.write ("test".getBytes());
        } finally {
            out.close ();
        }        
        assertNotNull("Cannot find file",FileUtil.toFileObject(root_1));
        assertNotNull("Cannot find file",FileUtil.toFileObject(root_2));
        assertNotNull("Cannot find file",FileUtil.toFileObject(root_3));
        TestClassPathImplementation impl = new TestClassPathImplementation();
	ClassPath cp = ClassPathFactory.createClassPath (impl);
        impl.addResource(Utilities.toURI(root_1).toURL());
        cp.addPropertyChangeListener (impl);
        impl.addResource (Utilities.toURI(root_2).toURL());
        impl.assertEvents(ClassPath.PROP_ENTRIES, ClassPath.PROP_ROOTS);
        assertTrue (cp.getRoots().length==2);
        impl.removeResource (Utilities.toURI(root_2).toURL());
        impl.assertEvents(ClassPath.PROP_ENTRIES, ClassPath.PROP_ROOTS);
        assertTrue (cp.getRoots().length==1);
        FileObject fo = cp.getRoots()[0];
        FileObject parentFolder = fo.getParent();        
        fo.delete();
        impl.assertEvents(ClassPath.PROP_ROOTS);
        assertTrue (cp.getRoots().length==0);
        parentFolder.createFolder("root_1");
        assertTrue (cp.getRoots().length==1);
        impl.assertEvents(ClassPath.PROP_ROOTS);
        FileObject archiveFile = FileUtil.toFileObject(root_3);
        impl.addResource(FileUtil.getArchiveRoot(archiveFile.toURL()));
        assertEquals (cp.getRoots().length,2);
        impl.assertEvents(ClassPath.PROP_ENTRIES, ClassPath.PROP_ROOTS);
        root_3.delete();
        root_3 = new File (getBaseDir(),"root_3.jar");
        Thread.sleep(1000);
        out = new JarOutputStream ( new FileOutputStream (root_3));
        try {            
            out.putNextEntry(new ZipEntry("test2.txt"));
            out.write ("test2".getBytes());
        } finally {
            out.close ();
        }
        archiveFile.refresh();
        impl.assertEvents(ClassPath.PROP_ROOTS);
        root_1.delete();
        root_2.delete();
        root_3.delete();
        cp = null;
    }

    public void testListening2() throws Exception {
        // Checks that changes in PathResourceImplementation.PROP_ROOTS matter.
        class FiringPRI implements PathResourceImplementation {
            private URL[] roots = new URL[0];
            public URL[] getRoots() {
                return roots;
            }
            void changeRoots(URL[] nue) {
                roots = nue;
                pcs.firePropertyChange(PROP_ROOTS, null, null);
            }
            public ClassPathImplementation getContent() {
                return null;
            }
            PropertyChangeSupport pcs = new PropertyChangeSupport(this);
            public void addPropertyChangeListener(PropertyChangeListener listener) {
                pcs.addPropertyChangeListener(listener);
            }
            public void removePropertyChangeListener(PropertyChangeListener listener) {
                pcs.removePropertyChangeListener(listener);
            }
        }
        FiringPRI pri = new FiringPRI();
        TestClassPathImplementation impl = new TestClassPathImplementation();
        impl.addResource(pri);
        ClassPath cp = ClassPathFactory.createClassPath(impl);
        assertEquals(Collections.emptyList(), Arrays.asList(cp.getRoots()));
        cp.addPropertyChangeListener(impl);
        File d = new File(getBaseDir(), "d");
        d.mkdir();
        pri.changeRoots(new URL[] {Utilities.toURI(d).toURL()});
        impl.assertEvents(ClassPath.PROP_ENTRIES, ClassPath.PROP_ROOTS);
        assertEquals(Collections.singletonList(FileUtil.toFileObject(d)), Arrays.asList(cp.getRoots()));
    }
    
    public void testChangesAcknowledgedWithoutListener() throws Exception {
        // Discovered in #72573.
        clearWorkDir();
        ClassPath cp = ClassPathSupport.createClassPath(FileUtil.urlForArchiveOrDir(new File(getWorkDir(), "root")));
        assertEquals("nothing there yet", null, cp.findResource("f"));
        FileObject f = FileUtil.createData(FileUtil.toFileObject(getWorkDir()), "root/f");
        assertEquals("found new file", f, cp.findResource("f"));
        f.delete();
        assertEquals("again empty", null, cp.findResource("f"));
    }

    public void testFlags() {
        final FlaggedClassPathImpl fcpImpl = new FlaggedClassPathImpl();
        final ClassPath cp = ClassPathFactory.createClassPath(fcpImpl);
        Set<ClassPath.Flag> flags = cp.getFlags();
        assertNotNull(flags);
        assertTrue(flags.isEmpty());
        fcpImpl.setFlags(EnumSet.of(ClassPath.Flag.INCOMPLETE));
        flags = cp.getFlags();
        assertNotNull(flags);
        assertEquals(1, flags.size());
        assertEquals(ClassPath.Flag.INCOMPLETE, flags.iterator().next());
        fcpImpl.setFlags(EnumSet.noneOf(ClassPath.Flag.class));
        flags = cp.getFlags();
        assertNotNull(flags);
        assertTrue(flags.isEmpty());
    }

    public void testFlagsFiring() {
        final FlaggedClassPathImpl fcpImpl = new FlaggedClassPathImpl();
        final ClassPath cp = ClassPathFactory.createClassPath(fcpImpl);
        Set<ClassPath.Flag> flags = cp.getFlags();
        assertNotNull(flags);
        assertTrue(flags.isEmpty());
        final MockPropertyChangeListener pl = new MockPropertyChangeListener(ClassPath.PROP_FLAGS);
        cp.addPropertyChangeListener(pl);
        fcpImpl.setFlags(EnumSet.of(ClassPath.Flag.INCOMPLETE));
        pl.assertEventCount(1);
        fcpImpl.setFlags(EnumSet.noneOf(ClassPath.Flag.class));
        pl.assertEventCount(1);
    }

    static final class TestClassPathImplementation implements ClassPathImplementation, PropertyChangeListener {

        private final PropertyChangeSupport support = new PropertyChangeSupport (this);
        private final List<PathResourceImplementation> resources = new ArrayList<PathResourceImplementation> ();
        private final SortedSet<String> events = new TreeSet<String>();

        public synchronized void addResource (URL resource) {
            PathResourceImplementation pr = ClassPathSupport.createResource (resource);
            addResource(pr);
        }

        public synchronized void addResource(PathResourceImplementation pr) {
            this.resources.add (pr);
            this.support.firePropertyChange (ClassPathImplementation.PROP_RESOURCES,null,null);
        }

        public synchronized void removeResource (URL resource) {
            for (Iterator it = this.resources.iterator(); it.hasNext();) {
                PathResourceImplementation pr = (PathResourceImplementation) it.next ();
                if (Arrays.asList(pr.getRoots()).contains (resource)) {
                    this.resources.remove (pr);
                    this.support.firePropertyChange (ClassPathImplementation.PROP_RESOURCES,null,null);
                    break;
                }
            }
        }

        public synchronized List<? extends PathResourceImplementation> getResources() {
            return this.resources;
        }

        public void addPropertyChangeListener(PropertyChangeListener listener) {
            this.support.addPropertyChangeListener (listener);
        }

        public void removePropertyChangeListener(PropertyChangeListener listener) {
            this.support.removePropertyChangeListener (listener);
        }

        public void propertyChange (PropertyChangeEvent event) {
            events.add(event.getPropertyName());
        }

        void assertEvents(String... events) {
            assertEquals(new TreeSet<String>(Arrays.asList(events)), this.events);
            this.events.clear();
        }
    }

    public void testFilteredClassPaths() throws Exception {
        FileObject bd = FileUtil.toFileObject(getBaseDir());
        FileObject u1fo = bd.createFolder("u1");
        FileObject u2fo = bd.createFolder("u2");
        final URL u1 = u1fo.toURL();
        final URL u2 = u2fo.toURL();
        class FPRI implements FilteringPathResourceImplementation {
            private int modulus = 2;
            public void changeIncludes(int modulus) {
                this.modulus = modulus;
                pcs.firePropertyChange(PROP_INCLUDES, null, null);
            }
            public URL[] getRoots() {
                return new URL[] {u1, u2};
            }
            public boolean includes(URL root, String resource) {
                int offset;
                if (root.equals(u1)) {
                    offset = 0;
                } else if (root.equals(u2)) {
                    offset = 1;
                } else {
                    throw new IllegalArgumentException(root.toString());
                }
                return (offset + resource.length()) % modulus == 0;
            }
            public ClassPathImplementation getContent() {
                return null;
            }
            private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
            public void addPropertyChangeListener(PropertyChangeListener listener) {
                pcs.addPropertyChangeListener(listener);
            }
            public void removePropertyChangeListener(PropertyChangeListener listener) {
                pcs.removePropertyChangeListener(listener);
            }
        }
        FPRI pr = new FPRI();
        TestClassPathImplementation impl = new TestClassPathImplementation();
        impl.addResource(pr);
        ClassPath cp = ClassPathFactory.createClassPath(impl);
        FileObject xx1 = u1fo.createData("xx");
        FileObject xxx1 = u1fo.createData("xxx");
        FileObject xy1 = FileUtil.createData(u1fo, "x/y");
        FileObject x_1 = u1fo.createData("x ");
        String cau = "\u010Dau";
        FileObject cau1 = u1fo.createData(cau);
        FileObject folder = u1fo.createFolder("folder");
        FileObject foldr = u1fo.createFolder("foldr");
        FileObject xx2 = u2fo.createData("xx");
        FileObject xxx2 = u2fo.createData("xxx");
        FileObject xy2 = FileUtil.createData(u2fo, "x/y");
        FileObject x_2 = u2fo.createData("x ");
        FileObject cau2 = u2fo.createData(cau);
        assertEquals(Arrays.asList(u1fo, u2fo), Arrays.asList(cp.getRoots()));
        assertTrue(cp.contains(xx1));
        assertTrue(cp.contains(x_1));
        assertFalse(cp.contains(xxx1));
        assertFalse(cp.contains(cau1));
        assertFalse(cp.contains(xy1));
        assertFalse(cp.contains(xx2));
        assertFalse(cp.contains(x_2));
        assertTrue(cp.contains(xxx2));
        assertTrue(cp.contains(cau2));
        assertTrue(cp.contains(xy2));
        assertFalse(cp.contains(folder));
        assertTrue(cp.contains(foldr));
        assertEquals(xx1, cp.findResource("xx"));
        assertEquals(x_1, cp.findResource("x "));
        assertEquals(xxx2, cp.findResource("xxx"));
        assertEquals(cau2, cp.findResource(cau));
        assertEquals(xy2, cp.findResource("x/y"));
        assertEquals(null, cp.findResource("folder"));
        assertEquals(foldr, cp.findResource("foldr"));
        assertEquals(Collections.singletonList(xx1), cp.findAllResources("xx"));
        assertEquals(Collections.singletonList(x_1), cp.findAllResources("x "));
        assertEquals(Collections.singletonList(xxx2), cp.findAllResources("xxx"));
        assertEquals(Collections.singletonList(cau2), cp.findAllResources(cau));
        assertEquals(Collections.singletonList(xy2), cp.findAllResources("x/y"));
        assertEquals(Collections.emptyList(), cp.findAllResources("folder"));
        assertEquals(Collections.singletonList(foldr), cp.findAllResources("foldr"));
        assertEquals("xx", cp.getResourceName(xx1));
        assertEquals("x ", cp.getResourceName(x_1));
        assertEquals("xxx", cp.getResourceName(xxx1));
        assertEquals(cau, cp.getResourceName(cau1));
        assertEquals("x/y", cp.getResourceName(xy1));
        assertEquals("folder", cp.getResourceName(folder));
        assertEquals("foldr", cp.getResourceName(foldr));
        assertEquals(u1fo, cp.findOwnerRoot(xx1));
        assertEquals(u1fo, cp.findOwnerRoot(x_1));
        assertEquals(u1fo, cp.findOwnerRoot(xxx1));
        assertEquals(u1fo, cp.findOwnerRoot(cau1));
        assertEquals(u1fo, cp.findOwnerRoot(xy1));
        assertEquals(u1fo, cp.findOwnerRoot(folder));
        assertEquals(u1fo, cp.findOwnerRoot(foldr));
        assertTrue(cp.isResourceVisible(xx1));
        assertTrue(cp.isResourceVisible(x_1));
        assertFalse(cp.isResourceVisible(xxx1));
        assertFalse(cp.isResourceVisible(cau1));
        assertFalse(cp.isResourceVisible(xy1));
        assertFalse(cp.isResourceVisible(folder));
        assertTrue(cp.isResourceVisible(foldr));
        ClassPath.Entry e1 = cp.entries().get(0);
        assertTrue(e1.includes("xx"));
        assertTrue(e1.includes("x "));
        assertFalse(e1.includes("xxx"));
        assertFalse(e1.includes(cau));
        assertFalse(e1.includes("x/y"));
        assertFalse(e1.includes("folder/"));
        assertTrue(e1.includes("foldr/"));
        assertTrue(e1.includes(xx1));
        assertTrue(e1.includes(x_1));
        assertFalse(e1.includes(xxx1));
        assertFalse(e1.includes(cau1));
        assertFalse(e1.includes(xy1));
        assertFalse(e1.includes(folder));
        assertTrue(e1.includes(foldr));
        try {
            e1.includes(xx2);
            fail();
        } catch (IllegalArgumentException iae) {}
        assertTrue(e1.includes(xx1.toURL()));
        assertTrue(e1.includes(x_1.toURL()));
        assertFalse(e1.includes(xxx1.toURL()));
        assertFalse(e1.includes(cau1.toURL()));
        assertFalse(e1.includes(xy1.toURL()));
        assertFalse(e1.includes(folder.toURL()));
        assertTrue(e1.includes(foldr.toURL()));
        try {
            e1.includes(xx2.toURL());
            fail();
        } catch (IllegalArgumentException iae) {}
        cp.addPropertyChangeListener(impl);
        pr.changeIncludes(3);
        impl.assertEvents(ClassPath.PROP_INCLUDES);
        assertFalse(cp.contains(xx1));
        assertFalse(cp.contains(x_1));
        assertTrue(cp.contains(xxx1));
        assertTrue(cp.contains(cau1));
        assertTrue(cp.contains(xy1));
        assertTrue(cp.contains(xx2));
        assertTrue(cp.contains(x_2));
        assertFalse(cp.contains(xxx2));
        assertFalse(cp.contains(cau2));
        assertFalse(cp.contains(xy2));
        assertEquals(xx2, cp.findResource("xx"));
        assertEquals(x_2, cp.findResource("x "));
        assertEquals(xxx1, cp.findResource("xxx"));
        assertEquals(cau1, cp.findResource(cau));
        assertEquals(xy1, cp.findResource("x/y"));
        e1 = cp.entries().get(0);
        assertFalse(e1.includes("xx"));
        assertFalse(e1.includes("x "));
        assertTrue(e1.includes("xxx"));
        assertTrue(e1.includes(cau));
        assertTrue(e1.includes("x/y"));
        assertFalse(e1.includes(xx1));
        assertFalse(e1.includes(x_1));
        assertTrue(e1.includes(xxx1));
        assertTrue(e1.includes(cau1));
        assertTrue(e1.includes(xy1));
        assertFalse(e1.includes(xx1.toURL()));
        assertFalse(e1.includes(x_1.toURL()));
        assertTrue(e1.includes(xxx1.toURL()));
        assertTrue(e1.includes(cau1.toURL()));
        assertTrue(e1.includes(xy1.toURL()));
    }

    public void testFpriChangeFiring() throws Exception {
        class FPRI implements FilteringPathResourceImplementation {
            URL root;
            PropertyChangeSupport pcs = new PropertyChangeSupport(this);
            FPRI(URL root) {
                this.root = root;
            }
            public boolean includes(URL root, String resource) {
                return true;
            }
            public URL[] getRoots() {
                return new URL[] {root};
            }
            public ClassPathImplementation getContent() {
                return null;
            }
            public void addPropertyChangeListener(PropertyChangeListener listener) {
                pcs.addPropertyChangeListener(listener);
            }
            public void removePropertyChangeListener(PropertyChangeListener listener) {
                pcs.removePropertyChangeListener(listener);
            }
            void fire(Object propid) {
                PropertyChangeEvent e = new PropertyChangeEvent(this, FilteringPathResourceImplementation.PROP_INCLUDES, null, null);
                e.setPropagationId(propid);
                pcs.firePropertyChange(e);
            }
        }
        FPRI fpri1 = new FPRI(FileUtil.urlForArchiveOrDir(new File(getWorkDir(), "src1")));
        FPRI fpri2 = new FPRI(FileUtil.urlForArchiveOrDir(new File(getWorkDir(), "src2")));
        class L implements PropertyChangeListener {
            int cnt;
            public void propertyChange(PropertyChangeEvent e) {
                if (ClassPath.PROP_INCLUDES.equals(e.getPropertyName())) {
                    cnt++;
                }
            }
        }
        ClassPath cp = ClassPathSupport.createClassPath(Arrays.asList(fpri1, fpri2));
        L l = new L();
        cp.addPropertyChangeListener(l);
        fpri1.fire(null);
        assertEquals(0, l.cnt);
        cp.entries();
        fpri1.fire(null);
        assertEquals(1, l.cnt);
        fpri2.fire(null);
        assertEquals(2, l.cnt);
        fpri1.fire("hello");
        assertEquals(3, l.cnt);
        fpri2.fire("goodbye");
        assertEquals(4, l.cnt);
        fpri1.fire("fixed");
        assertEquals(5, l.cnt);
        fpri2.fire("fixed");
        assertEquals(5, l.cnt);
        fpri1.fire("new");
        assertEquals(6, l.cnt);
    }

    public void testLeakingClassPath() throws Exception {
        ClassPath cp = ClassPathSupport.createClassPath(new URL("file:///a/"), new URL("file:///b/"));
        ClassPath proxyCP = ClassPathSupport.createProxyClassPath(cp);
        Reference<ClassPath> proxy = new WeakReference<ClassPath>(proxyCP);
        
        proxyCP.entries();
        
        proxyCP = null;
        
        assertGC("the proxy classpath needs to GCable", proxy);
    }
    
    public void testGetClassLoaderPerf () throws Exception {
        final String bootPathProp = System.getProperty("sun.boot.class.path");  //NOI18N
        List<URL> roots = new ArrayList<URL> ();
        StringTokenizer tk = new StringTokenizer (bootPathProp,File.pathSeparator);
        if (tk.hasMoreTokens()) {
            final String path = tk.nextToken();
            final File f = FileUtil.normalizeFile(new File (path));
            if (f.canRead()) {
                roots.add(Utilities.toURI(f).toURL());
            }
        }
        final ClassLoader bootLoader = new URLClassLoader(roots.toArray(new URL[roots.size()]), null);
        
        final String classPathProp = System.getProperty("java.class.path");     //NOI18N
        roots = new ArrayList<URL> ();
        List<URL> roots2 = new ArrayList<URL>();
        tk = new StringTokenizer (classPathProp,File.pathSeparator);
        while (tk.hasMoreTokens()) {
            final String path = tk.nextToken();
            final File f = FileUtil.normalizeFile(new File (path));
            if (!f.canRead()) {
                continue;
            }
            URL url = Utilities.toURI(f).toURL();
            roots2.add(url);
            if (FileUtil.isArchiveFile(url)) {
                url = FileUtil.getArchiveRoot(url);
            }
            roots.add(url);
        }
        
        final ClassPath cp = ClassPathSupport.createClassPath(roots.toArray(new URL[roots.size()]));
//        final ClassLoader loader = ClassLoaderSupport.create(cp,bootLoader);
//        final ClassLoader loader = new URLClassLoader(roots.toArray(new URL[roots.size()]),bootLoader);
        final ClassLoader loader = new URLClassLoader(roots2.toArray(new URL[roots2.size()]),bootLoader);
        
        final Set<String> classNames = getClassNames (cp);
        int noLoaded = 0;
        int noFailed = 0;
        long st = System.currentTimeMillis();
        for (String className : classNames) {
            try {
                final Class<?> c = loader.loadClass(className);
                noLoaded++;
            } catch (ClassNotFoundException e) {
                noFailed++;
            }
            catch (NoClassDefFoundError e) {
                noFailed++;
            }
        }
        long et = System.currentTimeMillis();
        System.out.println("Loaded: " + noLoaded + " in: " + (et-st)+"ms");
    }

    public void testMemoryLeak183370() throws Exception {
        final File  wd = getWorkDir();
        class TestResource implements PathResourceImplementation {
            private final File f;
            private final PropertyChangeSupport sup = new PropertyChangeSupport(this);
            public TestResource(final File f){
                this.f=f;
            }
            @Override
            public URL[] getRoots() {
                return new URL[]{FileUtil.urlForArchiveOrDir(f)};
            }
            @Override
            public ClassPathImplementation getContent() {
                return null;
            }
            @Override
            public void addPropertyChangeListener(PropertyChangeListener listener) {
                sup.addPropertyChangeListener(listener);
            }
            @Override
            public void removePropertyChangeListener(PropertyChangeListener listener) {
                sup.removePropertyChangeListener(listener);
            }
        };
        class TestCPImpl implements ClassPathImplementation {
            private final List<? extends PathResourceImplementation> result = Arrays.asList(new TestResource(new File(wd,"1")), new TestResource(new File(wd,"2")));
            private final PropertyChangeSupport sup = new PropertyChangeSupport(this);

            @Override
            public List<? extends PathResourceImplementation> getResources() {
                return result;
            }
            @Override
            public void addPropertyChangeListener(PropertyChangeListener listener) {
                sup.addPropertyChangeListener(listener);
            }
            @Override
            public void removePropertyChangeListener(PropertyChangeListener listener) {
                sup.removePropertyChangeListener(listener);
            }
        };
        final TestCPImpl impl = new TestCPImpl();
        final ClassPath cp = ClassPathFactory.createClassPath(impl);
        assertEquals(2 ,cp.entries().size());
        assertEquals(impl.getResources().get(0).getRoots()[0] ,cp.entries().get(0).getURL());
        assertEquals(impl.getResources().get(1).getRoots()[0] ,cp.entries().get(1).getURL());
        assertEquals(1,((TestResource)impl.getResources().get(0)).sup.getPropertyChangeListeners().length);
        assertEquals(1,((TestResource)impl.getResources().get(1)).sup.getPropertyChangeListeners().length);
        impl.sup.firePropertyChange(ClassPathImplementation.PROP_RESOURCES,null,null);
        assertEquals(2 ,cp.entries().size());
        assertEquals(impl.getResources().get(0).getRoots()[0] ,cp.entries().get(0).getURL());
        assertEquals(impl.getResources().get(1).getRoots()[0] ,cp.entries().get(1).getURL());
        assertEquals(1,((TestResource)impl.getResources().get(0)).sup.getPropertyChangeListeners().length);
        assertEquals(1,((TestResource)impl.getResources().get(1)).sup.getPropertyChangeListeners().length);
    }
    
    private Set<String> getClassNames (final ClassPath cp) {
        Set<String> classNames = new HashSet<String> ();
        for (FileObject root : cp.getRoots()) {
            Enumeration<? extends FileObject> fos = root.getChildren(true);
            while (fos.hasMoreElements()) {
                FileObject fo = fos.nextElement();
                if (isImportant (fo)) {
                    classNames.add(cp.getResourceName(fo, '.', false));
                }
            }
        }
        return classNames;
    }
    
    private boolean isImportant (final FileObject fo) {
        if (fo.isFolder()) {
            return false;
        }
        if (!"class".equals(fo.getExt())) {      //NOI18N
            return false;
        }
        return !fo.getName().contains("$");     //NOI18N
                
    }

    public void testJVMPathConversion() throws Exception {
        String root = Utilities.toURI(getWorkDir()).toString();
        ClassPath cp = ClassPathSupport.createClassPath(
                new URL(root + "folder/"),
                new URL("jar:" + root + "file.zip!/"),
                new URL("jar:" + root + "file.zip!/subdir/"));
        assertEquals(massagePath("<root>/folder:<root>/file.zip"), cp.toString(ClassPath.PathConversionMode.SKIP));
        assertEquals(massagePath("<root>/folder:<root>/file.zip:") + "jar:" + root + "file.zip!/subdir/", cp.toString(ClassPath.PathConversionMode.PRINT));
        try {
            cp.toString(ClassPath.PathConversionMode.FAIL);
            fail();
        } catch (IllegalArgumentException x) {/* OK */}
        CharSequence warnings = Log.enable(ClassPath.class.getName(), Level.WARNING);
        assertEquals(massagePath("<root>/folder:<root>/file.zip"), cp.toString(ClassPath.PathConversionMode.WARN));
        assertTrue(warnings.toString(), warnings.toString().contains("subdir"));

        cp = ClassPathSupport.createClassPath(
                new URL(root + "folder/"),
                new URL("jar:" + root + "file.zip!/"));
        assertEquals(cp.toString(), ClassPathSupport.createClassPath(cp.toString()).toString());
        // XXX could also test IAE (tricky - need to have a URLMapper in Lookup, etc.)
    }

    public void testInArchivePaths() throws Exception {
        final String root = Utilities.toURI(getWorkDir()).toString();
        //Test regular folder
        ClassPath cp = ClassPathSupport.createClassPath(new URL(root + "folder/"));
        assertEquals(massagePath("<root>/folder"), cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.INCLUDE));
        assertEquals(massagePath("<root>/folder"), cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.EXCLUDE));
        assertEquals(massagePath("<root>/folder"), cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.FAIL));
        //Test archive with no in archive path
        cp = ClassPathSupport.createClassPath(new URL("jar:" + root + "file.zip!/"));
        assertEquals(massagePath("<root>/file.zip"), cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.INCLUDE));
        assertEquals(massagePath("<root>/file.zip"), cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.EXCLUDE));
        assertEquals(massagePath("<root>/file.zip"), cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.FAIL));
        //Test archive with in archive path
        cp = ClassPathSupport.createClassPath(new URL("jar:" + root + "file.zip!/java.base/"));
        assertEquals(massagePath("<root>/file.zip!/java.base/"), cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.INCLUDE));
        assertEquals(massagePath("<root>/file.zip"), cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.EXCLUDE));
        try {
            cp.toString(ClassPath.PathConversionMode.FAIL, ClassPath.PathEmbeddingMode.FAIL);
            assertTrue("Should be unreachable",false);
        } catch (IllegalArgumentException iae) {
            //IAE should be thrown
        }
    }

    public void testEmptyClassPath() throws Exception {
        final ClassPath cp = ClassPath.EMPTY;
        assertNotNull(cp);
        assertTrue(cp.entries().isEmpty());
    }

    private String massagePath(String path) throws Exception {
        return path.replace('/', File.separatorChar).replace(':', File.pathSeparatorChar).replace("<root>", getWorkDir().getAbsolutePath());
    }

    public void testInvalidURLs() throws Exception {
        try {
            ClassPathSupport.createClassPath(new URL("file:/some/jar/without/correct/protocol.jar"));
            fail();
        } catch (IllegalArgumentException x) {/* right */}
        try {
            ClassPathSupport.createClassPath(new URL("file:/some/dir/without/final/slash"));
            fail();
        } catch (IllegalArgumentException x) {/* right */}
        try {
            File f = new File(getWorkDir(),"test.jar");
            f.createNewFile();
            ClassPathSupport.createClassPath(new URL(BaseUtilities.toURI(f).toString() + '/'));
            fail();
        } catch (IllegalArgumentException x) {/* right */}
    }
    
    public void testEntryGetRoot() throws Exception {
        final File wd = getWorkDir();
        final FileObject root = FileUtil.createFolder(new File(wd,"root"));
        final ClassPath cp = ClassPathSupport.createClassPath(root);
        final ClassPath.Entry entry = cp.entries().iterator().next();
        entry.isDataResult.set(Boolean.TRUE);
        final FileObject fo = entry.getRoot();
        assertNotNull(fo);
        assertEquals(root, fo);
    }

    public void testEquality() throws Exception {
        assertTrue(ClassPath.EMPTY.equals(ClassPath.EMPTY));
        assertFalse(ClassPath.EMPTY.equals(ClassPathSupport.createClassPath(new URL[0])));
        assertFalse(ClassPathSupport.createClassPath(new URL[0]).equals(ClassPathSupport.createClassPath(new URL[0]))); // TBD; could revisit
        class Custom implements ClassPathImplementation {
            @Override public List<? extends PathResourceImplementation> getResources() {
                return Collections.emptyList();
            }
            @Override public void addPropertyChangeListener(PropertyChangeListener listener) {}
            @Override public void removePropertyChangeListener(PropertyChangeListener listener) {}
            @Override public boolean equals(Object obj) {
                return obj instanceof Custom;
            }
            @Override public int hashCode() {
                return 0;
            }
        }
        ClassPathImplementation cpi = new Custom();
        assertTrue(ClassPathFactory.createClassPath(cpi).equals(ClassPathFactory.createClassPath(cpi)));
        assertTrue(ClassPathFactory.createClassPath(cpi).equals(ClassPathFactory.createClassPath(new Custom())));
        assertFalse(ClassPathFactory.createClassPath(cpi).equals(ClassPathSupport.createClassPath(new URL[0])));
    }

    public void testDataRoot () throws Exception {
        final File wd = getWorkDir();
        final ClassPath cp = ClassPathSupport.createClassPath(
                new URL(wd.toURI().toURL().toExternalForm()+"test1/"),    //NOI18N
                new URL(wd.toURI().toURL().toExternalForm()+"test2/")    //NOI18N
        );
        final FileObject test1 = FileUtil.createData(FileUtil.toFileObject(wd), "test1");   //NOI18N
        final FileObject test2 = FileUtil.createFolder(FileUtil.toFileObject(wd), "test2");   //NOI18N
        assertEquals(Collections.singletonList(test2),Arrays.asList(cp.getRoots()));
    }

    public void testGC() throws Exception {
        final FileObject wd = FileUtil.toFileObject(FileUtil.normalizeFile(getWorkDir()));
        final FileObject root1 = FileUtil.createFolder(wd, "src1"); //NOI18N
        assertNotNull(root1);
        FileObject root2 = FileUtil.createFolder(wd, "src2"); //NOI18N
        assertNotNull(root2);
        final MockPropertyChangeListener l = new MockPropertyChangeListener(ClassPath.PROP_ROOTS);
        ClassPath cp = ClassPathSupport.createClassPath(root1, root2);
        cp.addPropertyChangeListener(l);
        assertNotNull(cp);
        FileObject[] roots = cp.getRoots();
        assertEquals(2, roots.length);
        assertEquals(root1, roots[0]);
        assertEquals(root2, roots[1]);
        root2.delete();
        l.assertEventCount(1);
        roots = cp.getRoots();
        assertEquals(1, roots.length);
        assertEquals(root1, roots[0]);
        root2 = FileUtil.createFolder(wd, "src2"); //NOI18N
        l.assertEventCount(1);
        roots = cp.getRoots();
        assertEquals(2, roots.length);
        assertEquals(root1, roots[0]);
        assertEquals(root2, roots[1]);
        final Reference<ClassPath> cpRef = new WeakReference<>(cp);
        cp = null;
        assertGC("ClassPath freed", cpRef);
    }

}
