2 package jp.sourceforge.deployer;
\r
4 import java.io.ByteArrayOutputStream;
\r
6 import java.io.IOException;
\r
7 import java.io.InputStream;
\r
8 import java.net.MalformedURLException;
\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
19 * アーカイブ・ファイル配置後のクラスやリソースを利用するクラスローダーです。
\r
22 * アーカイブ・ファイルにはクラス・ファイル、リソース、jarファイルを含めることが出来、コンストラクタでディレクトリを指定することでクラスローダーの管理下に置くことができます。例えば、classes/からクラス・ファイル、lib/からjarファイルを検索するように初期化することが出来ます。
\r
25 * ホット・デプロイを前提としているので、クラスローダーが読み込んだリソースなどを破棄する{@link #dispose()}メソッドが用意されています。例えば{@link Deployer}クラスと連携する場合、{@link DeployerListener#undeployEnd(Deployer, File)}メソッドの呼び出しでこのクラスローダーを破棄することが出来ます。
\r
26 * TODO: dispose()メソッドは実装されていません。一度管理下に置いたリソースを破棄する方法が分からないためです。
\r
31 public class DeployerClassLoader extends ClassLoader {
\r
33 private List<DeployerClassLoaderListener> listenerList = new ArrayList<DeployerClassLoaderListener>();
\r
35 private File deployDirectory;
\r
37 private String classDirectory;
\r
39 private String jarDirectory;
\r
43 * {@link DeployerClassLoader}インスタンスを初期化します。
\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
59 public DeployerClassLoader(File deployDirectory, String classDirectory, String jarDirectory, ClassLoader parent) {
\r
61 this.initialize(deployDirectory, classDirectory, jarDirectory);
\r
66 * {@link DeployerClassLoader}インスタンスを初期化します。
\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
80 public DeployerClassLoader(File deployDirectory, String classDirectory, String jarDirectory) {
\r
82 this.initialize(deployDirectory, classDirectory, jarDirectory);
\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
101 private void initialize(File deployDirectory, String classDirectory, String jarDirectory) {
\r
102 if (deployDirectory == null) {
\r
103 throw new IllegalArgumentException("deployDirectory is null.");
\r
105 if (!deployDirectory.isDirectory()) {
\r
106 throw new IllegalArgumentException("deployDirectory is not directory.");
\r
108 if (classDirectory == null) {
\r
109 throw new IllegalArgumentException("classDirectory is null.");
\r
111 if (jarDirectory == null) {
\r
112 throw new IllegalArgumentException("jarDirectory is null.");
\r
115 this.deployDirectory = deployDirectory;
\r
116 this.classDirectory = classDirectory;
\r
117 this.jarDirectory = jarDirectory;
\r
122 * クラスローダーがクラスを操作したときのイベントを受け取るリスナーを追加します。
\r
125 * このメソッドはスレッドセーフです。
\r
129 * イベントが通知されるリスナー。<br>
\r
130 * nullの場合、{@link IllegalArgumentException}例外をスローします。
\r
132 public void addListener(DeployerClassLoaderListener listener) {
\r
133 if (listener == null) {
\r
134 throw new IllegalArgumentException("listener is null.");
\r
136 synchronized (this.listenerList) {
\r
137 this.listenerList.add(listener);
\r
143 * 既に登録されているリスナーを削除します。
\r
146 * このメソッドはスレッドセーフです。
\r
152 public void removeListener(DeployerClassLoaderListener listener) {
\r
153 synchronized (this.listenerList) {
\r
154 this.listenerList.remove(listener);
\r
160 * クラスローダーが保持している全てのリソースを破棄します。
\r
162 * TODO: メソッドは機能しません。
\r
164 public void dispose() {
\r
165 // TODO 定義済みクラスをうまく解放する方法を考える。
\r
173 * このメソッドは、クラスがまだ読み込まれていないときに呼び出されます。呼び出されると、クラス・ディレクトリやjarファイルからクラスを検索し、見つかった場合、クラスとして定義します({@link #defineClass(String, byte[], int, int)}メソッドを呼び出します)。クラスが見つからなかった場合、{@link ClassNotFoundException}例外をスローします。
\r
178 * @return 見つかったクラス。
\r
179 * @throws ClassNotFoundException
\r
183 protected Class<?> findClass(String name) throws ClassNotFoundException {
\r
184 String resName = name.replace('.', '/') + ".class";
\r
185 URL url = this.findResource(resName);
\r
187 throw new ClassNotFoundException("\"" + name + "\"クラスが見つかりませんでした。");
\r
191 InputStream in = url.openStream();
\r
194 ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
\r
196 byte[] buf = new byte[1024];
\r
198 while ((len = in.read(buf)) != -1) {
\r
199 byteOut.write(buf, 0, len);
\r
205 byte[] classData = byteOut.toByteArray();
\r
206 Class<?> clazz = this.defineClass(name, classData, 0, classData.length);
\r
208 synchronized (this.listenerList) {
\r
209 for (DeployerClassLoaderListener l : this.listenerList) {
\r
210 l.findClass(clazz, url);
\r
217 } catch (IOException e) {
\r
218 throw new ClassNotFoundException("\"" + name + "\"クラスの読み込みに失敗しました。", e);
\r
227 * まずクラス・ディレクトリから、次にjarディレクトリからファイルを検索します。そして、最初に見つかったファイルのURLを返します。
\r
230 * このメソッドは{@link #findResources(String)}メソッドを呼び出し、その最初のURLを返します。このとき、{@link #findResources(String)}メソッドが{@link IOException}例外をスローする可能性があります。{@link IOException}例外がスローされた場合、nullを返します。
\r
235 * @return リソースのURL。
\r
238 protected URL findResource(String name) {
\r
239 Enumeration<URL> resources;
\r
241 resources = this.findResources(name);
\r
242 } catch (IOException e) {
\r
245 if (resources.hasMoreElements()) {
\r
246 return resources.nextElement();
\r
257 * まずクラス・ディレクトリから、次にjarディレクトリからファイルを検索します。そして、見つかった全てのファイルのURLを列挙します。例えば、"foo/bar/boo.txt"がfoo.jarとbar.jarから見つかった場合、これらのURLが列挙されます。
\r
262 * @return リソースのURLの列挙。
\r
263 * @throws IOException
\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
272 File jarDir = new File(this.deployDirectory, this.jarDirectory);
\r
273 if (!jarDir.exists()) {
\r
276 return new ResourceEnumeration(classDir, jarDir, name);
\r
286 private class ResourceEnumeration implements Enumeration<URL> {
\r
288 private String resourceName;
\r
290 private File classDir;
\r
292 private List<File> jarFileList;
\r
294 private int currentIndex = -1;
\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
309 * nullの場合、{@link IllegalArgumentException}例外をスローします。
\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
315 if (jarDirectory != null && !jarDirectory.isDirectory()) {
\r
316 throw new IllegalArgumentException("jarDirectory is not directory.");
\r
318 if (resourceName == null) {
\r
319 throw new IllegalArgumentException("resourceName is null.");
\r
322 this.classDir = classDirectory;
\r
323 this.resourceName = resourceName;
\r
325 this.jarFileList = this.getJarFileList(jarDirectory);
\r
330 * 列挙中に次の要素が存在するかどうかを返します。
\r
333 * @return 次の要素が存在する場合はtrue、存在しない場合はfalse。
\r
335 public boolean hasMoreElements() {
\r
337 URL url = this.nextElement(false);
\r
338 return (url != null);
\r
339 } catch (IOException e) {
\r
340 throw new RuntimeException(e);
\r
349 * @return 列挙中の次の要素。<br>
\r
350 * 次の要素が存在しない場合は{@link NoSuchElementException}例外をスローします。
\r
352 public URL nextElement() {
\r
354 URL url = this.nextElement(true);
\r
358 throw new NoSuchElementException();
\r
360 } catch (IOException e) {
\r
361 throw new RuntimeException(e);
\r
365 private URL nextElement(boolean updateIndex) throws IOException {
\r
366 int curIdx = this.currentIndex;
\r
369 if (curIdx == this.jarFileList.size()) {
\r
372 if (curIdx == -1) {
\r
373 URL url = this.findResourceForClassDirectory(this.classDir, this.resourceName);
\r
376 this.currentIndex = curIdx;
\r
381 File jarFile = this.jarFileList.get(curIdx);
\r
382 URL url = this.findResourceForJarFile(jarFile, this.resourceName);
\r
385 this.currentIndex = curIdx;
\r
394 private List<File> getJarFileList(File dir) {
\r
395 List<File> jarFileList = new ArrayList<File>();
\r
398 File[] files = dir.listFiles();
\r
399 if (files == null) {
\r
400 return new ArrayList<File>();
\r
402 for (File file : files) {
\r
403 if (file.isFile()) {
\r
404 if (file.getName().endsWith(".jar")) {
\r
405 jarFileList.add(file);
\r
408 jarFileList.addAll(this.getJarFileList(file));
\r
413 return jarFileList;
\r
416 private URL findResourceForClassDirectory(File classDir, String resourceName) throws MalformedURLException {
\r
417 if (classDir != null) {
\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
430 private URL findResourceForJarFile(File jarFile, String resourceName) throws IOException {
\r
431 JarFile jar = new JarFile(jarFile);
\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