001package net.gdface.utils;
002
003import java.io.File;
004import java.io.FileFilter;
005import java.net.MalformedURLException;
006import java.net.URL;
007import java.net.URLClassLoader;
008import java.util.Collection;
009import java.util.HashSet;
010import java.util.Set;
011
012public class ClassLoaderUtils {
013        /**
014         * {@code parent}为{@code null}时策略<br>
015         * {@code defaultParentLoader} 使用default parent class loader,参见{@link URLClassLoader#newInstance(URL[])}<br>
016         * {@code threadContextLoader} 使用当前线程的Thread Context ClassLoader作为parent,参见{@link Thread#getContextClassLoader()} <br>
017         * {@code currentClassLoader} 使用当前类({@link ClassLoaderUtils})的class loader<br>
018         * @author guyadong
019         *
020         */
021        public static enum ParentStrategy{
022                defaultParentLoader,    threadContextLoader,currentClassLoader
023        }
024        /**
025         * 默认值 {@link ParentStrategy#currentClassLoader }
026         * @see {@link ParentStrategy}
027         */
028        private static final ThreadLocal<ParentStrategy> parentLoaderStrategy = new ThreadLocal<ParentStrategy>(){
029                @Override
030                protected ParentStrategy initialValue() {
031                        return ParentStrategy.currentClassLoader;
032                }};
033
034        private static final boolean addIfJar(Collection<URL> out, File file) {
035                if (file.isFile() && file.getName().endsWith(".jar")) {
036                        out.add(toURL(file));
037                        return true;
038                }
039                return false;
040        }
041
042        private static final URL toURL(File file) {
043                try {
044                        return file.toURI().toURL();
045                } catch (MalformedURLException e) {
046                        throw new RuntimeException(e);
047                }
048        }
049
050        /**
051         * 返回文件{@link URL}集合<br>
052         * 如果 {@code file} 是文件夹,则文件夹所有jar包文件生成{@link URL} 集合<br>
053         * 如果{@code file}是jar,则将返回包含该jar的{@link URL} 集合<br>
054         * 如果不是jar抛出异常
055         * 
056         * @param file
057         *            不存在时抛出异常
058         * @param recursive
059         *            为 {@code true}时递归搜索子目录,{@code file}为文件夹时有效
060         * @return
061         */
062        private static final Set<URL> toJarURLs(File file, final boolean recursive) {
063                final HashSet<URL> out = new HashSet<URL>();
064                if (!file.exists())
065                        throw new IllegalArgumentException(" not exists:" + file.toString());
066                if (file.isFile()) {
067                        if (!addIfJar(out, file))
068                                throw new IllegalArgumentException(" not jar:" + file.toString());
069                }
070                if (file.isDirectory()) {
071                        file.listFiles(new FileFilter() {
072                                @Override
073                                public boolean accept(File pathname) {
074                                        if (!addIfJar(out, pathname) && pathname.isDirectory() && recursive) {
075                                                out.addAll(toJarURLs(pathname, recursive));
076                                        }
077                                        return false;
078                                }
079                        });
080                        return out;
081                }
082                throw new IllegalArgumentException(" invalid file:" + file.toString());
083        }
084
085        private final static Set<URL> toJarURLs(String path, final boolean recursive) {
086                if (null == path || 0 == path.length())
087                        throw new IllegalArgumentException("path must not be  null or empty ");
088                return toJarURLs(new File(path), recursive);
089        }
090
091        private final static Set<URL> toJarURLs(final boolean recursive, String... paths) {
092                if (null == paths)
093                        throw new NullPointerException("paths is null");
094                HashSet<URL> out = new HashSet<URL>();
095                for (String path : paths) {
096                        out.addAll(toJarURLs(path, recursive));
097                }
098                return out;
099        }
100
101        private final static Set<URL> toJarURLs(String... classpath) {
102                if (null == classpath)
103                        throw new NullPointerException("classpaths is null");
104                final HashSet<URL> out = new HashSet<URL>();
105                for (String path : classpath) {
106                        if (null == path || 0 == path.length())
107                                throw new IllegalArgumentException("classPaths have null or empty element");
108                        File file = new File(path);
109                        if (!file.exists())
110                                throw new IllegalArgumentException("no exists : " + file);
111                        if (file.isFile()) {
112                                if (!addIfJar(out, file))
113                                        throw new IllegalArgumentException("not jar:" + file);
114                        } else if (file.isDirectory())
115                                out.add(toURL(file));
116                }
117                return out;
118        }
119
120        private final static Set<URL> toJarURLs(boolean recursive, String[] libdirs, String[] classpath) {
121                HashSet<URL> out = new HashSet<URL>();
122                if (null != libdirs) {
123                        out.addAll(toJarURLs(recursive, libdirs));
124                }
125                if (null != classpath)
126                        out.addAll(toJarURLs(classpath));
127                return out;
128        }
129
130        /** @see #makeURLClassLoader(ClassLoader, boolean, String[], String[]) */
131        public final static URLClassLoader makeURLClassLoader(ClassLoader parent, String... classpath) {
132                return makeURLClassLoader(parent, false, null, classpath);
133        }
134
135        /** @see #makeURLClassLoader(ClassLoader, boolean, String[], String[]) */
136        public final static URLClassLoader makeURLClassLoader(ClassLoader parent, boolean recursive, String... libdirs) {
137                return makeURLClassLoader(parent, recursive, libdirs, null);
138        }
139
140        /**
141         * 根据{@code libdirs}提供的lib路径和{@code classpath}创建{@link URLClassLoader}实例<br>
142         * 如果所有的参数中都没有找URL(jar或class 文件夹),则抛出异常
143         * @param parent 指定父类加载器,为null时根据{@link #parentLoaderStrategy}决定parent
144         * @param recursive
145         *            指示是否递归搜索文件夹,对 {@code libdirs}有效,see also
146         *            {@link #toJarURLs(File, boolean)}
147         * @param libdirs
148         *            path列表,path为jar包或jar所在文件夹(such as 'lib')
149         * @param classpath
150         *            jar包或class文件夹路径
151         * 
152         * @return
153         * @see {@link URLClassLoader#newInstance(URL[],ClassLoader)}
154         * @see {@linkplain Thread#getContextClassLoader()}
155         */
156        public final static URLClassLoader makeURLClassLoader(ClassLoader parent, boolean recursive, String[] libdirs, String[] classpath) {
157                Set<URL> out = toJarURLs(recursive, libdirs, classpath);
158                if (out.isEmpty())
159                        throw new IllegalArgumentException("empty libdirs and classpath");
160                if(null == parent){
161                        switch(parentLoaderStrategy.get()){
162                        case defaultParentLoader:
163                                return URLClassLoader.newInstance(out.toArray(new URL[out.size()]));
164                        case threadContextLoader:
165                                parent = Thread.currentThread().getContextClassLoader();
166                                break;
167                        case currentClassLoader:        
168                                parent = ClassLoaderUtils.class.getClassLoader();
169                                break;
170                        }
171                }
172                return URLClassLoader.newInstance(out.toArray(new URL[out.size()]),parent);
173        }
174        /** @see #makeURLClassLoader(ClassLoader, boolean, String[], String[]) */
175        public final static URLClassLoader makeURLClassLoader(ClassLoader parent, boolean recursive,
176                        Collection<String> libdirs, Collection<String> classpath) {
177                return makeURLClassLoader(parent, recursive,
178                                null == libdirs ? null : libdirs.toArray(new String[0]), null == classpath ? null : classpath.toArray(new String[0]));
179        }
180        /**
181         * @param parentLoaderStrategy if null,{@link ParentStrategy#currentClassLoader} instead
182         * @see #makeURLClassLoader(ClassLoader, boolean, String[], String[])
183         * @see #parentLoaderStrategy 
184         */
185        public final static void setParentLoaderStrategy(ParentStrategy parentLoaderStrategy){
186                        ClassLoaderUtils.parentLoaderStrategy.set(null == parentLoaderStrategy
187                                        ?ParentStrategy.currentClassLoader
188                                        :parentLoaderStrategy);
189        }
190        /** @see #setParentLoaderStrategy(ParentStrategy) */
191        public final static void setParentLoaderStrategy(String parentLoaderStrategy){
192                setParentLoaderStrategy(ParentStrategy.valueOf(parentLoaderStrategy));
193        }
194}