@SuppressWarnings("unchecked")
 private O createArchiveOutputStream(boolean overwrite) throws IOException {
   O archiveOut;
   FileOutputStream out;
   if (!file.exists() || file.length() == 0 || overwrite) {
     out = new FileOutputStream(file);
   } else {
     throw new FileNotFoundException(
         "can't open for output, check existence or access rights: " + file.getAbsolutePath());
   }
   String pathStr = path.toString();
   Set<String> streamCodecs = CompressCodecService.getCodecs();
   for (String codec : streamCodecs) {
     if (pathStr.endsWith("." + codec)) {
       archiveOut =
           (O)
               archiveService
                   .getCodec(getName())
                   .createArchiveOutputStream(codecService.getCodec(codec).encode(out));
       archiveOut.setWatcher(watcher);
       return archiveOut;
     }
   }
   archiveOut = (O) archiveService.getCodec(getName()).createArchiveOutputStream(out);
   archiveOut.setWatcher(watcher);
   return archiveOut;
 }
 @SuppressWarnings("unchecked")
 private I createArchiveInputStream() throws IOException {
   I archiveIn;
   FileInputStream in;
   if (file.isFile() && file.canRead()) {
     in = new FileInputStream(file);
   } else {
     throw new FileNotFoundException(
         "can't open for input, check existence or access rights: " + path);
   }
   String pathStr = path.toString();
   Set<String> streamCodecs = CompressCodecService.getCodecs();
   for (String codec : streamCodecs) {
     if (pathStr.endsWith("." + codec)) {
       archiveIn =
           (I)
               archiveService
                   .getCodec(getName())
                   .createArchiveInputStream(codecService.getCodec(codec).decode(in));
       archiveIn.setWatcher(watcher);
       return archiveIn;
     }
   }
   archiveIn = (I) archiveService.getCodec(getName()).createArchiveInputStream(in);
   archiveIn.setWatcher(watcher);
   return archiveIn;
 }
/** A basic archive session */
public abstract class ArchiveSession<I extends ArchiveInputStream, O extends ArchiveOutputStream>
    implements Session<ArchivePacket> {

  private static final ESLogger logger =
      ESLoggerFactory.getLogger(ArchiveSession.class.getSimpleName());

  private static final CompressCodecService codecService = CompressCodecService.getInstance();

  private static final ArchiveService archiveService = ArchiveService.getInstance();

  private boolean isOpen;

  private EnumSet<Mode> mode;

  private File file;

  private Path path;

  private I in;

  private O out;

  private BytesProgressWatcher watcher;

  private long packetCounter;

  private AtomicLong archiveCounter = new AtomicLong();

  protected ArchiveSession(BytesProgressWatcher watcher) {
    this.watcher = watcher;
    this.packetCounter = 0L;
  }

  public BytesProgressWatcher getWatcher() {
    if (watcher == null) {
      watcher = new BytesProgressWatcher(0L);
    }
    return watcher;
  }

  public abstract String getName();

  @Override
  public synchronized void open(EnumSet<Mode> mode, Path path, File file) throws IOException {
    if (isOpen) {
      return;
    }
    this.mode = mode;
    this.file = file;
    this.path = path;
    if (mode.contains(Mode.READ)) {
      this.in = createArchiveInputStream();
      this.isOpen = this.in != null;
      if (!isOpen) {
        throw new FileNotFoundException(
            "can't open for input, check existence or access rights: " + file.getAbsolutePath());
      }
    } else if (mode.contains(Mode.WRITE)) {
      this.out = createArchiveOutputStream(false);
      this.isOpen = this.out != null;
      if (!isOpen) {
        throw new FileNotFoundException(
            "can't open for output, check existence or access rights: " + file.getAbsolutePath());
      }
    } else if (mode.contains(Mode.OVERWRITE)) {
      this.out = createArchiveOutputStream(true);
      this.isOpen = this.out != null;
      if (!isOpen) {
        throw new FileNotFoundException(
            "can't open for output, check existence or access rights: " + file.getAbsolutePath());
      }
    }
  }

  @SuppressWarnings("unchecked")
  private I createArchiveInputStream() throws IOException {
    I archiveIn;
    FileInputStream in;
    if (file.isFile() && file.canRead()) {
      in = new FileInputStream(file);
    } else {
      throw new FileNotFoundException(
          "can't open for input, check existence or access rights: " + path);
    }
    String pathStr = path.toString();
    Set<String> streamCodecs = CompressCodecService.getCodecs();
    for (String codec : streamCodecs) {
      if (pathStr.endsWith("." + codec)) {
        archiveIn =
            (I)
                archiveService
                    .getCodec(getName())
                    .createArchiveInputStream(codecService.getCodec(codec).decode(in));
        archiveIn.setWatcher(watcher);
        return archiveIn;
      }
    }
    archiveIn = (I) archiveService.getCodec(getName()).createArchiveInputStream(in);
    archiveIn.setWatcher(watcher);
    return archiveIn;
  }

  @SuppressWarnings("unchecked")
  private O createArchiveOutputStream(boolean overwrite) throws IOException {
    O archiveOut;
    FileOutputStream out;
    if (!file.exists() || file.length() == 0 || overwrite) {
      out = new FileOutputStream(file);
    } else {
      throw new FileNotFoundException(
          "can't open for output, check existence or access rights: " + file.getAbsolutePath());
    }
    String pathStr = path.toString();
    Set<String> streamCodecs = CompressCodecService.getCodecs();
    for (String codec : streamCodecs) {
      if (pathStr.endsWith("." + codec)) {
        archiveOut =
            (O)
                archiveService
                    .getCodec(getName())
                    .createArchiveOutputStream(codecService.getCodec(codec).encode(out));
        archiveOut.setWatcher(watcher);
        return archiveOut;
      }
    }
    archiveOut = (O) archiveService.getCodec(getName()).createArchiveOutputStream(out);
    archiveOut.setWatcher(watcher);
    return archiveOut;
  }

  @Override
  public ArchivePacket newPacket() {
    return new ArchivePacket();
  }

  @Override
  public synchronized ArchivePacket read() throws IOException {
    if (!isOpen()) {
      throw new IOException("not open");
    }
    if (in == null) {
      throw new IOException("no input stream found");
    }
    ArchiveEntry entry = in.getNextEntry();
    if (entry == null) {
      return null;
    }
    ArchivePacket packet = newPacket();
    String name = entry.getName();
    packet.meta("name", name);
    ArchiveUtils.decodeArchiveEntryName(packet, name);
    int size = (int) entry.getEntrySize();
    if (size >= 0) {
      byte[] b = new byte[size]; // naive but fast, heap may explode
      int num = in.read(b, 0, size); // fill byte array from stream
      packet.payload(new String(b));
    } else {
      // slow copy, unknown size (zip deflate method)
      ByteArrayOutputStream b = new ByteArrayOutputStream();
      Streams.copy(in, b);
      packet.payload(new String(b.toByteArray()));
    }
    packetCounter++;
    return packet;
  }

  @Override
  @SuppressWarnings("unchecked")
  public synchronized void write(ArchivePacket packet) throws IOException {
    if (!isOpen()) {
      throw new IOException("not open");
    }
    if (out == null) {
      throw new IOException("no output stream found");
    }
    if (packet == null || packet.payload() == null) {
      throw new IOException("no payload to write for entry");
    }
    byte[] buf = packet.payload().toString().getBytes();
    String name = ArchiveUtils.encodeArchiveEntryName(packet);
    ArchiveEntry entry = out.newArchiveEntry();
    entry.setName(name);
    entry.setLastModified(new Date());
    entry.setEntrySize(buf.length);
    out.putArchiveEntry(entry);
    out.write(buf);
    out.closeArchiveEntry();
    packetCounter++;
    if (watcher.getBytesToTransfer() != 0
        && watcher.getBytesTransferred() > watcher.getBytesToTransfer()) {
      logger.debug(
          "bytes watcher: transferred = {}, rate {}",
          watcher.getBytesTransferred(),
          watcher.getRecentByteRatePerSecond());
      switchToNextArchive();
      watcher.resetWatcher();
    }
  }

  @Override
  public synchronized void close() throws IOException {
    if (!isOpen) {
      return;
    }
    if (out != null) {
      out.close();
    }
    if (in != null) {
      in.close();
    }
    this.isOpen = false;
  }

  @Override
  public long getPacketCounter() {
    return packetCounter;
  }

  @Override
  public boolean isOpen() {
    return isOpen;
  }

  /**
   * Switches to next archive stream if a certain byte limit was set
   *
   * @throws IOException
   */
  private void switchToNextArchive() throws IOException {
    close();
    String filename = file.getName();
    String prefix = Long.toString(archiveCounter.get()) + ".";
    if (filename.startsWith(prefix)) {
      filename = filename.substring(prefix.length());
    }
    filename = archiveCounter.incrementAndGet() + "." + filename;
    this.file = new File(file.getParent() + File.separator + filename);
    this.path = file.toPath();
    open(mode, path, file);
  }
}