/*
 * Copyright 2011, Mysema Ltd
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mysema.util;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

/**
 * ClassPathUtils provides classpath scanning functionality
 * 
 * @author tiwe
 *
 */
public final class ClassPathUtils {
    
    private static final Pattern JAR_URL_SEPARATOR = Pattern.compile("!");
    
    public static Set<Class<?>> scanPackage(ClassLoader classLoader, Package pkg) throws IOException {
        return scanPackage(classLoader, pkg.getName());
    }
    
    public static Set<Class<?>> scanPackage(ClassLoader classLoader, String pkg) throws IOException {
        String packagePath = pkg.replace('.', '/');
        Enumeration<URL> urls = classLoader.getResources(packagePath);
        Set<Class<?>> classes = new HashSet<Class<?>>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url.getProtocol().equals("jar")) {
                scanJar(classLoader, classes, url, packagePath);

            } else if (url.getProtocol().equals("file")) {
                scanDirectory(classLoader, pkg, classes, url, pkg);

            } else {
                throw new IllegalArgumentException("Illegal url : " + url);
            }
        }
        return classes;
    }

    private static void scanDirectory(ClassLoader classLoader, String pkg, Set<Class<?>> classes, 
            URL url, String packageName) throws IOException {
        Deque<File> files = new ArrayDeque<File>();
        String packagePath;
        try {
            File packageAsFile = new File(url.toURI());
            packagePath = packageAsFile.getPath();
            files.add(packageAsFile);
        } catch (URISyntaxException e) {
            throw new IOException(e);
        }
        while (!files.isEmpty()) {
            File file = files.pop();
            for (File child : file.listFiles()) {
                if (child.getName().endsWith(".class")) {
                    String fileName = child.getPath().substring(packagePath.length()+1).replace(File.separatorChar, '.');
                    String className = pkg + "." + fileName.substring(0, fileName.length()-6);
                    if (className.startsWith(packageName)) {
                        Class<?> cl = safeClassForName(classLoader, className);
                        if (cl != null) {
                            classes.add(cl);
                        }    
                    }                    
                } else if (child.isDirectory()) {
                    files.add(child);
                }
            }
        }
    }

    private static void scanJar(ClassLoader classLoader, Set<Class<?>> classes, URL url, 
            String packagePath) throws IOException {
        // See http://stackoverflow.com/a/402771/14731
        JarURLConnection connection = (JarURLConnection) url.openConnection();
        JarFile jarFile = connection.getJarFile();
        try {
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();            
                if (entry.getName().endsWith(".class") && entry.getName().startsWith(packagePath) 
                        && entry.getName().startsWith(connection.getEntryName())) {
                    String className = entry.getName().substring(0, entry.getName().length()-6).replace('/', '.');
                    Class<?> cl = safeClassForName(classLoader, className);
                    if (cl != null) {
                        classes.add(cl);
                    }
                }
            }    
        } finally {
            jarFile.close();    
        }                
    }
    
    public static Class<?> safeClassForName(ClassLoader classLoader, String className) {
        try {
            if (className.startsWith("com.sun")) {
                return null;
            } else {
                return Class.forName(className, true, classLoader);
            }
        } catch (ClassNotFoundException e) {
            return null;
        } catch (NoClassDefFoundError e) {
            return null;
        }
    }

    private ClassPathUtils() {}
    
}

@@ -28,18 +28,17 @@
 a Ltd%0A *
- 
 %0A
+
  * Licen
@@ -939,40 +939,8 @@
 ile;
-%0Aimport java.util.regex.Pattern;
 %0A%0A/*
@@ -1003,17 +1003,16 @@
 ality%0A *
- 
 %0A * @aut
@@ -1067,93 +1067,9 @@
 s %7B%0A
-    %0A    private static final Pattern JAR_URL_SEPARATOR = Pattern.compile(%22!%22);%0A    
 %0A
+
     
@@ -1225,29 +1225,25 @@
 e());%0A    %7D%0A
-    
 %0A
+
     public s
@@ -2089,17 +2089,16 @@
 classes,
- 
 %0A       
@@ -3122,36 +3122,32 @@
                %7D
-    
 %0A               
@@ -3152,36 +3152,16 @@
        %7D
-                    
 %0A       
@@ -3378,17 +3378,16 @@
 URL url,
- 
 %0A       
@@ -3797,28 +3797,16 @@
 ement();
-            
 %0A       
@@ -3895,17 +3895,16 @@
 agePath)
- 
 %0A       
@@ -4303,20 +4303,16 @@
        %7D
-    
 %0A       
@@ -4352,20 +4352,16 @@
 close();
-    
 %0A       
@@ -4366,36 +4366,16 @@
    %7D
-                
 %0A    %7D%0A
-    
 %0A
+
     
@@ -4843,15 +4843,11 @@
 ls() %7B%7D%0A
-    
 %0A%7D%0A

Remove unused constants