Package edu.mit.blocks.renderable

Source Code of edu.mit.blocks.renderable.BlockUtilities$MatchingComparator

package edu.mit.blocks.renderable;

import java.awt.Container;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import edu.mit.blocks.codeblocks.Block;
import edu.mit.blocks.codeblocks.BlockConnector;
import edu.mit.blocks.codeblocks.BlockLink;
import edu.mit.blocks.codeblocks.BlockLinkChecker;
import edu.mit.blocks.codeblocks.BlockStub;
import edu.mit.blocks.workspace.Workspace;
import edu.mit.blocks.workspace.WorkspaceEvent;
import edu.mit.blocks.workspace.WorkspaceWidget;

public class BlockUtilities {

    private static final Map<String, Integer> instanceCounter = new HashMap<String, Integer>();
    private static double zoom = 1.0;

    public static void reset() {
        zoom = 1.0;
        instanceCounter.clear();
    }

    public static void setZoomLevel(double newZoom) {
        zoom = newZoom;
    }

    /**
     * Returns true if the specified label is valid according to the specifications
     * of this block's genus. For example, if this block's label must be unique
     * (as specified from its genus), this method verifies that its label is
     * unique relative to the other instances present.
     * @param label the String block label to test
     * @return true if the specified label is valid according to the specifications
     * of this block's genus.
     */
    public static boolean isLabelValid(Workspace workspace, Long blockID, String label) {
        return isLabelValid(workspace.getEnv().getBlock(blockID), label);
    }

    public static boolean isLabelValid(Block block, String label) {
        if (block == null || label == null) {
            return false;
        } else if (block.labelMustBeUnique()) {
            Workspace workspace = block.getWorkspace();
            //search through the current block instances active in the workspace
            for (RenderableBlock rb : workspace.getRenderableBlocksFromGenus(block.getGenusName())) {
                if (label.equals(rb.getBlock().getBlockLabel())) {
                    return false;
                }
            }
        }
        //either label doesn't have to be unique or
        //label was found to be unique in the search
        return true;
    }

    public static void deleteBlock(RenderableBlock block) {
        block.setLocation(0, 0);

        WorkspaceWidget widget = block.getParentWidget();
        if (widget != null) {
            widget.removeBlock(block);
        }

        Container parent = block.getParent();
        if (parent != null) {
            parent.remove(block);
            parent.validate();
        }

        block.setParentWidget(null);
        Workspace workspace = block.getWorkspace();
        workspace.notifyListeners(new WorkspaceEvent(workspace, widget, block.getBlockID(), WorkspaceEvent.BLOCK_REMOVED));

    }

    public static RenderableBlock cloneBlock(Block myblock) {
        String mygenusname = myblock.getGenusName();
        String label = myblock.getBlockLabel();
        final Workspace workspace = myblock.getWorkspace();

        //sometimes the factory block will have an assigned label different
        //from its genus label.
        if (!myblock.getInitialLabel().equals(myblock.getBlockLabel())) {
            //acquire prefix and suffix length from myblock label
            int prefixLength = myblock.getLabelPrefix().length();
            int suffixLength = myblock.getLabelSuffix().length();
            //we need to set the block label without the prefix and suffix attached because those
            //values are automatically concatenated to the string specified in setBlockLabel.  I know its
            //weird, but its the way block labels were designed.
            if (prefixLength > 0 || suffixLength > 0) //TODO we could do this outside of this method, even in constructor
            {
                label = myblock.getBlockLabel().substring(prefixLength, myblock.getBlockLabel().length() - suffixLength);
            }
        }

        //check genus instance counter and if label unique - change label accordingly
        //also check if label already has a value at the end, if so update counter to have the max value
        //TODO ria need to make this smarter
        //some issues to think about:
        // - what if they throw out an instance, such as setup2? should the next time they take out
        //   a setup block, should it have setup2 on it?  but wouldn't that be confusing?
        // - when we load up a new project with some instances with numbered labels, how do we keep
        //   track of new instances relative to these old ones?
        // - the old implementation just iterated through all the instances of a particular genus in the
        //   workspace and compared a possible label to the current labels of that genus.  if there wasn't
        //   any current label that matched the possible label, it returned that label.  do we want to do this?
        //   is there something more efficient?

        String labelWithIndex = label;  //labelWithIndex will have the instance value

        int value;
        //initialize value that will be appended to the end of the label
        if (instanceCounter.containsKey(mygenusname)) {
            value = instanceCounter.get(mygenusname).intValue();
        } else {
            value = 0;
        }
        //begin check for validation of label
        //iterate until label is valid
        while (!isLabelValid(myblock, labelWithIndex)) {
            value++;
            labelWithIndex = labelWithIndex + value;
        }

        //set valid label and save current instance number
        instanceCounter.put(mygenusname, new Integer(value));
        if (!labelWithIndex.equals(label)) //only set it if the label actually changed...
        {
            label = labelWithIndex;
        }

        Block block;
        if (myblock instanceof BlockStub) {
            Block parent = ((BlockStub) myblock).getParent();
            block = new BlockStub(workspace, parent.getBlockID(), parent.getGenusName(), parent.getBlockLabel(), myblock.getGenusName());
        } else {
            block = new Block(workspace, myblock.getGenusName(), label);
        }

        // TODO - djwendel - create a copy of the RB properties too, using an RB copy constructor.  Don't just use the genus.
        //RenderableBlock renderable = new RenderableBlock(this.getParentWidget(), block.getBlockID());
        RenderableBlock renderable = new RenderableBlock(workspace, null, block.getBlockID());
        renderable.setZoomLevel(BlockUtilities.zoom);
        renderable.redrawFromTop();
        renderable.repaint();
        return renderable;
    }

    /**
     * Creates a string representation for the given RenderableBlock that
     * is disambiguated from string representations for blocks with the same label
     * by appending socket information to the end of the block's label.  The
     * created string is in the form:
     * BlockLabel [socketLabel1, ..., socketLabelN]
     * @param block the FactoryRenderableBlock to create a string representation of
     * @return a String containing the given block's keyword with a
     * list of its socket labels appended to the end.
     */
    public static String disambiguousStringRep(RenderableBlock block) {
        String rep = block.getKeyword();
        String genus = block.getGenus();
        Iterator<BlockConnector> sockets = block.getBlock().getSockets().iterator();
        if (sockets.hasNext()) {
            String socketLabels = " [";
            while (sockets.hasNext()) {
                if (socketLabels.length() > 2) {
                    socketLabels += ", ";
                }
                socketLabels += sockets.next().getLabel();
            }
            socketLabels += "]";
            //HACK!!! TODO: rewriting typeblocking
            if (genus.equals("sum")) {
                return rep + " [number]";
            } else if (genus.equals("string-append")) {
                return rep + " [text]";
            }
            return rep + socketLabels;
        }
        return rep;
    }

    /**
     * @param workspace The workspace in use
     * @param keyword
     *
     * @requires keyword != null
     * @return List of TextualFactoryBlocks, {T}, such that:
     *         T.toString contains keyword
     *            T == null if no matching blocks were found
     *          T.toString is unique for each T
     */
    public static List<TextualFactoryBlock> getAllMatchingBlocks(Workspace workspace, String keyword) {
        //Use Set such that we don't get any repeats
        Set<TextualFactoryBlock> matchingBlocks = new TreeSet<TextualFactoryBlock>(new MatchingComparator(keyword));

        //find all FactoryRenderableBlocks and check for a match
        for (RenderableBlock renderable : workspace.getFactoryManager().getBlocks()) {

            // TODO: don't assume they're all FactoryRenderableBlocks!  Collisions aren't...
            if (renderable == null || renderable.getBlockID().equals(Block.NULL) || !(renderable instanceof FactoryRenderableBlock)) {
                continue;
            }

            // first, check if query matches block keyword
            if (renderable.getKeyword().toLowerCase().contains(keyword.toLowerCase())) {
                matchingBlocks.add(new TextualFactoryBlock((FactoryRenderableBlock) renderable, renderable.getBlock().getBlockLabel()));
            }

            // grabs the quote block needed TODO: needs to be independent!
            if (keyword.startsWith("\"") && renderable.getBlock().getGenusName().equalsIgnoreCase("string")) {
                String[] quote = keyword.split("\"");
                // makes sure that there is text after the " so that it can be placed onto the block
                if (quote.length > 1) {
                    matchingBlocks.add(new TextualFactoryBlock((FactoryRenderableBlock) renderable, "\"" + quote[1] + "\""));
                }
            } // otherwise, if the keyword is too long, check to see if
            // the user is trying to type extra info for disambiguation
            else if (keyword.length() > renderable.getKeyword().length()) {
                if (disambiguousStringRep((FactoryRenderableBlock) renderable).toLowerCase().contains(keyword.toLowerCase())) {
                    matchingBlocks.add(new TextualFactoryBlock((FactoryRenderableBlock) renderable, disambiguousStringRep((FactoryRenderableBlock) renderable)));
                }
            }

            /////////////////////////////////////
            //TODO: Add code here for nicknames//
            /////////////////////////////////////

        }

        /* if blocks have the same labels, the search results will be ambiguous.
         * the following expands the string representation of the TFB if needed
         * to disambiguate the blocks. */
        ArrayList<TextualFactoryBlock> disambiguatedMatches = new ArrayList<TextualFactoryBlock>(matchingBlocks);
        TextualFactoryBlock t1, t2;
        for (int i = 0; i < disambiguatedMatches.size(); i++) {
            t1 = disambiguatedMatches.get(i);
            if (i > 0) {
                t2 = disambiguatedMatches.get(i - 1);
                if (t1.toString().equals(t2.toString())) {
                    disambiguatedMatches.set(i, new TextualFactoryBlock(t1.getfactoryBlock(), disambiguousStringRep(t1.getfactoryBlock())));
                    disambiguatedMatches.set(i - 1, new TextualFactoryBlock(t2.getfactoryBlock(), disambiguousStringRep(t2.getfactoryBlock())));
                }
            }
            if (i < disambiguatedMatches.size() - 1) {
                t2 = disambiguatedMatches.get(i + 1);
                if (t1.toString().equals(t2.toString())) {
                    disambiguatedMatches.set(i, new TextualFactoryBlock(t1.getfactoryBlock(), disambiguousStringRep(t1.getfactoryBlock())));
                    disambiguatedMatches.set(i + 1, new TextualFactoryBlock(t2.getfactoryBlock(), disambiguousStringRep(t2.getfactoryBlock())));
                }
            }
        }
        //List<TextualFactoryBlock> f = new ArrayList<TextualFactoryBlock>();
        return disambiguatedMatches;
    }

    /**
     *
     * @param plus
     *
     * @requires plus != null
     * @return List containing the two "+" TextualFactoryBlocks
     *       and any other blocks containing "+"
     */
    public static List<TextualFactoryBlock> getPlusBlocks(Workspace workspace, String plus) {
        Set<TextualFactoryBlock> matchingBlocks = new HashSet<TextualFactoryBlock>();
        // looks through the factory blocks
        for (RenderableBlock renderable : workspace.getFactoryManager().getBlocks()) {
            if (renderable == null || renderable.getBlockID().equals(Block.NULL) || !(renderable instanceof FactoryRenderableBlock)) {
                continue;
            }
            //TODO genus names are based from TNG, need to figure out a workaround
            // grabs the "+" number and text blocks
            if (renderable.getBlock().getGenusName().equalsIgnoreCase("sum")) {
                // changes the label so that the search result will not be ambiguous
                matchingBlocks.add(new TextualFactoryBlock((FactoryRenderableBlock) renderable, "+ [number]"));
            }
            if (renderable.getBlock().getGenusName().equalsIgnoreCase("string-append")) {
                // changes the label so that the search result will not be ambiguous
                matchingBlocks.add(new TextualFactoryBlock((FactoryRenderableBlock) renderable, "+ [text]"));
            }
            // selects any other block that contains the number (for variables that contains the number)
            if (renderable.getKeyword().toLowerCase().contains(plus.toLowerCase())) {
                matchingBlocks.add(new TextualFactoryBlock((FactoryRenderableBlock) renderable, renderable.getBlock().getBlockLabel()));
            }
        }

        return new ArrayList<TextualFactoryBlock>(matchingBlocks);
    }

    /**
     * @param workspace
     * @param digits
     *
     * @requires digits != null
     * @return List containing a number TextualFactoryBlock
     *       and any other blocks containing the numbers
     */
    public static List<TextualFactoryBlock> getDigits(Workspace workspace, String digits) {
        Set<TextualFactoryBlock> matchingBlocks = new TreeSet<TextualFactoryBlock>(new MatchingComparator(digits));
        // looks through the factory blocks
        for (RenderableBlock renderable : workspace.getFactoryManager().getBlocks()) {
            if (renderable == null || renderable.getBlockID().equals(Block.NULL) || !(renderable instanceof FactoryRenderableBlock)) {
                continue;
            }

            //TODO genus name are based from TNG, need to figure out a workaround
            // selects the number block
            if (renderable.getBlock().getGenusName().equalsIgnoreCase("number")) {
                matchingBlocks.add(new TextualFactoryBlock((FactoryRenderableBlock) renderable, digits));
            }
            // selects any other block that contains the number (for variables that contains the number)
            if (renderable.getKeyword().toLowerCase().contains(digits.toLowerCase())) {
                matchingBlocks.add(new TextualFactoryBlock((FactoryRenderableBlock) renderable, renderable.getBlock().getBlockLabel()));
            }
        }
        return new ArrayList<TextualFactoryBlock>(matchingBlocks);
    }

    /**
     * Comparator used by getAllMatchingBlocks() to sort according to the position in the word that the match occurs,
     * then according to the sort order of TextualFactoryBlocks
     * @author Daniel
     */
    private static class MatchingComparator implements Comparator<TextualFactoryBlock> {

        private String keyword;

        public MatchingComparator(String keyword) {
            this.keyword = keyword.toLowerCase();
        }

        public int compare(TextualFactoryBlock t1, TextualFactoryBlock t2) {
            if (t1.compareTo(t2) == 0) {
                return 0;
            }
            if (t1.toString().toLowerCase().indexOf(keyword) == t2.toString().toLowerCase().indexOf(keyword)) {
                return t1.compareTo(t2);
            }
            return t1.toString().toLowerCase().indexOf(keyword) > t2.toString().toLowerCase().indexOf(keyword) ? 1 : -1;
        }
    }

    /**
     * Returns a new RenderableBlock instance with the matching genusName.
     * New block will also have matching label is label is not-null. May return null.
     *
     * @param workspace The workspace to use
     * @param genusName
     * @param label
     *
     * @requires if block associated with genusName has a non editable
     *        or unique block label, then "label" MUST BE NULL.
     * @return  A new RenderableBlock with matching genusName and label (if label is not-null).
     *       If no matching blocks were found, return null.
     */
    public static RenderableBlock getBlock(Workspace workspace, String genusName, String label) {
        if (genusName == null) {
            return null;
        }

//    find all blocks on the page and look for any match
        for (Block block : workspace.getBlocks()) {
            //make sure we're not dealing with null blocks
            if (block == null || block.getBlockID() == null || block.getBlockID().equals(Block.NULL)) {
                continue;
            }
            //find the block with matching genus and either a matching label or an editable label
            if (block.getGenusName().equals(genusName) && (block.isLabelEditable() || block.getBlockLabel().equals(label)
                    || block.isInfix())) {
                //for block stubs, need to make sure that the label matches because stubs of the same kind
                //(i.e. global var getters, agent var setters, etc.) have the same genusName
                //but stubs of different parents do not share the same label
                if (block instanceof BlockStub && !block.getBlockLabel().equals(label)) {
                    continue;
                }
                //create new renderable block instance
                RenderableBlock renderable = BlockUtilities.cloneBlock(block);
                //make sure renderable block is not a null instance of a block
                if (renderable == null || renderable.getBlockID().equals(Block.NULL)) {
                    throw new RuntimeException("Invariant Violated: a valid non null blockID just"
                            + "returned a null instance of RenderableBlock");
                    //please throw an exception here because it wouldn't make any sense
                    //if the Block is valid but it's associated RenderableBlock is not
                }
                //do not drop down default arguments
                renderable.ignoreDefaultArguments();
                //get corresponding block
                Block newblock = workspace.getEnv().getBlock(renderable.getBlockID());
                //make sure corresponding block is not a null instance of block
                if (newblock == null || newblock.getBlockID().equals(Block.NULL)) {
                    throw new RuntimeException("Invariant Violated: a valid non null blockID just"
                            + "returned a null instance of Block");
                    //please throw an exception here because it wouldn't make any sense
                    //if the Block is valid but it's associated RenderableBlock is not
                }
                //attempt to set the label text if possible as defined by the specs
                //should not set the labels of block stubs because their labels are determined by their parent
                if ((block.isLabelEditable() || block.getBlockLabel().equals(label))) {
                    if (label != null && !(block instanceof BlockStub)) {
                        if (newblock.isLabelEditable() && !newblock.labelMustBeUnique()) {
                            newblock.setBlockLabel(label);
                        }
                    }
                }
                //return renderable block
                return renderable;
            }



            /////////////////////////////////////
            //TODO: Add code here for nicknames//
            /////////////////////////////////////



        }
        //TODO: the part below is a hack. If there are other types of blocks, we need to account for them
        return null;
    }

    private static boolean isNullBlockInstance(Workspace workspace, Long blockID) {
        if (blockID == null) {
            return true;
        } else if (blockID.equals(Block.NULL)) {
            return true;
        } else if (workspace.getEnv().getBlock(blockID) == null) {
            return true;
        } else if (workspace.getEnv().getBlock(blockID).getBlockID() == null) {
            return true;
        } else if (workspace.getEnv().getBlock(blockID).getBlockID().equals(Block.NULL)) {
            return true;
        } else if (workspace.getEnv().getRenderableBlock(blockID) == null) {
            return true;
        } else if (workspace.getEnv().getRenderableBlock(blockID).getBlockID() == null) {
            return true;
        } else if (workspace.getEnv().getRenderableBlock(blockID).getBlockID().equals(Block.NULL)) {
            return true;
        } else {
            return false;
        }
    }

    public static BlockNode makeNodeWithChildren(Workspace workspace, Long blockID) {
        if (isNullBlockInstance(workspace, blockID)) {
            return null;
        }
        Block block = workspace.getEnv().getBlock(blockID);
        String genus = block.getGenusName();
        String parentGenus = block instanceof BlockStub ? ((BlockStub) block).getParentGenus() : null;
        String label;
        if (!block.labelMustBeUnique() || block instanceof BlockStub) {
            label = block.getBlockLabel();
        } else {
            label = null;
        }
        BlockNode node = new BlockNode(genus, parentGenus, label);
        for (BlockConnector socket : block.getSockets()) {
            if (socket.hasBlock()) {
                node.addChild(makeNodeWithStack(workspace, socket.getBlockID()));
            }
        }
        return node;
    }

    public static BlockNode makeNodeWithStack(Workspace workspace, Long blockID) {
        if (isNullBlockInstance(workspace, blockID)) {
            return null;
        }
        Block block = workspace.getEnv().getBlock(blockID);
        String genus = block.getGenusName();
        String parentGenus = block instanceof BlockStub ? ((BlockStub) block).getParentGenus() : null;
        String label;
        if (!block.labelMustBeUnique() || block instanceof BlockStub) {
            label = block.getBlockLabel();
        } else {
            label = null;
        }
        BlockNode node = new BlockNode(genus, parentGenus, label);
        for (BlockConnector socket : block.getSockets()) {
            if (socket.hasBlock()) {
                node.addChild(makeNodeWithStack(workspace, socket.getBlockID()));
            }
        }
        if (block.hasAfterConnector()) {
            node.setAfter(makeNodeWithStack(workspace, block.getAfterBlockID()));
        }
        return node;
    }

    /**
     * Checks to see if the block still exists
     * @return True if renderable block is still there, False otherwise
     */
    public static boolean blockExists(Workspace workspace, BlockNode node) {
        String genusName = node.getGenusName(); //genusName may not be null
        RenderableBlock renderable = BlockUtilities.getBlock(workspace, genusName, node.getLabel());
        if (renderable == null) {
            return false;
        } else {
            return true;
        }
    }

    public static RenderableBlock makeRenderable(Workspace workspace, BlockNode node, WorkspaceWidget widget) {
        String genusName = node.getGenusName(); //genusName may not be null
        RenderableBlock renderable = BlockUtilities.getBlock(workspace, genusName, node.getLabel());
        if (renderable == null) {
            throw new RuntimeException("No children block exists for this genus: " + genusName);
        }
        Block block = workspace.getEnv().getBlock(renderable.getBlockID()); //assume not null
        widget.blockDropped(renderable);
        for (int i = 0; i < node.getChildren().size(); i++) {
            BlockConnector socket = block.getSocketAt(i);
            BlockNode child = node.getChildren().get(i);
            RenderableBlock childRenderable = makeRenderable(workspace, child, widget);
            Block childBlock = workspace.getEnv().getBlock(childRenderable.getBlockID());

            //link blocks
            BlockLink link;
            if (childBlock.hasPlug()) {
                link = BlockLinkChecker.canLink(workspace, block, childBlock, socket, childBlock.getPlug());
            } else if (childBlock.hasBeforeConnector()) {
                link = BlockLinkChecker.canLink(workspace, block, childBlock, socket, childBlock.getBeforeConnector());
            } else {
                link = null;
            }//assume link is not null
            link.connect();
            workspace.notifyListeners(new WorkspaceEvent(workspace, workspace.getEnv().getRenderableBlock(link.getPlugBlockID()).getParentWidget(), link, WorkspaceEvent.BLOCKS_CONNECTED));

        }
        if (node.getAfterNode() != null) {
            BlockConnector socket = block.getAfterConnector(); //assume has after connector
            BlockNode child = node.getAfterNode();
            RenderableBlock childRenderable = makeRenderable(workspace, child, widget);
            Block childBlock = workspace.getEnv().getBlock(childRenderable.getBlockID());

            //link blocks
            BlockLink link;
            if (childBlock.hasPlug()) {
                link = BlockLinkChecker.canLink(workspace, block, childBlock, socket, childBlock.getPlug());
            } else if (childBlock.hasBeforeConnector()) {
                link = BlockLinkChecker.canLink(workspace, block, childBlock, socket, childBlock.getBeforeConnector());
            } else {
                link = null;
            }//assume link is not null
            link.connect();
            workspace.notifyListeners(new WorkspaceEvent(workspace, workspace.getEnv().getRenderableBlock(link.getPlugBlockID()).getParentWidget(), link, WorkspaceEvent.BLOCKS_CONNECTED));

        }
        return renderable;
    }
}
TOP

Related Classes of edu.mit.blocks.renderable.BlockUtilities$MatchingComparator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.