org.pumpkindb.examples.todolist.Main.java Source code

Java tutorial

Introduction

Here is the source code for org.pumpkindb.examples.todolist.Main.java

Source

/**
 * Copyright (c) 2017, All Contributors (see CONTRIBUTORS file)
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.pumpkindb.examples.todolist;

import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.gui2.dialogs.MessageDialog;
import com.googlecode.lanterna.gui2.dialogs.MessageDialogBuilder;
import com.googlecode.lanterna.gui2.dialogs.TextInputDialog;
import com.googlecode.lanterna.gui2.table.Table;
import com.googlecode.lanterna.input.KeyStroke;
import com.googlecode.lanterna.screen.TerminalScreen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import com.googlecode.lanterna.terminal.Terminal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.pumpkindb.*;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

public class Main {

    static class StackCollectingProgram implements Encodable {
        private final Encodable program;

        StackCollectingProgram(Encodable program) {
            this.program = program;
        }

        @Override
        public void encode(ByteBuf buffer) {
            UUID uuid = UUID.randomUUID();
            Encodables script = new Encodables(Arrays.asList(program, new Instruction("TRY"),
                    new Instruction("STACK"), new Uuid(uuid), new Instruction("SUBSCRIBE"), new Instruction("SWAP"),
                    new Uuid(uuid), new Instruction("PUBLISH"), new Instruction("UNSUBSCRIBE")));
            script.encode(buffer);
        }
    }

    static class Item {
        private final String value;
        private final UUID uuid = UUID.randomUUID();

        Item(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }

        public UUID getUuid() {
            return uuid;
        }

    }

    // expects UUID on the stack
    static class AddTodoItem implements Encodable {
        public static final byte[] PREFIX = "AddedTodoItem".getBytes();

        @Override
        public void encode(ByteBuf buffer) {
            Encodables script = new Encodables(Arrays.asList(new Data(PREFIX),
                    new Instructions("HLC CONCAT 2DUP SWAP ASSOC HLC ROT SWAP CONCAT SWAP ASSOC")));
            script.encode(buffer);
        }
    }

    // top: value
    // 2nd: uuid
    static class ChangeTodoItem implements Encodable {
        public static final byte[] PREFIX = "ChangedTodoItem".getBytes();

        @Override
        public void encode(ByteBuf buffer) {
            Encodables script = new Encodables(Arrays.asList(new Instruction("OVER"), new Data(PREFIX),
                    new Instructions("SWAP HLC CONCAT CONCAT DUP -ROT SWAP ASSOC SWAP HLC CONCAT SWAP ASSOC")));
            script.encode(buffer);
        }
    }

    // uuid value
    static class AddNewTodoItem implements Encodable {
        @Override
        public void encode(ByteBuf buffer) {
            // uuid value
            new Instruction("OVER").encode(buffer);
            // uuid value uuid
            new AddTodoItem().encode(buffer);
            // uuid value
            new ChangeTodoItem().encode(buffer);
        }
    }

    static class DefineInstruction implements Encodable {
        private final Encodable encodable;
        private final String name;

        DefineInstruction(Encodable encodable, String name) {
            this.encodable = encodable;
            this.name = name;
        }

        @Override
        public void encode(ByteBuf buffer) {
            encodable.encode(buffer);
            new Closure(new Instruction(name)).encode(buffer);
            new Instruction("DEF").encode(buffer);
        }
    }

    static class Handler implements MessageHandler {

        private final Consumer<List<Encodable>> callback;

        Handler(Consumer<List<Encodable>> callback) {
            this.callback = callback;
        }

        @Override
        public void accept(ByteBuf byteBuf) {
            List<Encodable> encodables = new ArrayList<>();
            new BinaryParsingIterator(byteBuf).forEachRemaining(encodables::add);
            callback.accept(encodables);
        }

    }

    static class RoutingHandler implements MessageHandler {

        private final UUID uuid = UUID.randomUUID();
        private final Map<String, Handler> handlers = new HashMap<>();

        public UUID getUuid() {
            return uuid;
        }

        public void registerHandler(String key, Handler handler) {
            handlers.put(key, handler);
        }

        public void unregisterHandler(String key) {
            handlers.remove(key);
        }

        @Override
        public void accept(ByteBuf byteBuf) {
            BinaryParsingIterator iterator = new BinaryParsingIterator(byteBuf);
            if (iterator.hasNext()) {
                Data key = (Data) iterator.next();
                Handler handler = handlers.get(new String(key.getData()));
                if (handler != null) {
                    handler.accept(byteBuf);
                }
            }
        }

    }

    private static Encodable fetchTodoList(UUID uuid) {
        return new Encodables(Arrays.asList(new Uuid(uuid), new Instruction("SUBSCRIBE"),
                new Closure(new Utf8String("AddedTodoItem"), new Closure(new Utf8String("Items"), // routing key
                        new Instructions("SWAP CURSOR/VAL DUP DUP CURSOR DUP ROT"),
                        new Utf8String("ChangedTodoItem"),
                        new Instructions("SWAP CONCAT CURSOR/SEEKLAST DROP CURSOR/VAL"), new Instructions("SWAP"),
                        new Utf8String("ChangedTodoStatus"),
                        new Instructions("SWAP CONCAT CURSOR DUP ROT CURSOR/SEEKLAST"),
                        new Closure(new Instructions("CURSOR/VAL")),
                        new Closure(new Instruction("DROP"), new Bool(false)), new Instructions("IFELSE"),
                        new UnsignedInteger(4), new Instruction("WRAP"), new Uuid(uuid), new Instruction("PUBLISH"),
                        new Bool(true)), new Instruction("CURSOR/DOWHILE-PREFIXED")),
                new Instruction("READ"), new Instruction("UNSUBSCRIBE")));
    }

    private static Encodable eventLog(UUID itemUuid, UUID routingUuid) {
        return new Encodables(Arrays.asList(new Uuid(routingUuid), new Instruction("SUBSCRIBE"),
                new Closure(new Uuid(itemUuid), new Closure(new Utf8String("EventLog"), // routing key
                        new Instructions("SWAP DUP CURSOR/KEY SWAP CURSOR/VAL DUP RETR"), new UnsignedInteger(4),
                        new Instruction("WRAP"), new Uuid(routingUuid), new Instruction("PUBLISH"), new Bool(true)),
                        new Instruction("CURSOR/DOWHILE-PREFIXED")),
                new Instruction("READ"), new Instruction("UNSUBSCRIBE")));
    }

    private static DefineInstruction todoMarkDefinition() {
        return new DefineInstruction(new Closure(new Closure(
                // UUID t/f
                new Instruction("OVER"), new Utf8String("ChangedTodoStatus"),
                new Instructions("SWAP HLC CONCAT CONCAT DUP -ROT SWAP ASSOC SWAP HLC CONCAT SWAP ASSOC"),
                new Instruction("COMMIT")), new Instructions("WRITE")), "TODO/MARK");
    }

    private static DefineInstruction todoNewDefinition() {
        return new DefineInstruction(
                new Closure(new Closure(new AddNewTodoItem(), new Instruction("COMMIT")), new Instruction("WRITE")),
                "TODO/NEW");
    }

    private static boolean hasPrefix(byte[] b, String prefix) {
        ByteBuf bb = Unpooled.wrappedBuffer(b);
        return (bb.readableBytes() > prefix.length()) && (bb
                .readCharSequence(prefix.length(), Charset.defaultCharset()).toString().contentEquals(prefix));

    }

    public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {

        Terminal terminal = new DefaultTerminalFactory().createTerminal();
        TerminalScreen screen = new TerminalScreen(terminal);
        TerminalSize terminalSize = terminal.getTerminalSize();

        screen.startScreen();

        // Create gui and start gui
        MultiWindowTextGUI gui = new MultiWindowTextGUI(screen, new DefaultWindowManager(),
                new EmptySpace(TextColor.ANSI.BLUE));

        MessageDialog messageDialog = new MessageDialogBuilder().setTitle("Welcome to PumpkinDB TodoList")
                .setText("This example shows basic PumpkinDB functionality\nby storing all todo list management "
                        + "events in it\nand querying the state with PumpkinScript.\n\nEnjoy!")
                .build();

        messageDialog.showDialog(gui);

        // Create window to hold the panel
        BasicWindow window = new BasicWindow("TODO");
        window.setHints(Arrays.asList(Window.Hint.CENTERED, Window.Hint.EXPANDED));

        CheckBoxList<String> checkBoxList = new CheckBoxList<>();
        Label help = new Label(
                "[up/down] navigate [space/enter] toggle [n] new item [l] event log\n" + "[?] about [q] quit");
        Panel helpPanel = new Panel();
        helpPanel.addComponent(help);

        Panel panel = new Panel(new BorderLayout());
        panel.addComponent(checkBoxList, BorderLayout.Location.CENTER);
        panel.addComponent(helpPanel.withBorder(Borders.singleLine("Help")), BorderLayout.Location.BOTTOM);

        window.setComponent(panel);
        window.setFocusedInteractable(checkBoxList);

        List<UUID> itemMapping = new ArrayList<>();

        Handler itemListHandler = new Handler(encodables -> {

            ArrayList<Encodable> e = new ArrayList<>(encodables);

            while (!e.isEmpty()) {
                Data id = (Data) e.remove(0);
                Data text = (Data) e.remove(0);
                Data status = (Data) e.remove(0);
                boolean checked = status.getData()[0] == 1;
                checkBoxList.addItem(new String(text.getData()), checked);

                ByteBuffer bb = ByteBuffer.wrap(id.getData());
                long ms = bb.getLong();
                long ls = bb.getLong();
                itemMapping.add(new UUID(ms, ls));
            }

        });

        RoutingHandler routingHandler = new RoutingHandler();
        routingHandler.registerHandler("Items", itemListHandler);

        Client client = new Client("localhost", routingHandler);
        client.connect();

        checkBoxList.addListener((itemIndex, checked) -> {
            Encodables cmd = new Encodables(Arrays.asList(todoMarkDefinition(),
                    new Uuid(itemMapping.get(itemIndex)), new Bool(checked), new Instruction("TODO/MARK")));
            client.send(cmd);
            itemMapping.clear();
            checkBoxList.clearItems();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            client.send(fetchTodoList(routingHandler.getUuid()));
        });

        window.addWindowListener(new WindowListenerAdapter() {
            @Override
            public void onUnhandledInput(Window basePane, KeyStroke keyStroke, AtomicBoolean hasBeenHandled) {
                if (keyStroke.getCharacter() == Character.valueOf('n')) {
                    String item = TextInputDialog.showDialog(gui, "New item", "Enter your new todo list item", "");
                    if (item != null) {
                        Item i = new Item(item);
                        Encodables cmd = new Encodables(Arrays.asList(todoNewDefinition(), new Uuid(i.getUuid()),
                                new Utf8String(i.getValue()), new Instruction("TODO/NEW")));
                        itemMapping.clear();
                        checkBoxList.clearItems();
                        client.send(cmd);
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        client.send(fetchTodoList(routingHandler.getUuid()));
                    }
                }
                if (keyStroke.getCharacter() == Character.valueOf('q')) {
                    window.close();
                }

                if (keyStroke.getCharacter() == Character.valueOf('?')) {
                    BasicWindow aboutWindow = new AboutWindow(terminalSize);
                    gui.addWindowAndWait(aboutWindow);
                }

                if (keyStroke.getCharacter() == Character.valueOf('l')) {
                    BasicWindow window = new BasicWindow("Event log");
                    window.setHints(Arrays.asList(Window.Hint.CENTERED, Window.Hint.EXPANDED));

                    Panel panel = new Panel(new BorderLayout());
                    Table<String> table = new Table<>("Timestamp", "What happened");

                    routingHandler.registerHandler("EventLog", new Handler(encodables -> {
                        Data key = (Data) encodables.remove(0);
                        Data reference = (Data) encodables.remove(0);
                        Data value = (Data) encodables.remove(0);

                        // Parse key
                        ByteBuf bb = Unpooled.wrappedBuffer(key.getData());
                        bb.skipBytes(16); // uuid
                        bb.skipBytes(4); // HLC epoch
                        byte[] ts = new byte[8];
                        bb.readBytes(ts); // timestamp

                        long ms = TimeUnit.MILLISECONDS.convert(new BigInteger(ts).longValue(),
                                TimeUnit.NANOSECONDS);
                        Date date = new Date(ms);

                        // Parse reference
                        String event = "Unknown";

                        if (hasPrefix(reference.getData(), "AddedTodoItem")) {
                            event = "Item added";
                        }

                        if (hasPrefix(reference.getData(), "ChangedTodoItem")) {
                            event = "Item changed to: " + new String(value.getData());
                        }

                        if (hasPrefix(reference.getData(), "ChangedTodoStatus")) {
                            boolean done = value.getData()[0] == 1;
                            event = "Item marked as " + (done ? "done" : "not done");
                        }

                        String formattedDate = new SimpleDateFormat("EEE MMM d yyyy h:mm a", Locale.ENGLISH)
                                .format(date);

                        table.getTableModel().addRow(formattedDate, event);
                    }));

                    client.send(
                            eventLog(itemMapping.get(checkBoxList.getSelectedIndex()), routingHandler.getUuid()));

                    panel.addComponent(table, BorderLayout.Location.CENTER);
                    panel.addComponent(new Button("Close", window::close), BorderLayout.Location.BOTTOM);

                    window.setComponent(panel);
                    gui.addWindowAndWait(window);
                }
            }
        });

        //        client.send(new StackCollectingProgram(new Closure(fetchTodoList(itemListHandler))));
        client.send(fetchTodoList(routingHandler.getUuid()));

        gui.addWindowAndWait(window);

        client.shutdown();

        screen.stopScreen();
    }
}