OSDN Git Service

パッケージを変更しました。
[deployer/repo.git] / deployer / trunk / src / main / java / jp / sourceforge / deployer / DeployerClassLoader.java
1 \r
2 package jp.sourceforge.deployer;\r
3 \r
4 import java.io.ByteArrayOutputStream;\r
5 import java.io.File;\r
6 import java.io.IOException;\r
7 import java.io.InputStream;\r
8 import java.net.MalformedURLException;\r
9 import java.net.URL;\r
10 import java.util.ArrayList;\r
11 import java.util.Enumeration;\r
12 import java.util.List;\r
13 import java.util.NoSuchElementException;\r
14 import java.util.jar.JarEntry;\r
15 import java.util.jar.JarFile;\r
16 \r
17 /**\r
18  * <p>\r
19  * アーカイブ・ファイル配置後のクラスやリソースを利用するクラスローダーです。\r
20  * </p>\r
21  * <p>\r
22  * アーカイブ・ファイルにはクラス・ファイル、リソース、jarファイルを含めることが出来、コンストラクタでディレクトリを指定することでクラスローダーの管理下に置くことができます。例えば、classes/からクラス・ファイル、lib/からjarファイルを検索するように初期化することが出来ます。\r
23  * </p>\r
24  * <p>\r
25  * ホット・デプロイを前提としているので、クラスローダーが読み込んだリソースなどを破棄する{@link #dispose()}メソッドが用意されています。例えば{@link Deployer}クラスと連携する場合、{@link DeployerListener#undeployEnd(Deployer, File)}メソッドの呼び出しでこのクラスローダーを破棄することが出来ます。\r
26  * TODO: dispose()メソッドは実装されていません。一度管理下に置いたリソースを破棄する方法が分からないためです。\r
27  * </p>\r
28  * \r
29  * @author uguu\r
30  */\r
31 public class DeployerClassLoader extends ClassLoader {\r
32 \r
33     private List<DeployerClassLoaderListener> listenerList = new ArrayList<DeployerClassLoaderListener>();\r
34 \r
35     private File                              deployDirectory;\r
36 \r
37     private String                            classDirectory;\r
38 \r
39     private String                            jarDirectory;\r
40 \r
41     /**\r
42      * <p>\r
43      * {@link DeployerClassLoader}インスタンスを初期化します。\r
44      * </p>\r
45      * \r
46      * @param deployDirectory\r
47      *            アーカイブ・ファイルを配置したディレクトリ。このディレクトリ以下からクラス・ファイル、リソース、jarファイルなどを検索します。<br>\r
48      *            nullの場合、{@link IllegalArgumentException}例外をスローします。<br>\r
49      *            ディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
50      * @param classDirectory\r
51      *            クラス・ファイルを検索するディレクトリの名前。<br>\r
52      *            nullの場合、{@link IllegalArgumentException}例外をスローします。\r
53      * @param jarDirectory\r
54      *            jarファイルを検索するディレクトリの名前。<br>\r
55      *            nullの場合、{@link IllegalArgumentException}例外をスローします。\r
56      * @param parent\r
57      *            親クラスローダー。\r
58      */\r
59     public DeployerClassLoader(File deployDirectory, String classDirectory, String jarDirectory, ClassLoader parent) {\r
60         super(parent);\r
61         this.initialize(deployDirectory, classDirectory, jarDirectory);\r
62     }\r
63 \r
64     /**\r
65      * <p>\r
66      * {@link DeployerClassLoader}インスタンスを初期化します。\r
67      * </p>\r
68      * \r
69      * @param deployDirectory\r
70      *            アーカイブ・ファイルを配置したディレクトリ。このディレクトリ以下からクラス・ファイル、リソース、jarファイルなどを検索します。<br>\r
71      *            nullの場合、{@link IllegalArgumentException}例外をスローします。<br>\r
72      *            ディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
73      * @param classDirectory\r
74      *            クラス・ファイルを検索するディレクトリの名前。<br>\r
75      *            nullの場合、{@link IllegalArgumentException}例外をスローします。\r
76      * @param jarDirectory\r
77      *            jarファイルを検索するディレクトリの名前。<br>\r
78      *            nullの場合、{@link IllegalArgumentException}例外をスローします。\r
79      */\r
80     public DeployerClassLoader(File deployDirectory, String classDirectory, String jarDirectory) {\r
81         super();\r
82         this.initialize(deployDirectory, classDirectory, jarDirectory);\r
83     }\r
84 \r
85     /**\r
86      * <p>\r
87      * クラスローダーを初期化します。\r
88      * </p>\r
89      * \r
90      * @param deployDirectory\r
91      *            アーカイブ・ファイルを配置したディレクトリ。このディレクトリ以下からクラス・ファイル、リソース、jarファイルなどを検索します。<br>\r
92      *            nullの場合、{@link IllegalArgumentException}例外をスローします。<br>\r
93      *            ディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
94      * @param classDirectory\r
95      *            クラス・ファイルを検索するディレクトリの名前。<br>\r
96      *            nullの場合、{@link IllegalArgumentException}例外をスローします。\r
97      * @param jarDirectory\r
98      *            jarファイルを検索するディレクトリの名前。<br>\r
99      *            nullの場合、{@link IllegalArgumentException}例外をスローします。\r
100      */\r
101     private void initialize(File deployDirectory, String classDirectory, String jarDirectory) {\r
102         if (deployDirectory == null) {\r
103             throw new IllegalArgumentException("deployDirectory is null.");\r
104         }\r
105         if (!deployDirectory.isDirectory()) {\r
106             throw new IllegalArgumentException("deployDirectory is not directory.");\r
107         }\r
108         if (classDirectory == null) {\r
109             throw new IllegalArgumentException("classDirectory is null.");\r
110         }\r
111         if (jarDirectory == null) {\r
112             throw new IllegalArgumentException("jarDirectory is null.");\r
113         }\r
114 \r
115         this.deployDirectory = deployDirectory;\r
116         this.classDirectory = classDirectory;\r
117         this.jarDirectory = jarDirectory;\r
118     }\r
119 \r
120     /**\r
121      * <p>\r
122      * クラスローダーがクラスを操作したときのイベントを受け取るリスナーを追加します。\r
123      * </p>\r
124      * <p>\r
125      * このメソッドはスレッドセーフです。\r
126      * </p>\r
127      * \r
128      * @param listener\r
129      *            イベントが通知されるリスナー。<br>\r
130      *            nullの場合、{@link IllegalArgumentException}例外をスローします。\r
131      */\r
132     public void addListener(DeployerClassLoaderListener listener) {\r
133         if (listener == null) {\r
134             throw new IllegalArgumentException("listener is null.");\r
135         }\r
136         synchronized (this.listenerList) {\r
137             this.listenerList.add(listener);\r
138         }\r
139     }\r
140 \r
141     /**\r
142      * <p>\r
143      * 既に登録されているリスナーを削除します。\r
144      * </p>\r
145      * <p>\r
146      * このメソッドはスレッドセーフです。\r
147      * </p>\r
148      * \r
149      * @param listener\r
150      *            削除するリスナー。\r
151      */\r
152     public void removeListener(DeployerClassLoaderListener listener) {\r
153         synchronized (this.listenerList) {\r
154             this.listenerList.remove(listener);\r
155         }\r
156     }\r
157 \r
158     /**\r
159      * <p>\r
160      * クラスローダーが保持している全てのリソースを破棄します。\r
161      * </p>\r
162      * TODO: メソッドは機能しません。\r
163      */\r
164     public void dispose() {\r
165         // TODO 定義済みクラスをうまく解放する方法を考える。\r
166     }\r
167 \r
168     /**\r
169      * <p>\r
170      * クラスを検索します。\r
171      * </p>\r
172      * <p>\r
173      * このメソッドは、クラスがまだ読み込まれていないときに呼び出されます。呼び出されると、クラス・ディレクトリやjarファイルからクラスを検索し、見つかった場合、クラスとして定義します({@link #defineClass(String, byte[], int, int)}メソッドを呼び出します)。クラスが見つからなかった場合、{@link ClassNotFoundException}例外をスローします。\r
174      * </p>\r
175      * \r
176      * @param name\r
177      *            検索するクラスのFQN。\r
178      * @return 見つかったクラス。\r
179      * @throws ClassNotFoundException\r
180      *             クラスが見つからなかった場合。\r
181      */\r
182     @Override\r
183     protected Class<?> findClass(String name) throws ClassNotFoundException {\r
184         String resName = name.replace('.', '/') + ".class";\r
185         URL url = this.findResource(resName);\r
186         if (url == null) {\r
187             throw new ClassNotFoundException("\"" + name + "\"クラスが見つかりませんでした。");\r
188         }\r
189 \r
190         try {\r
191             InputStream in = url.openStream();\r
192             try {\r
193                 // classファイルを読み込む。\r
194                 ByteArrayOutputStream byteOut = new ByteArrayOutputStream();\r
195                 try {\r
196                     byte[] buf = new byte[1024];\r
197                     int len;\r
198                     while ((len = in.read(buf)) != -1) {\r
199                         byteOut.write(buf, 0, len);\r
200                     }\r
201                 } finally {\r
202                     byteOut.close();\r
203                 }\r
204                 // クラスを定義する。\r
205                 byte[] classData = byteOut.toByteArray();\r
206                 Class<?> clazz = this.defineClass(name, classData, 0, classData.length);\r
207                 // イベントを通知する。\r
208                 synchronized (this.listenerList) {\r
209                     for (DeployerClassLoaderListener l : this.listenerList) {\r
210                         l.findClass(clazz, url);\r
211                     }\r
212                 }\r
213                 return clazz;\r
214             } finally {\r
215                 in.close();\r
216             }\r
217         } catch (IOException e) {\r
218             throw new ClassNotFoundException("\"" + name + "\"クラスの読み込みに失敗しました。", e);\r
219         }\r
220     }\r
221 \r
222     /**\r
223      * <p>\r
224      * リソースを検索します。\r
225      * </p>\r
226      * <p>\r
227      * まずクラス・ディレクトリから、次にjarディレクトリからファイルを検索します。そして、最初に見つかったファイルのURLを返します。\r
228      * </p>\r
229      * <p>\r
230      * このメソッドは{@link #findResources(String)}メソッドを呼び出し、その最初のURLを返します。このとき、{@link #findResources(String)}メソッドが{@link IOException}例外をスローする可能性があります。{@link IOException}例外がスローされた場合、nullを返します。\r
231      * </p>\r
232      * \r
233      * @param name\r
234      *            リソース名。\r
235      * @return リソースのURL。\r
236      */\r
237     @Override\r
238     protected URL findResource(String name) {\r
239         Enumeration<URL> resources;\r
240         try {\r
241             resources = this.findResources(name);\r
242         } catch (IOException e) {\r
243             return null;\r
244         }\r
245         if (resources.hasMoreElements()) {\r
246             return resources.nextElement();\r
247         } else {\r
248             return null;\r
249         }\r
250     }\r
251 \r
252     /**\r
253      * <p>\r
254      * リソースを検索します。\r
255      * </p>\r
256      * <p>\r
257      * まずクラス・ディレクトリから、次にjarディレクトリからファイルを検索します。そして、見つかった全てのファイルのURLを列挙します。例えば、"foo/bar/boo.txt"がfoo.jarとbar.jarから見つかった場合、これらのURLが列挙されます。\r
258      * </p>\r
259      * \r
260      * @param name\r
261      *            リソース名。\r
262      * @return リソースのURLの列挙。\r
263      * @throws IOException\r
264      *             入出力エラーが発生した場合。\r
265      */\r
266     @Override\r
267     protected Enumeration<URL> findResources(String name) throws IOException {\r
268         File classDir = new File(this.deployDirectory, this.classDirectory);\r
269         if (!classDir.exists()) {\r
270             classDir = null;\r
271         }\r
272         File jarDir = new File(this.deployDirectory, this.jarDirectory);\r
273         if (!jarDir.exists()) {\r
274             jarDir = null;\r
275         }\r
276         return new ResourceEnumeration(classDir, jarDir, name);\r
277     }\r
278 \r
279     /**\r
280      * <p>\r
281      * リソースを列挙します。\r
282      * </p>\r
283      * \r
284      * @author uguu\r
285      */\r
286     private class ResourceEnumeration implements Enumeration<URL> {\r
287 \r
288         private String     resourceName;\r
289 \r
290         private File       classDir;\r
291 \r
292         private List<File> jarFileList;\r
293 \r
294         private int        currentIndex = -1;\r
295 \r
296         /**\r
297          * <p>\r
298          * インスタンスを初期化します。\r
299          * </p>\r
300          * \r
301          * @param classDirectory\r
302          *            クラス・ファイルを検索するディレクトリ。<br>\r
303          *            このパスが存在し、かつディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
304          * @param jarDirectory\r
305          *            jarファイルを検索するディレクトリ。<br>\r
306          *            このパスが存在し、かつディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
307          * @param resourceName\r
308          *            列挙するリソースの名前。<br>\r
309          *            nullの場合、{@link IllegalArgumentException}例外をスローします。\r
310          */\r
311         public ResourceEnumeration(File classDirectory, File jarDirectory, String resourceName) {\r
312             if (classDirectory != null && !classDirectory.isDirectory()) {\r
313                 throw new IllegalArgumentException("classDirectory is not directory.");\r
314             }\r
315             if (jarDirectory != null && !jarDirectory.isDirectory()) {\r
316                 throw new IllegalArgumentException("jarDirectory is not directory.");\r
317             }\r
318             if (resourceName == null) {\r
319                 throw new IllegalArgumentException("resourceName is null.");\r
320             }\r
321 \r
322             this.classDir = classDirectory;\r
323             this.resourceName = resourceName;\r
324 \r
325             this.jarFileList = this.getJarFileList(jarDirectory);\r
326         }\r
327 \r
328         /**\r
329          * <p>\r
330          * 列挙中に次の要素が存在するかどうかを返します。\r
331          * </p>\r
332          * \r
333          * @return 次の要素が存在する場合はtrue、存在しない場合はfalse。\r
334          */\r
335         public boolean hasMoreElements() {\r
336             try {\r
337                 URL url = this.nextElement(false);\r
338                 return (url != null);\r
339             } catch (IOException e) {\r
340                 throw new RuntimeException(e);\r
341             }\r
342         }\r
343 \r
344         /**\r
345          * <p>\r
346          * 列挙中の次の要素を返します。\r
347          * </p>\r
348          * \r
349          * @return 列挙中の次の要素。<br>\r
350          *         次の要素が存在しない場合は{@link NoSuchElementException}例外をスローします。\r
351          */\r
352         public URL nextElement() {\r
353             try {\r
354                 URL url = this.nextElement(true);\r
355                 if (url != null) {\r
356                     return url;\r
357                 } else {\r
358                     throw new NoSuchElementException();\r
359                 }\r
360             } catch (IOException e) {\r
361                 throw new RuntimeException(e);\r
362             }\r
363         }\r
364 \r
365         private URL nextElement(boolean updateIndex) throws IOException {\r
366             int curIdx = this.currentIndex;\r
367 \r
368             while (true) {\r
369                 if (curIdx == this.jarFileList.size()) {\r
370                     return null;\r
371                 }\r
372                 if (curIdx == -1) {\r
373                     URL url = this.findResourceForClassDirectory(this.classDir, this.resourceName);\r
374                     if (url != null) {\r
375                         if (updateIndex) {\r
376                             this.currentIndex = curIdx;\r
377                         }\r
378                         return url;\r
379                     }\r
380                 } else {\r
381                     File jarFile = this.jarFileList.get(curIdx);\r
382                     URL url = this.findResourceForJarFile(jarFile, this.resourceName);\r
383                     if (url != null) {\r
384                         if (updateIndex) {\r
385                             this.currentIndex = curIdx;\r
386                         }\r
387                         return url;\r
388                     }\r
389                 }\r
390                 curIdx++;\r
391             }\r
392         }\r
393 \r
394         private List<File> getJarFileList(File dir) {\r
395             List<File> jarFileList = new ArrayList<File>();\r
396 \r
397             if (dir != null) {\r
398                 File[] files = dir.listFiles();\r
399                 if (files == null) {\r
400                     return new ArrayList<File>();\r
401                 }\r
402                 for (File file : files) {\r
403                     if (file.isFile()) {\r
404                         if (file.getName().endsWith(".jar")) {\r
405                             jarFileList.add(file);\r
406                         }\r
407                     } else {\r
408                         jarFileList.addAll(this.getJarFileList(file));\r
409                     }\r
410                 }\r
411             }\r
412 \r
413             return jarFileList;\r
414         }\r
415 \r
416         private URL findResourceForClassDirectory(File classDir, String resourceName) throws MalformedURLException {\r
417             if (classDir != null) {\r
418                 return null;\r
419             }\r
420             String resName = resourceName.replace('/', File.separatorChar);\r
421             File resFile = new File(classDir, resName);\r
422             if (resFile.exists()) {\r
423                 URL url = new URL("file:/" + resFile.getAbsolutePath().replace(File.separatorChar, '/'));\r
424                 return url;\r
425             } else {\r
426                 return null;\r
427             }\r
428         }\r
429 \r
430         private URL findResourceForJarFile(File jarFile, String resourceName) throws IOException {\r
431             JarFile jar = new JarFile(jarFile);\r
432             try {\r
433                 JarEntry entry = jar.getJarEntry(resourceName);\r
434                 if (entry != null) {\r
435                     URL url = new URL("jar:file:/" + jarFile.getAbsolutePath().replace(File.separatorChar, '/') + "!/" + entry.getName());\r
436                     return url;\r
437                 } else {\r
438                     return null;\r
439                 }\r
440             } finally {\r
441                 jar.close();\r
442             }\r
443         }\r
444 \r
445     }\r
446 \r
447 }\r