zklogtool/src/main/java/com/zklogtool/data/TransactionLogFileList.java
public interface TransactionLogFileList {

    /**
     *
     * @return First transaction log file to be processed.
     * @throws NoFileException Thrown if file is not (yet) accessible.
     */
    File getFirstTransactionLog() throws NoFileException;

    /**
     *
     * @param previousTransactionLog Transaction log file that precedes
     * file that is to be returned. 
     * @return Transaction log file that succeeds
     * <code>previousTransactionLog</code> transaction log file.
     * @throws NoFileException Thrown if file is not (yet) accessible.
     */
    File getNextTransactionLog(File previousTransactionLog) throws NoFileException;

}
zklogtool/src/main/java/com/zklogtool/printer/DataDecoder.java
public interface DataDecoder {
   
    /**
     *
     * @param data
     * @return
     */
    public String decode(byte[] data);
    
}
zklogtool/src/main/java/com/zklogtool/util/PropertiesReader.java
public class PropertiesReader {

    File propertiesFile;
    Properties prop;

    /**
     *
     * @param propertiesFile Zookeeper configuration file.
     * @throws IOException Thrown if there is a problem with reading
     * <code>propertiesFile</code>.
     */
    public PropertiesReader(File propertiesFile) throws IOException {

        this.propertiesFile = propertiesFile;

        prop = new Properties();
        prop.load(new FileInputStream(propertiesFile));

    }

    /**
     *
     * @return Directory where transaction log files are stored.
     */
    public String getTransactionLogDir() {

        if (prop.containsKey("dataLogDir")) {
            return prop.getProperty("dataLogDir") + "/version-2";
        } else if (prop.containsKey("dataDir")) {
            return prop.getProperty("dataDir") + "/version-2";
        } else {
            return null;
        }

    }

    /**
     *
     * @return Directory where snapshot files are stored.
     */
    public String getSnapshotDir() {

        if (prop.containsKey("dataDir")) {
            return prop.getProperty("dataDir") + "/version-2";
        } else {
            return null;
        }

    }

    public File getPropertiesFile() {
        return propertiesFile;
    }

}
zklogtool/src/main/java/com/zklogtool/data/Transaction.java
public class Transaction {

    private TxnHeader txnHeader;
    private Record txnRecord;

    public Transaction(TxnHeader txnHeader, Record txnRecord) {
        super();
        this.txnHeader = txnHeader;
        this.txnRecord = txnRecord;
    }

    public TxnHeader getTxnHeader() {
        return txnHeader;
    }

    public void setTxnHeader(TxnHeader txnHeader) {
        this.txnHeader = txnHeader;
    }

    public Record getTxnRecord() {
        return txnRecord;
    }

    public void setTxnRecord(Record txnRecord) {
        this.txnRecord = txnRecord;
    }

}
zklogtool/src/main/java/com/zklogtool/data/TransactionState.java
public enum TransactionState {

    /**
     * Transaction is completely written and CRC is successfully validated.
     */
    OK,
    /**
     * Transaction is not written at all.
     */
    EMPTY,
    /**
     * Part of transaction is written.
     */
    INCOMPLETE,
    /**
     * Part or entire transaction is written but CRC validation has failed.
     */
    CORRUPTION
}
zklogtool/src/main/java/com/zklogtool/data/TransactionIterator.java
public interface TransactionIterator extends Iterator<Transaction> {

    /**
     *
     * @return <code>TransactionState</code> of the next
     * <code>Transaction</code>.
     */
    public TransactionState nextTransactionState();

}
zklogtool/src/main/java/com/zklogtool/reader/SnapshotFileReader.java
public class SnapshotFileReader {

    static private Logger logger = getLogger(SnapshotFileReader.class);

    public final static int SNAP_MAGIC = wrap("ZKSN".getBytes()).getInt();

    final File snapshotFile;
    final long TS;

    /**
     *
     * @param snapshotFile Zookeeper snapshot file.
     * @param TS Zxid of last transaction completely committed before snapshot
     * took place. It is written in snapshot filename.
     */
    public SnapshotFileReader(File snapshotFile, long TS) {
        this.snapshotFile = snapshotFile;
        this.TS = TS;
    }

    public File getSnapshotFile() {
        return snapshotFile;
    }

    /**
     * Reads snapshot file and returns <code>DataState</code> based on it.
     *
     * @return Returns <code>DataState</code> based only on snapshot file.
     * @throws CRCValidationException Thrown if CRC validation failed. Snapshot
     * is probably corrupted.
     * @throws IOException Thrown if there is a problem with reading snapshot
     * file.
     */
    public DataState readFuzzySnapshot() throws CRCValidationException, IOException {

        DataTree dt = new DataTree();
        Map<Long, Integer> sessions = new HashMap<Long, Integer>();

        InputStream snapIS = null;
        CheckedInputStream crcIn = null;

        try {
            logger.info("Reading snapshot " + snapshotFile);
            snapIS = new BufferedInputStream(new FileInputStream(snapshotFile));
            crcIn = new CheckedInputStream(snapIS, new Adler32());
            InputArchive ia = getArchive(crcIn);

            FileHeader header = new FileHeader();
            header.deserialize(ia, "fileheader");
            if (header.getMagic() != SNAP_MAGIC) {
                throw new IOException("Mismatching magic headers "
                        + header.getMagic()
                        + " !=  " + SNAP_MAGIC);
            }

            int count = ia.readInt("count");

            while (count > 0) {
                long id = ia.readLong("id");
                int to = ia.readInt("timeout");
                sessions.put(id, to);
                count--;
            }

            dt.deserialize(ia, "tree");

            long checkSum = crcIn.getChecksum().getValue();
            long val = ia.readLong("val");
            if (val != checkSum) {
                throw new CRCValidationException("CRC corruption in snapshot");
            }

        } catch (IOException e) {
            logger.warn("Problem reading snap file " + snapshotFile, e);

            throw new IOException(e);

        } finally {
            if (snapIS != null) {
                snapIS.close();
            }
            if (crcIn != null) {
                crcIn.close();
            }
        }

        long lastZxid = -1;

        DataState dataState = new DataState(dt, sessions, lastZxid);

        Iterator<Entry<String, DataNode>> it = dataState.getNodes().entrySet().iterator();
        while (it.hasNext()) {

            Entry<String, DataNode> e = it.next();

            long tempZxid = e.getValue().stat.getCzxid();

            if (tempZxid > lastZxid) {
                lastZxid = tempZxid;
            }

            tempZxid = e.getValue().stat.getPzxid();
            if (tempZxid > lastZxid) {
                lastZxid = tempZxid;
            }

            tempZxid = e.getValue().stat.getMzxid();
            if (tempZxid > lastZxid) {
                lastZxid = tempZxid;
            }

        }

        dataState.setLastZxid(lastZxid);

        return dataState;

    }

    /**
     * Reads snapshot file and applies transactions to ensure that returned
     * <code>DataState</code> is not fuzzy.
     *
     * @param transactionIterator Transaction iterator of transactions that are
     * to be applied if needed to ensure returned <code>DataState</code> is not
     * fuzzy.
     * @return <code>DataState</code> that is not fuzzy.
     * @throws CRCValidationException Thrown if CRC validation failed, snapshot
     * is probably corrupted.
     * @throws Exception Thrown if there is a problem while applying
     * transactions.
     * @throws IOException Thrown if there is a problem with reading snapshot
     * file.
     */
    public DataState restoreDataState(TransactionIterator transactionIterator) throws CRCValidationException, IOException, Exception {

        DataState fuzzy = readFuzzySnapshot();
        long lastZxid = fuzzy.getLastZxid();
        long currentZxid = TS + 1;
        Transaction t;

        // rewind iterator to right zxid
        do {

            switch (transactionIterator.nextTransactionState()) {

                case OK:
                    t = transactionIterator.next();
                    break;
                case EMPTY:
                    throw new Exception("No tranasction");
                default:
                    throw new Exception("Problem while reading transactions");

            }

        } while (t.getTxnHeader().getZxid() <= TS);

        //apply all transactions in between 
        //there should be a check if there are any transactions in between, if not sessions may not be correct
        logger.debug("Current zxid: " + currentZxid);
        logger.debug("Last zxid: " + lastZxid);
        logger.debug("Current txn zxid: " + t.getTxnHeader().getZxid());

        while (currentZxid <= lastZxid) {

            //check ordering
            if (t.getTxnHeader().getZxid() != currentZxid) {
                throw new Exception("Transactions not in order");
            }

            //apply transaction
            fuzzy.processTransaction(t);

            //read next
            switch (transactionIterator.nextTransactionState()) {

                case OK:
                    t = transactionIterator.next();
                    break;
                case EMPTY:
                    throw new Exception("No tranasction");
                default:
                    throw new Exception("Problem while reading transactions");

            }

            //set next expected transaction
            currentZxid++;

        }

        return fuzzy;

    }

    /*
    
     Uses reflection to construct DataState because it has only private constructor
     DataState has only private constructor because API users should never try to
     initialize their own instance
    
     Only way to get DataState should be using restoreDataState and readFuzzySnapshot 
     methods of this class
    
     */
    private DataState getDataState() {

        try {

            //create data tree
            DataTree dataTree = new DataTree();

            //inject logger
            Field field = dataTree.getClass().getDeclaredField("LOG");
            field.setAccessible(true);
            //field.set(TS, field);

            //create data state
            Constructor ctor = DataState.class.getDeclaredConstructors()[0];

            ctor.setAccessible(true);
            DataState dataState = (DataState) ctor.newInstance(null, null, null);

            return dataState;

        } catch (Exception x) {
            //
        }

        return null;

    }

}
zklogtool/src/main/java/com/zklogtool/util/DataDirHelper.java
public class DataDirHelper {

    private final File transactionLogDir;
    private final File snapshotDir;
    
    FilenameFilter snapFilter = new FilenameFilter(){

            @Override
            public boolean accept(File file, String name) {
                if(name.startsWith("snapshot"))
                    return true;
                else
                    return false;
                
            }      
        
        };
    
    
    FilenameFilter logFilter = new FilenameFilter(){

            @Override
            public boolean accept(File file, String name) {
                if(name.startsWith("log"))
                    return true;
                else
                    return false;
                
            }      
        
        };
    
    Comparator<File> comparator = new Comparator<File>(){

        @Override
        public int compare(File t, File t1) {
            
            long first = getZxidFromName(t.getName());
            long second = getZxidFromName(t1.getName());
            
            if(first > second)
                return 1;
            else if(first == second)
                return 0;
            else
                return -1;     
            
        }        
    
    };

    /**
     *
     * @param transactionLogDir Directory where transaction log files are stored.
     * @param snapshotDir Directory where snapshot files are stored.
     */
    public DataDirHelper(File transactionLogDir, File snapshotDir) {
        this.transactionLogDir = transactionLogDir;
        this.snapshotDir = snapshotDir;
        
    }

    /**
     *
     * @return Lexicographically ordered list of snapshot files.
     */
    public List<File> getSortedSnapshotList() {
              
        List<File> files = new ArrayList<>();
        
        for(File f : snapshotDir.listFiles(snapFilter)){    
            files.add(f);
        }
        
        sort(files,comparator);
        
        return files;
    }

    /**
     *
     * @return Lexicographically ordered list of transaction log files.
     */
    public List<File> getSortedLogList() {  

        List<File> files = new ArrayList<>();

        for(File f : transactionLogDir.listFiles(logFilter)){    
            files.add(f);
        }
     
        sort(files,comparator);
        
        return files;
    }

}
zklogtool/src/main/java/com/zklogtool/util/Util.java
public static long getZxidFromName(String name) {
    long zxid = -1;
    String nameParts[] = name.split("\\.");
    if (nameParts.length == 2 && (nameParts[0].equals("snapshot") || nameParts[0].equals("log"))) {
        try {
            zxid = parseLong(nameParts[1], 16);
        } catch (NumberFormatException e) {
        }
    }
    return zxid;
}
zklogtool/src/main/java/com/zklogtool/data/DataDirTransactionLogFileList.java
public class DataDirTransactionLogFileList implements TransactionLogFileList {

    private File dataDir;
    private DataDirHelper dataDirHelper;

    /**
     *
     * @param dataDir Directory where transaction log files are stored.
     */
    public DataDirTransactionLogFileList(File dataDir) {
        this.dataDir = dataDir;
        dataDirHelper = new DataDirHelper(dataDir, null);
    }

    /**
     * Returns chronologically first transaction log file from directory
     * injected in constructor. That is file that has lowest zxid in filename.
     *
     * @return Chronologically first transaction log file from directory
     * injected in constructor.
     * @throws NoFileException Thrown if there are no transaction log files at
     * all in directory injected in constructor.
     */
    @Override
    public File getFirstTransactionLog() throws NoFileException {

        List<File> transactionLogs = dataDirHelper.getSortedLogList();

        if (!transactionLogs.isEmpty()) {
            return transactionLogs.get(0);
        } else {
            throw new NoFileException("No transaction log found in " + dataDir.getAbsolutePath());
        }

    }

    /**
     * Returns transaction log file that chronologically succeeds
     * <code>previousTransactionLog</code>. That is file with next lowest zxid
     * number in name.
     *
     * @param previousTransactionLog Transaction log file that chronologically
     * precedes transaction log that is to be returned.
     * @return Transaction log file that chronologically succeeds
     * <code>previousTransactionLog</code>.
     * @throws NoFileException Thrown if there is no transaction log file with
     * higher zxid number in directory injected in constructor.
     */
    @Override
    public File getNextTransactionLog(File previousTransactionLog) throws NoFileException {

        List<File> transactionLogs = dataDirHelper.getSortedLogList();

        for (int i = 0; i < transactionLogs.size() - 1; i++) {

            if (previousTransactionLog.getAbsolutePath().contentEquals(transactionLogs.get(i).getAbsolutePath())) {
                return transactionLogs.get(i + 1);
            }

        }

        throw new NoFileException("No transaction log found in " + dataDir.getAbsolutePath() 
                + "that succeeds" + previousTransactionLog.toString());

    }

}
zklogtool/src/main/java/com/zklogtool/reader/TransactionLogReaderFactory.java
public class TransactionLogReaderFactory {

    public TransactionLogFileReader getReader(File transactionLog) throws FileNotFoundException, IOException {

        return new TransactionLogFileReader(transactionLog);

    }

}
zklogtool/src/main/java/com/zklogtool/data/DataState.java
public final class DataState {

    private DataTree dt;
    private Map<Long, Integer> sessions;
    private boolean fuzzy;

    /**
     *
     * @param dt Zookeeper <code>DataTree</code> structure.
     * @param sessions <code>Map</code> of session identifiers and session
     * timeouts.
     * @param lastZxid Zxid of last committed transaction.
     */
    public DataState(DataTree dt, Map<Long, Integer> sessions, long lastZxid) {

        this.dt = dt;
        this.sessions = sessions;
        setLastZxid(lastZxid);
    }

    /**
     *
     * @return <code>Map</code> of session identifiers and session timeouts.
     */
    public Map<Long, Integer> getSessions() {
        return sessions;
    }

    /**
     *
     * @param sessions <code>Map</code> of session identifiers and session
     * timeouts.
     */
    public void setSessions(Map<Long, Integer> sessions) {
        this.sessions = sessions;
    }

    /**
     *
     * @return Zxid of last committed transaction.
     */
    public long getLastZxid() {
        //return lastZxid;
        return dt.lastProcessedZxid;
    }

    /**
     *
     * @param lastZxid Zxid of last committed transaction.
     */
    public void setLastZxid(long lastZxid) {
        //this.lastZxid = lastZxid;
        dt.lastProcessedZxid = lastZxid;
    }

    //this should return something meaningfull
    /**
     * Applies transaction to this <code>DataState</code>. Zxid of transaction
     * <code>t</code> is not checked, and it is possible to apply out of order
     * transactions using this method. User should take care about consistency.
     *
     * @param t Transaction to be applied.
     */
    public void processTransaction(Transaction t) {

        TxnHeader hdr = t.getTxnHeader();
        Record txn = t.getTxnRecord();

        //there should be a check for put and remove operations
        switch (hdr.getType()) {
            case ZooDefs.OpCode.createSession:
                sessions.put(hdr.getClientId(), ((CreateSessionTxn) txn).getTimeOut());
                break;
            case ZooDefs.OpCode.closeSession:
                sessions.remove(hdr.getClientId());
                break;
        }

        dt.processTxn(hdr, txn);

    }

    
    /**
     * Returns <code>Set</code> of children names of znode <code>path</code>.
     * 
     * @param path Path of parent znode.
     * @return <code>Set</code> of children names of znode <code>path</code>.
     */
    public Set<String> getChildren(String path) {

        return getNode(path).getChildren();

    }

    /**
     * 
     * @return Number of persistent znodes.
     */
    public int getNodeCount() {
        return getNodes().size();
    }

    /**
     *
     * @return Number of ephemeral znodes.
     */
    public int getEphemeralsCount() {
        return getEphemerals().size();
    }

    /**
     *
     * @return <code>Map</code> of persistent znode names and <code>DataNode</code>s.
     */
    public Map<String, DataNode> getNodes() {

        try {

            Field field = dt.getClass().getDeclaredField("nodes");
            field.setAccessible(true);
            Object value = field.get(dt);
            return (Map<String, DataNode>) value;

        } catch (Exception ex) {

            //this should never happen
            return null;

        }

    }

    /**
     *
     * @return <code>Map</code> of ephemeral znode names and <code>DataNode</code>s.
     */
    public Map<Long, HashSet<String>> getEphemerals() {

        try {

            Field field = dt.getClass().getDeclaredField("ephemerals");
            field.setAccessible(true);
            Object value = field.get(dt);

            return (Map<Long, HashSet<String>>) value;

        } catch (Exception ex) {

            //this should never happen
            return null;

        }

    }

    /**
     * Returns <code>DataNode</code> for znode <code>path</code>.
     * 
     * @param path Full name of znode.
     * @return <code>DataNode</code> structure of <code>path</code> znode.
     */
    public DataNode getNode(String path) {
        return getNodes().get(path);
    }

    /**
     * Returns Zookeeper <code>List&lt;ACL&gt;</code> of <code>dataNode</code>.
     * @param dataNode <code>DataNode</code> for which ACL list is returned.
     * @return Zookeeper <code>List\<ACL\></code> of <code>dataNode</code>.
     */
    public List<ACL> getACL(DataNode dataNode) {

        try {

            Field field = dt.getClass().getDeclaredField("longKeyMap");
            field.setAccessible(true);
            Object value = field.get(dt);
            Map<Long, List<ACL>> longKeyMap = (Map<Long, List<ACL>>) value;

            Field field2 = DataNode.class.getDeclaredField("acl");
            field2.setAccessible(true);
            Object value2 = field2.get(dataNode);
            Long acl = (Long) value2;

            return longKeyMap.get(acl.longValue());

        } catch (Exception ex) {

            //this should never happen
            return null;

        }

    }

}
zklogtool/src/main/java/com/zklogtool/data/TransactionLog.java
public class TransactionLog implements Iterable<Transaction> {

    final TransactionLogFileList transactionLogList;
    final TransactionLogReaderFactory factory;

    /**
     * Constructs <code>TransactionLog</code> that provides iterator over
     * <code>Transactions</code> in one transaction log file
     * <code>transactionLogFile</code>.
     *
     * @param transactionLogFile Transaction log file to iterate over.
     * @param factory <code>TransactionLogReaderFactory</code> that provides
     * <code>TransactionLogFileReader</code> to be used.
     */
    public TransactionLog(final File transactionLogFile, TransactionLogReaderFactory factory) {

        this.transactionLogList = new TransactionLogFileList() {

            @Override
            public File getFirstTransactionLog() throws NoFileException {
                return transactionLogFile;
            }

            @Override
            public File getNextTransactionLog(File previousTransactionLog) throws NoFileException {
                throw new NoFileException("");
            }

        };

        this.factory = factory;

    }

    /**
     * Constructs <code>TransactionLog</code> that provides iterator over
     * <code>Transactions</code> written in multiple transaction log files.
     * Transaction log files are provided by
     * <code>transactionLogFileList</code>.
     *
     * @param transactionLogFileList Provides transaction log files to iterate
     * over.
     * @param factory <code>TransactionLogReaderFactory</code> that provides
     * <code>TransactionLogFileReader</code> to be used.
     */
    public TransactionLog(TransactionLogFileList transactionLogFileList, TransactionLogReaderFactory factory) {

        this.transactionLogList = transactionLogFileList;
        this.factory = factory;

    }

    @Override
    public TransactionIterator iterator() {

        return new TransactionLogIterator(transactionLogList, factory);
    }

    /**
     * Implements <code>TransactionIterator</code>. Remove operation is not
     * supported.
     *
     */
    public class TransactionLogIterator implements TransactionIterator {

        private TransactionLogFileReader reader;
        private TransactionLogFileList transactionLogList;

        private Transaction t;
        private TransactionState s;

        private TransactionLogIterator(TransactionLogFileList transactionLogFileList, TransactionLogReaderFactory factory) {

            this.transactionLogList = transactionLogFileList;

            loadNextTransaction();

        }

        @Override
        public boolean hasNext() {

            if (nextTransactionState() == TransactionState.OK) {
                return true;
            } else {
                return false;
            }

        }

        @Override
        public Transaction next() {

            if (hasNext()) {

                Transaction temp = t;

                loadNextTransaction();

                return temp;

            } else {
                throw new NoSuchElementException("No such element");
            }

        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Not supported");
        }

        /**
         *
         * @return TransactionState of the next <code>Transaction</code> in
         * iteration.
         */
        @Override
        public TransactionState nextTransactionState() {

            if (s != TransactionState.OK) {
                loadNextTransaction();
            }

            return s;
        }

        private void loadNextTransaction() {

            try {

                if (reader == null) {

                    reader = factory.getReader(transactionLogList.getFirstTransactionLog());

                }

                t = reader.getNextTransaction();

                if (t != null) {
                    s = TransactionState.OK;
                } else {
                    s = TransactionState.EMPTY;

                    try {

                        reader = factory.getReader(transactionLogList.getNextTransactionLog(reader.getTransactionLogFile()));

                    } catch (NoFileException ex) {
                        // this is ok, there is no next file so default behaviour is to stick with current file
                    }
                }

            } catch (IncompleteTransactionException ex) {

                s = TransactionState.INCOMPLETE;

            } catch (CRCValidationException ex) {

                s = TransactionState.CORRUPTION;

            } catch (IOException ex) {

                s = TransactionState.CORRUPTION;

            } catch (NoFileException ex) {

                s = TransactionState.EMPTY;

            }

        }

    }

}
zklogtool/src/main/java/com/zklogtool/printer/DataNodePrinter.java
public class DataNodePrinter {

    StringBuilder sb;
    DataDecoder dd;

    int indentation;

    /**
     *
     * @param sb <code>StringBuilder</code> to which information is appended.
     * @param dd Decoder used to convert DataNode data byte array to
     * <code>String</code>.
     */
    public DataNodePrinter(StringBuilder sb, DataDecoder dd) {

        this.sb = sb;
        this.dd = dd;

        indentation = 0;
    }

    private void println(String s) {
        for (int i = 0; i < indentation; i++) {
            sb.append("\t");
        }
        sb.append(s).append(lineSeparator());
    }

    /**
     *
     * @param dataNode <code>DataNode</code> to print information from.
     * @param dataState <code>DataState</code> is needed because it holds ACL.
     */
    public void printDataNode(DataNode dataNode, DataState dataState) {

        println("cZxid:\t\t" + Util.longToHexString(dataNode.stat.getCzxid()));
        println("mZxid:\t\t" + Util.longToHexString(dataNode.stat.getMzxid()));
        println("ctime:\t\t" + new Date(dataNode.stat.getCtime()));
        println("mtime:\t\t" + new Date(dataNode.stat.getMtime()));
        println("version:\t" + dataNode.stat.getVersion());
        println("cversion:\t" + dataNode.stat.getCversion());
        println("aversion:\t" + dataNode.stat.getAversion());
        println("ephemeralOwner:\t" + Util.longToHexString(dataNode.stat.getEphemeralOwner()));
        printACL(dataState.getACL(dataNode));

        byte[] data = readData(dataNode);

        if (data != null) {
            println("data:\t\t" + dd.decode(data));
        } else {
            println("data:\t\tnull");
        }

    }

    private void printACL(List<ACL> acl) {

        println("ACL:");

        indentation += 2;

        //special paths have no ACL
        if (acl != null) {
            for (ACL a : acl) {

                String perms = getACLString(a.getPerms());
                String id = a.getId().getId();
                String scheme = a.getId().getScheme();

                println(scheme + ":" + id + "  " + perms);
            }
        } else {
            println("null");
        }

        indentation -= 2;

    }

}
zklogtool/src/main/java/com/zklogtool/printer/UnicodeDecoder.java
public class UnicodeDecoder implements DataDecoder {

    @Override
    public String decode(byte[] data) {

        //maybe should check charset before 
        return new String(data);
    }

}


Question: import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.zklogtool.data.DataDirTransactionLogFileList;
import com.zklogtool.data.DataState;
import com.zklogtool.data.Transaction;
import com.zklogtool.data.TransactionIterator;
import com.zklogtool.data.TransactionLog;
import com.zklogtool.data.TransactionLogFileList;
import com.zklogtool.data.TransactionState;
import com.zklogtool.printer.DataDecoder;
import com.zklogtool.printer.DataNodePrinter;
import com.zklogtool.printer.UnicodeDecoder;
import com.zklogtool.reader.SnapshotFileReader;
import com.zklogtool.reader.TransactionLogReaderFactory;
import com.zklogtool.util.DataDirHelper;
import com.zklogtool.util.PropertiesReader;
import static com.zklogtool.util.Util.getZxidFromName;
import java.io.File;
import java.io.IOException;
import static java.lang.Long.parseLong;
import static java.lang.System.exit;
import static java.text.Collator.getInstance;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.apache.zookeeper.server.DataNode;
/* 
 * Copyright 2014 Alen Caljkusic.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zklogtool.cli;


/**
 * Represents <b>snapshot</b> command and holds logic for <b>snapshot</b>
 * command execution. It is also used by jCommander for command parameters.
 * <br>
 * Snapshot command can printout contents of single Zookeeper snapshot file.
 * Snapshot file is taken lazily so it may not represent data tree at any point
 * of time. Snapshot command can reconstruct Zookeeper data tree after a certain
 * zxid by taking one of the snapshots and applying transactions from
 * transaction log files to it. Reconstructed data tree is not fuzzy and it
 * corresponds to Zookeeper data tree at some point in time. To reconstruct data
 * tree one must provide <code>Arguments.DATA_DIR</code> argument.
 *
 */
@Parameters(commandDescription = "Display zookeeper data tree at certain time")
public class CommandSnapshot {

    /**
     * Zxid up to which data tree should be restored. Strings first and last can
     * also be used. In that case, first or last data tree state that it can be
     * reconstructed from snapshots and transaction logs will get displayed.
     * Default value is last.
     *
     */
    @Parameter(names = Arguments.ZXID, description = "Hex value of last commited zxid. Strings first and last can also be used")
    public String zxid = "last";

    /**
     * Directory where snapshot and transaction log files are stored. If
     * snapshot and transaction log files are not stored in same directory use
     * this option to point to directory where snapshot files are stored.
     *
     */
    @Parameter(names = Arguments.DATA_DIR, description = "Zookeeper data direcory path")
    public String dataDir;

    /**
     * Directory where transaction log files are stored. Zookeeper can be
     * configured to store transaction log files and snapshots in different
     * directories. In that case use this option to point to directory where
     * transaction log files are stored.
     *
     */
    @Parameter(names = Arguments.DATA_LOG_DIR, description = "Zookeeper log direcory path. If not provided dataDir is used")
    public String dataLogDir;

    /**
     * Zookeeper snapshot file. Snapshot file is taken lazily so it may not
     * represent data tree at any point of time. Therefor it is called fuzzy
     * snapshot.
     *
     */
    @Parameter(names = Arguments.SNAP_FILE, description = "Zookeeper snapshot file. Data is fuzzy.")
    public String snapFile;

    /**
     * Zookeeper configuration file. Zookeeper configuration file tells
     * zookeeper where to store transaction log files and snapshot files.
     * zklogtool can read locations from configuration file.
     *
     */
    @Parameter(names = Arguments.PROPERTIES_FILE, description = "Zookeeper configuration file path")
    public String propertiesFile;

    /**
     * Decoder that converts znodes byte array to <code>String</code> that can
     * be printed to output. Znode holds data in form of a byte array. That byte
     * array must be decoded in something that can be printed to output in order
     * to display it.
     *
     */
    @Parameter(names = Arguments.DATA_DECODER, description = "Decoder used to display znode's data byte array")
    public String dataDecoder = "UnicodeDecoder";

    /**
     * Holds logic for <b>snapshot</b> command execution.
     *
     */
    public void execute() {

        final StringBuilder print = new StringBuilder();
        DataDecoder decoder = null;
        DataState dataState = null;

        if (dataDecoder.contentEquals("UnicodeDecoder")) {
            decoder = new UnicodeDecoder();
        } else {

            System.err.println("Decoder not recognized");
            exit(1);
        }

        final DataNodePrinter printer = new DataNodePrinter(print, decoder);

        if (snapFile != null) {

            File snapshotFile = new File(snapFile);
            if (!checkFileValid(snapshotFile)) {
                exit(1);
            }

            SnapshotFileReader reader = new SnapshotFileReader(snapshotFile, 0);
            try {
                dataState = reader.readFuzzySnapshot();
            } catch (IOException ex) {
                System.err.println("Problem while reading file or corruption: " + snapshotFile.getAbsolutePath());
                exit(1);
            }

        } else {

            TransactionLog transactionLog = null;
            TransactionLogReaderFactory factory = new TransactionLogReaderFactory();
            File transactionLogDir = null;
            File snapshotDir = null;
            long zxidLong = 0;

            if (zxid != null && !(zxid.contentEquals("last") || zxid.contentEquals("first"))) {
                
                if(zxid.startsWith("0x")){
                    zxidLong = parseLong(zxid.substring(2),16);
                }else{
                    zxidLong = parseLong(zxid);
                }
                
            }

            if (dataDir != null) {
                transactionLogDir = new File(dataDir);
                snapshotDir = new File(dataDir);
            }
            if (dataLogDir != null) {
                transactionLogDir = new File(dataLogDir);
            }
            if (propertiesFile != null) {

                File properties = new File(propertiesFile);
                if (!checkFileValid(properties)) {
                    exit(1);
                }

                PropertiesReader propertiesReader = null;

                try {
                    propertiesReader = new PropertiesReader(properties);
                } catch (IOException e) {
                    System.err.println("Problem with reading properties file: " + properties.getAbsolutePath());
                    exit(1);
                }

                String transactionLogDirPath = propertiesReader.getTransactionLogDir();
                String snapshotDirPath = propertiesReader.getSnapshotDir();

                if (transactionLogDirPath == null || snapshotDirPath == null) {
                    System.err.println("Problem in properties file: " + properties.getAbsolutePath());
                    exit(1);
                }

                transactionLogDir = new File(transactionLogDirPath);
                snapshotDir = new File(snapshotDirPath);

            }

            if (!checkDirectoryValid(transactionLogDir)) {
                exit(1);
            }

            if (!checkDirectoryValid(snapshotDir)) {
                exit(1);
            }

            DataDirHelper dataDirHelper = new DataDirHelper(transactionLogDir, snapshotDir);
            List<File> snapshots = dataDirHelper.getSortedSnapshotList();
            TransactionLogFileList l = new DataDirTransactionLogFileList(transactionLogDir);
            transactionLog = new TransactionLog(l, factory);

            if (snapshots.isEmpty()) {
                System.err.println("No snapshot files found");
                exit(1);
            }

            //determine what snapshot file to read
            File snapFile = null;

            if (zxid.contentEquals("first")) {
                snapFile = snapshots.get(0);
            } else if (zxid.contentEquals("last")) {
                if (snapshots.size() >= 2) {
                    snapFile = snapshots.get(snapshots.size() - 2);
                } else {
                    snapFile = snapshots.get(0);
                }
            } else {

                int i = snapshots.size() - 1;
                while (i >= 0) {

Answer:                     long snapZxid = getZxidFromName(snapshots.get(i).getName());