やっぱりごりごりEntry作戦かなと思った矢先、そういえばeclipseのパッケージエクスプローラー
はjarがきれいに展開されていることに気がつき、何か神ソースがあるのではと思いeclipseのソースを
見てみることにした。
とりあえずorg.eclipse.jdt.uiのsrc.zipを展開。「Jar」の付くものを検索してみるといくつか引っかかった
が、怪しいのはorg.eclipse.jdt.internal.ui.compare.JarStructureCreator。
おそらくこれでJarの構造を構築している。
このクラスは内部実装がなく、処理はorg.eclipse.compare.ZipFileStructureCreatorに実装してある。
ということでorg.eclipse.compareのsrc.zipを解凍し、該当クラスを探すと、どうやら正解だったらしく
InputStreamから内部構造を構築していた。
で、結論から言うと、処理はゴリゴリですorz
ZipFileStructureCreatorにはZipFileとZipFolderというインナークラスがあって、それが
コンポジットになっています。
ZipEntryをループで取り出して、パスをパースしながらクラス構造に変換しています。
(パスが「/」で終わっていたらフォルダとみなし、それ以外はファイルとみなすようです)
ちなみにこのZipFileStructureCreatorはアーカイブ読み込みと同時に内部の各データも読み込んでいるようで、
テスト用に改造しwarファイルを読ませてみたところ、がっつりメモリに展開されました。
ということでeclipse大先生に見習いごりごりやるという前提で、
・warを読み込んで、パス(war内部のパス)とクラス(ArchiveFile or ArchiveFolder)のMapを作成する。 ・ArchiveFolderには、フォルダ以下のものを取れるAPIを用意する。 ・内部データを読み込むタイミングは任意に決められるようにする(初回ロード時or初回アクセス時 キャッシュするかしないか)
という感じで作ろうと思います。
これをwarのクラスローダーに入れて、WEB-INF/lib以下のものにパスを通せば、まずは動作は出来そう。
あとはgetResource、getResourceAsStreamの対応かな。
さて、休憩終了。。。本業に戻ります。
以下テストソース(eclipseありがとう)
package jartest; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class ArchiveIntrospector { public static void main(String[] args) { InputStream is = ArchiveIntrospector.class.getResourceAsStream("test.war"); ArchiveIntrospector ji = new ArchiveIntrospector(is); System.out.println(ji); } private ArchiveFolder root; public ArchiveIntrospector(InputStream is) { readArchive(is); } public ArchiveFolder getArchiveRoot() { return root; } private void readArchive(InputStream is) { ZipInputStream zip = new ZipInputStream(is); root = new ArchiveFolder(""); try { for (;;) { ZipEntry entry = zip.getNextEntry(); if (entry == null) break; ArchiveFile ze = root.createContainer(entry.getName()); //データ読み込み if (ze != null) { int length = (int) entry.getSize(); if (length >= 0) { byte[] buffer = new byte[length]; int offset = 0; do { int n = zip.read(buffer, offset, length); offset += n; length -= n; } while (length > 0); ze.setBytes(buffer); } else { byte[] buffer = new byte[1024]; int n; do { n = zip.read(buffer, 0, 1024); ze.appendBytes(buffer, n); } while (n >= 0); } } zip.closeEntry(); } } catch (IOException ex) { throw new RuntimeException(ex); } finally { try { zip.close(); } catch (IOException ex) { } } } @Override public String toString() { Object[] children = root.getChildren(); if (children == null) return ""; else { return toStringChild(children); } } private String toStringChild(Object[] children) { String line = ""; for (int i = 0; i < children.length; i++) { ArchiveResource o = (ArchiveResource) children[i]; line += o.getName() + "\n"; if (o instanceof ArchiveFolder) { ArchiveFolder f = (ArchiveFolder) o; if (f.getChildren() != null) line += toStringChild(f.getChildren()); } } return line; } public static abstract class ArchiveResource { private String name; public ArchiveResource(String name) { this.name = name; } public String getName() { return name; } } public static class ArchiveFolder extends ArchiveResource { private Map<String, ArchiveResource> children = new TreeMap<String, ArchiveResource>(); public ArchiveFolder(String name) { super(name); } public ArchiveResource[] getChildren() { return children.values().toArray(new ArchiveResource[] {}); } public ArchiveFile createContainer(String path) { String entry = path; int pos = path.indexOf('/'); if (pos < 0) pos = path.indexOf('\\'); if (pos >= 0) { entry = path.substring(0, pos); path = path.substring(pos + 1); } else if (entry.length() > 0) { ArchiveFile ze = new ArchiveFile(entry); children.put(entry, ze); return ze; } else return null; ArchiveFolder folder = null; if (children != null) { Object o = children.get(entry); if (o instanceof ArchiveFolder) folder = (ArchiveFolder) o; } if (folder == null) { if (path.length() > 0) return null; folder = new ArchiveFolder(entry); children.put(entry, folder); } return folder.createContainer(path); } } public static class ArchiveFile extends ArchiveResource { private byte[] contents; public ArchiveFile(String name) { super(name); } public Object[] getChildren() { return null; } public InputStream getContents() { if (contents == null) contents = new byte[0]; return new ByteArrayInputStream(contents); } public byte[] getBytes() { return contents; } void setBytes(byte[] buffer) { contents = buffer; } void appendBytes(byte[] buffer, int length) { if (length > 0) { int oldLen = 0; if (contents != null) oldLen = contents.length; byte[] newBuf = new byte[oldLen + length]; if (oldLen > 0) System.arraycopy(contents, 0, newBuf, 0, oldLen); System.arraycopy(buffer, 0, newBuf, oldLen, length); contents = newBuf; } } } }