import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

public class TemplateMethodSample01 {
  public static void main(String[] args) {
    StrPacker packer = new SimpleStringPacker();
    packer.add("Hello");
    packer.add("World");
    /* ここで "ChibaTech" を追加する */ 
    packer.showBytes(); // データの内容を表示
    printHexDump(packer.byteArray, 16); // 16バイトごとに表示
  }

  public static void printHexDump(byte[] bytes, int bytesPerLine) {
    for (int i = 0; i < bytes.length; i += bytesPerLine) {
      System.out.printf("%04X: ", i); // オフセット表示

      // 16進表示
      for (int j = 0; j < bytesPerLine; j++) {
        int index = i + j;
        if (index < bytes.length) {
          System.out.printf("%02X ", bytes[index]);
        } else {
          System.out.print("   "); // 空白で埋める
        }
      }

      System.out.print(" | ");

      // ASCII表示
      for (int j = 0; j < bytesPerLine; j++) {
        int index = i + j;
        if (index < bytes.length) {
          byte b = bytes[index];
          char c = (b >= 32 && b <= 126) ? (char) b : '.';
          System.out.print(c);
        } else {
          System.out.print(" ");
        }
      }

      System.out.println();
    }
  }
}

abstract class StrPacker {
  ArrayList<String> strArray = new ArrayList<String>();
  byte[] byteArray;

  // Template method 引数の文字列を逐次追加する
  public final void add(String mes) {
    unpackData(); // 一旦データを解凍
    strArray.add(mes); // 文字列を追加
    packData(); // 再度データを圧縮
  }
  protected abstract void unpackData(); // 詳細はサブクラスで実装
  protected abstract void packData(); // 詳細はサブクラスで実装
  public void showBytes() {
    for (byte b : byteArray) {
      System.out.printf("%02X ", b);
    }
    System.out.println();
  }
}

class SimpleStringPacker extends StrPacker {
  @SuppressWarnings("unchecked")
  @Override
  protected void unpackData() {
    if (byteArray == null || byteArray.length == 0) {
      return; // 初回は何もしない
    }
    try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteArray))) {
      strArray = (ArrayList<String>) in.readObject();
    } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
    }
    // strArray = ZipFile.extract(byteArray);
  }

  @Override
  protected void packData() {
    try (ByteArrayOutputStream out = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(out)) {
      oos.writeObject(strArray);
      byteArray = out.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
      // byteArray = ZipFile.compress(strArray);
    }
    System.out.println("Size of byteArray: " + byteArray.length);
  }
}
