eclipse大先生

やっぱりごりごり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;
      }
    }
  }
}