class PackageUtils {

    public static List<Class> findClassesInPackage(String packageName, ClassFilter filter) {
        try {
            List<String> classes = Lists.newArrayList();
            String packageDirName = packageName.replace('.', '/');
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

            Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName);
            List<URL> dirs = Lists.newArrayList();
            while (dirEnumeration.hasMoreElements()) {
                URL dir = dirEnumeration.nextElement();
                dirs.add(dir);
            }

            Iterator<URL> dirIterator = dirs.iterator();
            while (dirIterator.hasNext()) {
                URL url = dirIterator.next();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    findClassesInDirPackage(packageName, URLDecoder.decode(url.getFile(), "UTF-8"), classes);
                } else if ("jar".equals(protocol)) {
                    JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        String name = entry.getName();
                        String pName = packageName;
                        if (name.charAt(0) == '/') {
                            name = name.substring(1);
                        }
                        if (name.startsWith(packageDirName)) {
                            int idx = name.lastIndexOf('/');
                            if (idx != -1) {
                                pName = name.substring(0, idx).replace('/', '.');
                            }

                            if (idx != -1) {
                                // it's not inside a deeper dir
                                if (name.endsWith(".class") && !entry.isDirectory()) {
                                    String className = name.substring(pName.length() + 1, name.length() - 6);
                                    classes.add(makeFullClassName(pName, className));
                                }
                            }
                        }
                    }
                }
            }

            List<Class> result = Lists.newArrayList();
            for (String clazz : classes) {
                if (filter == null || filter.preFilter(clazz)) {
                    Class<?> cls = null;
                    try {
                        cls = Class.forName(clazz);
                    } catch (Throwable e) {
                        // ignore
                    }

                    if (cls != null && (filter == null || filter.filter(cls))) {
                        result.add(cls);
                    }
                }
            }

            return result;
        } catch (IOException e) {
            throw new FunctionException("findClassesInPackage : " + packageName + " is failed. ", e);
        }

    }

}