co.phoenixlab.hearthstone.hearthcapturelib.HearthCaptureDumpReader.java Source code

Java tutorial

Introduction

Here is the source code for co.phoenixlab.hearthstone.hearthcapturelib.HearthCaptureDumpReader.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Vincent Zhang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package co.phoenixlab.hearthstone.hearthcapturelib;

import co.phoenixlab.hearthstone.hearthcapturelib.packets.CapturePacket;
import co.phoenixlab.hearthstone.hearthcapturelib.tcp.TCPPacket;
import co.phoenixlab.hearthstone.hearthcapturelib.util.HCapUtils;
import co.phoenixlab.hearthstone.hearthcapturelib.util.InstantTypeAdapter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;

/**
 * Reads packet dumps that were dumped by HearthCaptureDumper
 */
public class HearthCaptureDumpReader implements AutoCloseable {

    static class SignalPacket extends CapturePacket {

    }

    private static final SignalPacket SIGNAL_PACKET = new SignalPacket();

    private static int version = 1;

    private final BufferedReader reader;
    private final Gson gson;
    private CaptureQueue queue;
    private DumpedPacketQueue outQueue;
    private DumpedPacketQueue inQueue;
    private long startTime;

    public HearthCaptureDumpReader(Path dumpFile) throws IOException {
        reader = Files.newBufferedReader(dumpFile, StandardCharsets.UTF_8);
        gson = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantTypeAdapter()).create();
    }

    /**
     * Reads a dump file header, starts the async parsing, and returns the CaptureQueue that receives the read packets.
     *
     * @return A CaptureQueue that receives the packets read from disk.
     * @throws IOException If there was an error reading or if the file is invalid.
     */
    public CaptureQueue read() throws IOException {
        String line = reader.readLine();
        if (!line.startsWith("HCLDMP ")) {
            throw new IOException("Invalid HearthCaptureLib dump: Invalid magic");
        }
        line = line.substring("HCLDMP ".length()).trim();
        int vers;
        try {
            vers = Integer.parseInt(line);
        } catch (NumberFormatException nfe) {
            throw new IOException("Invalid HearthCaptureLib dump: Invalid version number: " + line);
        }
        if (vers != version) {
            throw new IOException("Unknown HCLDMP version " + vers);
        }
        line = reader.readLine();
        if (!line.startsWith("startTime ")) {
            throw new IOException("Invalid HearthCaptureLib dump: Invalid capture start timestamp");
        }
        line = line.substring("startTime ".length()).trim();
        startTime = -1L;
        try {
            startTime = Long.parseLong(line);
        } catch (NumberFormatException nfe) {
            throw new IOException("Invalid HearthCaptureLib dump: Invalid capture start timestamp " + line);
        }
        outQueue = new DumpedPacketQueue(startTime);
        inQueue = new DumpedPacketQueue(startTime);
        queue = new CaptureQueue(outQueue, inQueue);
        HearthCaptureLib.executor.execute(this::parse);
        return queue;
    }

    @SuppressWarnings("unchecked")
    private void parse() {
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                if (Thread.interrupted()) {
                    break;
                }
                CapturePacket packet = gson.fromJson(line, CapturePacket.class);
                boolean inbound = packet.isInbound();
                Class<? extends CapturePacket> clazz;
                try {
                    clazz = (Class<? extends CapturePacket>) Class.forName(packet._structName);
                } catch (ClassNotFoundException e) {
                    HCapUtils.logger.warning("Unknown type " + packet._structName);
                    continue;
                }
                packet = gson.fromJson(line, clazz);
                if (inbound) {
                    inQueue.put(packet);
                } else {
                    outQueue.put(packet);
                }
            }
        } catch (IOException e) {
            HCapUtils.logger.log(Level.WARNING, "Error reading from dump file", e);
        } catch (InterruptedException ignore) {
        } finally {
            queue.close();
        }
    }

    @Override
    public void close() throws IOException {
        reader.close();
    }

    public static void main(String[] args) {
        if (args.length > 0) {
            try {
                Path path = Paths.get(args[0]);
                try (HearthCaptureDumpReader dumpReader = new HearthCaptureDumpReader(path)) {
                    HCapUtils.logger.info("Reading dump...");
                    final CaptureQueue queue = dumpReader.read();
                    CountDownLatch latch = new CountDownLatch(2);
                    HearthCaptureLib.executor.execute(() -> {
                        try {
                            while (!queue.isClosed()) {
                                System.out.println(queue.getInboundPackets().next().toJSON());
                            }
                        } catch (InterruptedException ignored) {
                        }
                        latch.countDown();
                    });
                    HearthCaptureLib.executor.execute(() -> {
                        try {
                            while (!queue.isClosed()) {
                                System.err.println(queue.getOutboundPackets().next().toJSON());
                            }
                        } catch (InterruptedException ignored) {
                        }
                        latch.countDown();
                    });
                    latch.await();
                    HCapUtils.logger.info("Read complete!");
                }
            } catch (InvalidPathException e) {
                HCapUtils.logger.severe("Invalid path specified: " + e.getLocalizedMessage());
            } catch (IOException e) {
                HCapUtils.logger.log(Level.SEVERE, "Unable to open file for reading.", e);
                e.printStackTrace();
            } catch (InterruptedException e) {
                HCapUtils.logger.warning("Program interrupted.");
            }
        } else {
            HCapUtils.logger.severe(
                    "Usage: java -cp HearthCaptureLib.jar co.phoenixlab.hearthstone.hearthcapturelib.HearthCaptureDumpReader FILE_TO_READ_FROM");
        }
        HCapUtils.logger.info("Application terminated.");
    }

    private class DumpedPacketQueue implements PacketQueue {

        private final ArrayBlockingQueue<CapturePacket> packets;
        private final long startTime;
        private AtomicBoolean closed;

        private DumpedPacketQueue(long startTime) {
            //  Large buffer because we're reading a dump from disk, as opposed to receiving them in real time.
            packets = new ArrayBlockingQueue<>(0xFFFF);
            this.startTime = startTime;
            closed = new AtomicBoolean(false);
        }

        @Override
        public CapturePacket next() throws InterruptedException {
            if (closed.get()) {
                return null;
            }
            CapturePacket packet = packets.take();
            if (packet == SIGNAL_PACKET) {
                throw new InterruptedException();
            }
            return packet;
        }

        @Override
        public CapturePacket peek() {
            if (closed.get()) {
                return null;
            }
            return packets.peek();
        }

        @Override
        public boolean hasNext() {
            return !closed.get() && packets.peek() != null;
        }

        @Override
        public boolean isClosed() {
            return closed.get();
        }

        @Override
        public void put(TCPPacket packet) {
            throw new UnsupportedOperationException();
        }

        public void put(CapturePacket packet) throws InterruptedException {
            packets.put(packet);
        }

        @Override
        public void close() {
            closed.set(true);
            boolean cleared = false;
            while (!cleared) {
                try {
                    packets.put(SIGNAL_PACKET);
                    cleared = true;
                } catch (InterruptedException ignore) {
                }
            }
        }

        @Override
        public long getCaptureStartTime() {
            return startTime;
        }
    }
}