Store Engine

B+Tree

B+Tree Node Classes

B+Tree Base Node

    public BNode(int order) {
        this.order = order;
        this.keys = new LimitedArrayList<>(order);
    }

B+Tree Leaf Node

    /**
     * Leaf node for B+Tree
     * leaf node has prev and next connections in B+Tree
     * @param order
     */
    public BLeafNode(int order) {
        super(order);
        this.prev = null;
        this.next = null;
    }

B+Tree Tree Node (Non-Leaf Node)

    public BTreeNode(int order) {
        super(order);
        this.children = new LimitedArrayList<>(order + 1);
    }

B+Tree Insert

B+Tree Leaf Node Insert

    /**
     * Adds a key to the node. Splitting if necessary.
     *
     * @param key to be inserted.
     */
    @Override
    public void insert(T key, List<BNode<T>> path) {
        // Ensure input is not null.
        if (key == null)
            throw new IllegalArgumentException("Input cannot be null");

        path.add(this);

        for (int i = 0; i < this.keys.size(); i++) {
            if (key.compareTo(this.keys.get(i)) <= 0) {
                this.keys.add(i, key);
                return;
            }
        }
        this.keys.add(key);
    }

B+Tree Tree Node Insert

    /**
     * Adds a key to the node. Splitting if necessary.
     *
     * @param key to be inserted.
     */
    @Override
    public void insert(T key, List<BNode<T>> path) {
        // Ensure input is not null.
        if (key == null)
            throw new IllegalArgumentException("Input cannot be null");

        path.add(this);

        // non-leaf node does not insert, pass to leaf node to insert in B+Tree
        for (int i = 0; i < this.keys.size(); i++) {
            if (key.compareTo(this.keys.get(i)) <= 0) {
                this.children.get(i).insert(key, path);
                return;
            }
        }
        this.children.get(this.children.size() - 1).insert(key, path);
    }

B+Tree Get

B+Tree Leaf Node Get

    /**
     * get the key from the node.
     *
     * @param key to be searched.
     */
    @Override
    public List<T> get(T key) {
        List<T> keys = new ArrayList<>();
        BLeafNode<T> current = this;

        // search current node 
        keys.addAll(current.simpleGet(key);
        current = current.next;

        // search a series of consecutive nodes to get all values in B+Tree
        while (current != null) {
            List<T> newKeys = current.simpleGet(key);
            keys.addAll(newKeys);
            if (current.keys.size() != newKeys.size()) {
                break;
            }
            current = current.next;
        }
        return keys;
    }

    /**
     * get the key from one node only.
     *
     * @param key to be searched.
     */
    public List<T> simpleGet(T key) {
        if (key == null) {
            return null;
        }
        List<T> keys = new ArrayList<>();

        for (T t : this.keys) {
            if (t.compareTo(key) == 0) {
                keys.add(t);
            }
        }
        return keys;
    }

B+Tree Tree Node Get

    /**
     * Get a key from the tree.
     *
     * @param key to be searched.
     */
    @Override
    public List<T> get(T key) {
        if (key == null) {
            return null;
        }

        // non-leaf node does not get, pass to leaf node to get in B+Tree
        for (int i = 0; i < this.keys.size(); i++) {
            if (this.keys.get(i).compareTo(key) >= 0) {
                return this.children.get(i).get(key);
            }
        }
        return this.children.get(this.children.size() - 1).get(key);
    }

B+Tree.java

/**
 * B+Tree
 *
 * @param <T> the generic type this B+Tree uses. It extends comparable
 *            which allows us to order two of the same type.
 */
public class BTree<T extends Comparable<T>> {
    /**
     * Fields of a B+Tree.
     * <p>
     * Notice that we keep track of the root of the B+Tree. This is because
     * of the nature of the B+Tree. It grows upwards! Unless you can return the
     * root each time (which is a possible implementation approach) you will need
     * to keep track of the root.
     */
    private int order;          // Order of the BTree.
    private BNode<T> root;     // Root node of the BTree.

    /**
     * Constructor which sets the field 'order'
     *
     * @param order of the BTree.
     */
    public BTree(int order) {
        this.order = order;
        root = new BLeafNode<T>(order);
    }

    /**
     * Adds key to the BTree.
     *
     * @param key to be inserted.
     */
    public void insert(T key) {
        // Ensure input is not null.
        if (key == null)
            throw new IllegalArgumentException("Input cannot be null");

        List<BNode<T>> path = new ArrayList<BNode<T>>();
        root.insert(key, path);

        // split if required
        for (int i = path.size() - 1; i > 0; i--) {
            if (path.get(i).keys.size() == order) {
                split(path.get(i - 1), path.get(i));
            }
        }

        // split root if required
        if (root.keys.size() == order) {
            BTreeNode<T> newRoot = new BTreeNode<>(order);
            split(newRoot, root);
            root = newRoot;
        }
    }

    public List<T> get(T key) {
        if (key == null) {
            throw new IllegalArgumentException("Input cannot be null");
        }

        if (root != null) {
            return root.get(key);
        }
        return null;
    }

    /**
     * Will conduct a split on the provided indexed child of the provided node.
     *
     * @param node         The current node whose provided child to split will be split.
     * @param childToSplit The child to split within the provided node.
     */
    private void split(BNode<T> node, BNode<T> childToSplit) {

        // Ensure neither input is null.
        if (node == null || childToSplit == null)
            throw new IllegalArgumentException("Input cannot be null");

        // Get median key
        int med = childToSplit.keys.size() / 2;
        T medValue = childToSplit.keys.get(med);


        if (childToSplit instanceof BTreeNode) {
            // not leaf
            BTreeNode<T> leftNode = new BTreeNode<>(order);
            BTreeNode<T> rightNode = new BTreeNode<>(order);

            // here use med not med+1 to let med value goes up 
            leftNode.keys = childToSplit.keys.get(0, med);
            rightNode.keys = childToSplit.keys.get(med + 1, order);

            leftNode.children = ((BTreeNode<T>) childToSplit).children.get(0, med + 1);
            rightNode.children = ((BTreeNode<T>) childToSplit).children.get(med + 1, order + 1);

            boolean found = false;
            int i = 0;
            for (; i < node.keys.size(); i++) {
                if (medValue.compareTo(node.keys.get(i)) <= 0) {
                    node.keys.add(i, medValue);
                    found = true;
                    break;
                }
            }

            if (!found) {
                node.keys.add(medValue);
            }

            ((BTreeNode<T>) node).children.remove(childToSplit);
            ((BTreeNode<T>) node).children.add(i, leftNode);
            ((BTreeNode<T>) node).children.add(i + 1, rightNode);

        } else {
            // leaf node
            BLeafNode<T> leftNode = new BLeafNode<>(order);
            BLeafNode<T> rightNode = new BLeafNode<>(order);

            // here use med+1 not med to let the med value also stays at left leaf
            leftNode.keys = childToSplit.keys.get(0, med + 1);
            rightNode.keys = childToSplit.keys.get(med + 1, order);

            // reset connections
            leftNode.prev = ((BLeafNode<T>) childToSplit).prev;
            leftNode.next = rightNode;
            rightNode.prev = leftNode;
            rightNode.next = ((BLeafNode<T>) childToSplit).next;

            boolean found = false;
            int i = 0;
            for (; i < node.keys.size(); i++) {
                if (medValue.compareTo(node.keys.get(i)) <= 0) {
                    node.keys.add(i, medValue);
                    found = true;
                    break;
                }
            }

            if (!found) {
                node.keys.add(medValue);
            }

            ((BTreeNode<T>) node).children.remove(childToSplit);
            ((BTreeNode<T>) node).children.add(i, leftNode);
            ((BTreeNode<T>) node).children.add(i + 1, rightNode);
        }
    }

PostObj & IndexPostObj

PostObj

public class PostObj {

    String image_url;
    int comment_count;
    int like_count;
    String text;

    public PostObj(String image_url, int comment_count, int like_count, String text) {
        this.image_url = image_url;
        this.comment_count = comment_count;
        this.like_count = like_count;
        this.text = text;
    }
}

IndexPostObj

public class IndexPostObj implements Comparable<IndexPostObj> {

    PostObj postObj;
    String index;

    public IndexPostObj(PostObj postObj, String index) {
        this.postObj = postObj;
        this.index = index;
    }

    /**
     * compare by index, used in b+tree
     * @param indexPostObj the index - post obj
     * @return greater or smaller
     */
    @Override
    public int compareTo(IndexPostObj indexPostObj) {
        return this.index.compareTo(indexPostObj.index);
    }
}

Store Engine

/**
     * extract post object from json string
     *
     * @param jsonString the json string to extract
     * @return list of post obj
     */
    public List<PostObj> extractPostObj(String jsonString) {

        List<PostObj> postObjs = new ArrayList<PostObj>();
        try {
            JSONObject json = new JSONObject(jsonString);
            JSONArray posts = (JSONArray) json.get("allpost");

            // extract all fields, and then create new postobj
            for (int i = 0; i < posts.length(); i++) {
                JSONObject post = (JSONObject) posts.get(i);
                String imageUrl = post.getString("img_url");
                int commentCount = post.getInt("comment_count");
                int likeCount = post.getInt("like_count");
                String text = post.getString("text");
                PostObj postObj = new PostObj(imageUrl, commentCount, likeCount, text);
                postObjs.add(postObj);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return postObjs;
    }

    /**
     * extract tag and add tag post obj to b+tree
     *
     * @param postObjs the list of post obj
     * @return btree of index post obj
     */
    public BTree<IndexPostObj> extractTagPostObj(List<PostObj> postObjs) {
        Pattern pattern = Pattern.compile(HASHTAG_REGEX);
        BTree<IndexPostObj> bTree = new BTree<>(ORDER);
        for (PostObj postObj : postObjs) {
            List<String> indexes = new ArrayList<String>();
            boolean matchFound = true;
            String textString = postObj.text;
            while (matchFound && !textString.equals("")) {
                Matcher matcher = pattern.matcher(textString);
                matchFound = matcher.find();
                if (matchFound) {
                    String index = matcher.group(0);
                    if (!indexes.contains(index))
                        indexes.add(index);
                    textString = textString.substring(matcher.end());
                }
            }
            if (indexes.size() != 0) {
                for (String index : indexes) {
                    bTree.insert(new IndexPostObj(postObj, index));
                }
            } else {
                bTree.insert(new IndexPostObj(postObj, "#NoTag"));
            }
        }
        return bTree;
    }

    /**
     * query all post obj given tag
     *
     * @param tag to search
     * @return list of post obj
     */
    public List<PostObj> queryTag(String tag) {
        List<PostObj> returnedPostObjs = new ArrayList<>();
        List<IndexPostObj> indexPostObjs = this.tagTree.get(new IndexPostObj(null, tag));
        for (IndexPostObj indexPostObj : indexPostObjs) {
            returnedPostObjs.add(indexPostObj.postObj);
        }
        return returnedPostObjs;
    }

    /**
     * query all post obj given user
     *
     * @param user to search
     * @return list of post obj
     */
    public List<PostObj> queryUser(String user) {
        List<PostObj> returnedPostObjs = new ArrayList<>();
        List<IndexPostObj> indexPostObjs = this.userTree.get(new IndexPostObj(null, user));
        for (IndexPostObj indexPostObj : indexPostObjs) {
            returnedPostObjs.add(indexPostObj.postObj);
        }
        return returnedPostObjs;
    }

    public List<PostObj> queryPost() {
        return this.postObjs;
    }