Package edu.mit.blocks.codeblocks

Source Code of edu.mit.blocks.codeblocks.BlockStub

package edu.mit.blocks.codeblocks;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import edu.mit.blocks.codeblocks.BlockConnector.PositionType;

import edu.mit.blocks.renderable.RenderableBlock;
import edu.mit.blocks.workspace.Workspace;
import edu.mit.blocks.workspace.WorkspaceEvent;

/**
* <code>BlockStub</code> are a special form of blocks that provide a particular
* reference to its "parent" block.  These references can set, get, or increment
* the value of its "parent" block.  References may also get the value for a
* particular agent.  Finally, for a procedure block, its reference is a call
* block, which executes the procedure. 
*
* The parent instance for a set of stubs is not permanent.  The parent intance
* may change if the original parent it removed and then a new one with the
* same parent name is added to the block canvas. BlockStub manages the mapping
* between stubs and their parent.
*/
public class BlockStub extends Block {

    /**
     * Temporary mapping for parent type (caller plugs).
     * TODO remove once BlockUtilities cloneBlock() is finished
     */
    private static Map<String, String> parentToPlugType = new HashMap<String, String>();

    //stub type string constants
    private static final String GETTER_STUB = "getter";
    private static final String SETTER_STUB = "setter";
    private static final String CALLER_STUB = "caller";
    private static final String AGENT_STUB = "agent";

    //this particular stub type is unique to Starlogo TNG - may choose to remove it
    private static final String INC_STUB = "inc";
    private String parentName;
    private final String parentGenus;
    private final String stubGenus;

    /**
     * mySocketToParentSocket maps the sockets of this stubs to the sockets of its parent
     * this mapping is used to help in the maintanence this stub's sockets with respect to its parent
     */
    //private HashMap<BlockConnector,BlockConnector> mySocketToParentSocket = new HashMap<BlockConnector, BlockConnector>();
    /**
     * Constructs a new <code>BlockStub</code> instance using the specified
     * genus name of its parent block, the block id of its parent, the block name of parent
     * and its stub genus.  The exact reference to the parent through the specified initParentID
     * is needed, in addition to the other specified parameters, to completely construct a new block
     * stub.
     * @param initParentID the Long block ID of its initial parent
     * @param parentGenus the BlockGenus String name of its initial parent
     * @param parentName
     * @param stubGenus
     */
    public BlockStub(Workspace workspace, Long initParentID, String parentGenus, String parentName, String stubGenus) {
        super(workspace, stubGenus);

        assert initParentID != Block.NULL : "Parent id of stub should not be null";

        this.parentGenus = parentGenus;
        this.parentName = parentName;
        this.stubGenus = stubGenus;

        //initial parent of this
        Block parent = workspace.getEnv().getBlock(initParentID);
        //has parent block label
        this.setBlockLabel(parent.getBlockLabel());
        //initialize stub properties based on stubGenus such as sockets, plugs, and labels
        //this initialization assumes that nothing is connected to the parent yet
        //note: instead of modifying the stub blocks currect sockets, we replace them with whole new ones
        //such that the initkind of the stub blocks connectors are the same as their parents
        if (stubGenus.startsWith(GETTER_STUB)) {
            //set plug to be the single socket of parent or plug if parent has no sockets
            if (parent.getNumSockets() > 0) {
                this.setPlug(parent.getSocketAt(0).getKind(), this.getPlug().getPositionType(), this.getPlugLabel(), this.getPlug().isLabelEditable(), Block.NULL);
            } else {
                this.setPlug(parent.getPlugKind(), this.getPlug().getPositionType(), this.getPlugLabel(), this.getPlug().isLabelEditable(), Block.NULL);
            }

        } else if (stubGenus.startsWith(SETTER_STUB)) {
            BlockConnector mySoc = this.getSocketAt(0);
            //set socket type to be parent socket type or plug if parent has no sockets
            if (parent.getNumSockets() > 0) {
                this.setSocketAt(0, parent.getSocketAt(0).getKind(), mySoc.getPositionType(),
                        mySoc.getLabel(), mySoc.isLabelEditable(), mySoc.isExpandable(), mySoc.getBlockID());
            } else {
                this.setSocketAt(0, parent.getPlugKind(), mySoc.getPositionType(),
                        mySoc.getLabel(), mySoc.isLabelEditable(), mySoc.isExpandable(), mySoc.getBlockID());
            }
        } else if (stubGenus.startsWith(CALLER_STUB)) {
            //if parent has socket block connected to its first socket or has multiple sockets (parent may have blocks connected to its other sockets)
            if (parent.getSocketAt(0).getBlockID() != Block.NULL || parent.getNumSockets() > 1) {
                //retrieve sockets from parent and set sockets accordingly
                Iterator<BlockConnector> sockets = parent.getSockets().iterator();
                for (int i = 0; sockets.hasNext(); i++) {
                    BlockConnector socket = sockets.next();
                    //socket labels should correspond with the socket blocks of parent
                    if (socket.getBlockID() != Block.NULL) {
                        addSocket(socket.getKind(), BlockConnector.PositionType.SINGLE, workspace.getEnv().getBlock(socket.getBlockID()).getBlockLabel(), false, false, Block.NULL);
                    }
                }
            }

            //TODO: remove the following once BlockUtilities.cloneBlock() is finished
            // If our parent already has a plug type, we want to update
            // Note that we don't need to call renderables, since we are still
            // in the constructor
            String kind = parentToPlugType.get(parent.getBlockLabel() + parent.getGenusName());
            if (kind != null) {
                removeBeforeAndAfter();
                //TODO ria commented code relates to creating mirror plugs for caller stubs that have no sockets
                //if(this.getNumSockets() == 0){
                //  setPlug(kind, PositionType.MIRROR, "", false, Block.NULL);
                //} else {
                setPlug(kind, PositionType.SINGLE, "", false, Block.NULL);
                //}
            }

        } else if (stubGenus.startsWith(AGENT_STUB)) {
            //getter for specific who
            //set plug to be parent single socket kind or plug kind if parent has no sockets
            if (parent.getNumSockets() > 0) {
                setPlug(parent.getSocketAt(0).getKind(), this.getPlug().getPositionType(), this.getPlugLabel(), this.getPlug().isLabelEditable(), this.getPlugBlockID());
            } else {
                setPlug(parent.getPlugKind(), this.getPlug().getPositionType(), this.getPlugLabel(), this.getPlug().isLabelEditable(), this.getPlugBlockID());
            }

        } else if (stubGenus.startsWith(INC_STUB)) {
            //only included for number variables
            //do nothing for now
        }

        //has  page label of parent if parent has page label
        this.setPageLabel(parent.getPageLabel());
        //add new stub to hashmaps
        //parent should have existed in hashmap before this stub was created
        //(look at main Block constructor)
        //thus no problem should occur with following line
        workspace.getEnv().getBlockStubs(parentName + parentGenus).add(this.getBlockID());

    }

    /**
     * Constructs a new BlockStub instance.  This contructor is protected as it should only be called
     * while Block loads its information from the save String
     * @param workspace The workspace this stub should be created in
     * @param blockID the Long block ID of this
     * @param stubGenus the BlockGenus of this
     * @param label the Block label of this
     * @param parentName the String name of its parent
     * @param parentGenus the String BlockGenus name of its parent
     */
    protected BlockStub(Workspace workspace, Long blockID, String stubGenus, String label, String parentName, String parentGenus) {
        super(workspace, blockID, stubGenus, label, true);   //stubs may have stubs...
        //unlike the above constructor, the blockID specified should already
        //be referencing a fully loaded block with all necessary information
        //such as sockets, plugs, labels, etc.
        //the only information we need to handle is the stub information here.
        this.stubGenus = stubGenus;
        this.parentName = parentName;
        this.parentGenus = parentGenus;

        //there's a chance that the parent for this has not been added to parentNameToBlockStubs mapping
        String key = parentName + parentGenus;
        if (workspace.getEnv().containsBlockStubs(key)) {
            workspace.getEnv().getBlockStubs(parentName + parentGenus).add(this.getBlockID());
        } else {
            ArrayList<Long> stubs = new ArrayList<Long>();
            stubs.add(this.getBlockID());
            workspace.getEnv().putBlockStubs(key, stubs);
        }
    }

    /**
     * Returns a list of the block ids of the specified parent's stubs
     * @param blockID
     */
    public static Iterable<Long> getStubsOfParent(Workspace workspace, Block block) {
        ArrayList<Long> stubs = workspace.getEnv().getBlockStubs(block.getBlockLabel() + block.getGenusName());
        if (stubs != null) {
            return stubs;
        } else {
            return new ArrayList<Long>();
        }
    }

    /**
     * Saves the parent block information with the specified blockID in the Stub Map
     * @param blockID
     */
    public static void putNewParentInStubMap(Workspace workspace, Long blockID) {
        String key = workspace.getEnv().getBlock(blockID).getBlockLabel() + workspace.getEnv().getBlock(blockID).getGenusName();
        workspace.getEnv().putParentBlock(key, blockID);

        if (workspace.getEnv().getBlockStubs(key) == null) {
            workspace.getEnv().putBlockStubs(key, new ArrayList<Long>());
        }

        //notify dangling stubs and update their renderables
        //dangling stubs will be waiting to have a parent assigned to them
        //and reflect that graphically
        for (Long stubID : workspace.getEnv().getBlockStubs(key)) {
            BlockStub stub = (BlockStub) workspace.getEnv().getBlock(stubID);
            stub.notifyRenderable();
        }

    }

    /**
     * Updates BlockStub hashmaps and the BlockStubs of the parent of its new name
     * @param oldParentName
     * @param newParentName
     * @param parentID
     */
    public static void parentNameChanged(Workspace workspace, String oldParentName, String newParentName, Long parentID) {
        String oldKey = oldParentName + workspace.getEnv().getBlock(parentID).getGenusName();
        String newKey = newParentName + workspace.getEnv().getBlock(parentID).getGenusName();

        //only update if parents name really did "change" meaning the new parent name is
        //different from the old parent name
        if (!oldKey.equals(newKey)) {
            workspace.getEnv().putParentBlock(newKey, parentID);

            //update the parent name of each stub
            ArrayList<Long> stubs = workspace.getEnv().getBlockStubs(oldKey);
            for (Long stub : stubs) {
                BlockStub blockStub = ((BlockStub) workspace.getEnv().getBlock(stub));
                blockStub.parentName = newParentName;
                //update block label of each
                blockStub.setBlockLabel(newParentName);
                blockStub.notifyRenderable();
            }

            //check if any stubs already exist for new key
            ArrayList<Long> existingStubs = workspace.getEnv().getBlockStubs(newKey);
            if (existingStubs != null) {
                stubs.addAll(existingStubs);
            }

            workspace.getEnv().putBlockStubs(newKey, stubs);

            //remove old parent name from hash maps
            workspace.getEnv().removeParentBlock(oldKey);
            workspace.getEnv().removeBlockStubs(oldKey);
        }
    }

    /**
     * Updates the BlockStubs associated with the parent of its new page label
     * @param newPageLabel
     * @param parentID
     */
    public static void parentPageLabelChanged(Workspace workspace, String newPageLabel, Long parentID) {
        String key = workspace.getEnv().getBlock(parentID).getBlockLabel() + workspace.getEnv().getBlock(parentID).getGenusName();

        //update each stub
        ArrayList<Long> stubs = workspace.getEnv().getBlockStubs(key);
        for (Long stub : stubs) {
            BlockStub blockStub = ((BlockStub) workspace.getEnv().getBlock(stub));
            blockStub.setPageLabel(newPageLabel);
            blockStub.notifyRenderable();
        }

    }

    /**
     * Updates the BlocksStubs associated with the parent of its new page label
     * @param parentID
     */
    public static void parentConnectorsChanged(Workspace workspace, Long parentID) {
        String key = workspace.getEnv().getBlock(parentID).getBlockLabel() + workspace.getEnv().getBlock(parentID).getGenusName();

        //update each stub only if stub is a caller (as callers are the only type of stubs that
        //can change its connectors after being created)
        ArrayList<Long> stubs = workspace.getEnv().getBlockStubs(key);
        for (Long stub : stubs) {
            BlockStub blockStub = ((BlockStub) workspace.getEnv().getBlock(stub));
            if (blockStub.stubGenus.startsWith(CALLER_STUB)) {
                blockStub.updateConnectors();
                //System.out.println("updated connectors of: "+blockStub);
                blockStub.notifyRenderable();
            }
        }
    }

    /**
     * Updates the plug on caller stubs associated with the given parent.
     * @param kind the new plug kind that callers should set
     */
    public static void parentPlugChanged(Workspace workspace, Long parentID, String kind) {
        String key = workspace.getEnv().getBlock(parentID).getBlockLabel() + workspace.getEnv().getBlock(parentID).getGenusName();

        // Update our type mapping.
        if (kind == null) {
            parentToPlugType.remove(key);
        } else {
            parentToPlugType.put(key, kind);
        }

        // update each stub only if stub is a caller
        ArrayList<Long> stubs = workspace.getEnv().getBlockStubs(key);
        for (Long stub : stubs) {
            BlockStub blockStub = ((BlockStub) workspace.getEnv().getBlock(stub));
            if (blockStub.stubGenus.startsWith(CALLER_STUB)) {
                if (kind == null) {
                    blockStub.restoreInitConnectors();
                } else {
                    blockStub.updatePlug(kind);
                }
            }
        }
    }

    ////////////////////////////////////
    // PARENT INFORMATION AND METHODS //
    ////////////////////////////////////
    /**
     * Returns the parent name of this stub
     * @return the parent name of this stub
     */
    public String getParentName() {
        return parentName;
    }

    /**
     * Returns the parent block of this stub
     * @return the parent block of this stub
     */
    public Block getParent() {
        String key = parentName + parentGenus;
        if (!workspace.getEnv().containsParentBlock(key)) {
            return null;
        }
        return workspace.getEnv().getBlock(workspace.getEnv().getParentBlockID(key));
    }

    /**
     * Returns the parent block genus of this stub
     * @return the parent block genus of this stub
     */
    public String getParentGenus() {
        return parentGenus;
    }

    /**
     *
     */
    public boolean doesParentExist() {
        //TODO ria: needs to check BlockCanvas if parent is "alive"

        return true;
    }

    ///////////////////////////////////
    // METHODS OVERRIDDEN FROM BLOCK //
    ///////////////////////////////////
    /**
     * Overriden from Block.  Can not change the genus of a Stub.
     */
    public void changeGenusTo(String genusName) {
        //return null;
    }

    //////////////////////////////////////////////////
    //BLOCK STUB CONNECTION INFORMATION AND METHODS //
    //////////////////////////////////////////////////
    /**
     * Updates the conenctors of this stub according to its parent.
     * For now only caller stubs should update their connector information after
     * being created. 
     */
    private void updateConnectors() {
        Block parent = getParent();
        if (parent != null) {
            //retrieve sockets from parent and set sockets accordingly
            Iterator<BlockConnector> parentSockets = parent.getSockets().iterator();
            int i; //socket index
            //clear all sockets TODO temporary solution
            for (BlockConnector socket : getSockets()) {
                removeSocket(socket);
            }
            //add parent sockets
            for (i = 0; parentSockets.hasNext(); i++) {
                BlockConnector parentSocket = parentSockets.next();
                if (parentSocket.getBlockID() != Block.NULL) {
                    //may need to add more sockets if parent has > 1 sockets
                    if (i > this.getNumSockets() - 1) {
                        //socket labels should correspond with the socket blocks of parent
                        if (parentSocket.getBlockID() != Block.NULL) {
                            addSocket(parentSocket.getKind(), BlockConnector.PositionType.SINGLE, workspace.getEnv().getBlock(parentSocket.getBlockID()).getBlockLabel(), false, false, Block.NULL);
                        }
                    } else {
                        BlockConnector con = getSocketAt(i);
                        this.setSocketAt(i, parentSocket.getKind(), con.getPositionType(), workspace.getEnv().getBlock(parentSocket.getBlockID()).getBlockLabel(), con.isLabelEditable(),
                                con.isExpandable(), con.getBlockID());
                    }
                }
            }
        }
    }

    /**
     * Restores the initial state of the before, after, and plug. Disconnects
     * any invalid blocks. Only caller stubs should use this method.
     */
    private void restoreInitConnectors() {
        if (!hasPlug()) {
            return;     // Already in original state
        }
        // We have to check for a plug connector.
        Long id = getPlugBlockID();
        if (id != null && !id.equals(Block.NULL)) {
            disconnectBlock(id);
        }

        // Always synchronize! We can't have both a plug and a before.
        removePlug();
        resetBeforeAndAfter();
        workspace.getEnv().getRenderableBlock(getBlockID()).updateConnectors();
        notifyRenderable();
    }

    /**
     * Updates the plug type. Disconnects any invalid blocks. Only caller
     * stubs should use this method.
     * @param kind must not be null
     */
    private void updatePlug(String kind) {
        if (hasPlug() && getPlugKind().equals(kind)) {
            return;
        }

        // We have to check for a before and after block.
        Long id = getBeforeBlockID();
        if (id != null && !id.equals(Block.NULL)) {
            disconnectBlock(id);
        }

        id = getAfterBlockID();
        if (id != null && !id.equals(Block.NULL)) {
            disconnectBlock(id);
        }

        // We also need to check the plug, because it may be connected to
        // the wrong type.
        id = getPlugBlockID();
        if (id != null && !id.equals(Block.NULL)) {
            disconnectBlock(id);
        }

        // Always synchronize! We can't have both a plug and a before.
        removeBeforeAndAfter();
        setPlug(kind, PositionType.SINGLE, kind, false, Block.NULL);
        workspace.getEnv().getRenderableBlock(getBlockID()).updateConnectors();
        notifyRenderable();
    }

    /**
     * Disconnect the given block from us. Must have a valid id.
     */
    private void disconnectBlock(Long id) {
        Block b2 = workspace.getEnv().getBlock(id);
        BlockConnector conn2 = b2.getConnectorTo(getBlockID());
        BlockConnector conn = getConnectorTo(id);
        BlockLink link = BlockLink.getBlockLink(workspace, this, b2, conn, conn2);
        RenderableBlock rb = workspace.getEnv().getRenderableBlock(link.getSocketBlockID());
        link.disconnect();
        rb.blockDisconnected(link.getSocket());
        workspace.notifyListeners(
                new WorkspaceEvent(workspace, rb.getParentWidget(), link, WorkspaceEvent.BLOCKS_DISCONNECTED));

    }

    ////////////////////////////////////////
    // METHODS FROM BLOCK GENUS           //
    ////////////////////////////////////////
    /**
     * Returns the Color of this; May return Color.Black if color was unspecified.
     * @return the Color of this; May return Color.Black if color was unspecified.
     */
    public Color getColor() {
        if (getParent() == null) {
            return super.getColor();
        }
        return getParent().getColor();
    }

    /**
     * @return current information about block
     */
    public String toString() {
        return "Block Stub +" + getBlockID() + ": " + getBlockLabel() + " with sockets: " + getSockets() + " and plug: " + getPlug();
    }

    @Override
    public boolean isCommandBlock() {
        return hasAfterConnector() && hasBeforeConnector();
    }

    @Override
    public boolean isDataBlock() {
        return !hasAfterConnector() && !hasBeforeConnector();
    }

    @Override
    public boolean isFunctionBlock() {
        return hasPlug() && (this.getNumSockets() > 0);
    }

    ////////////////////////
    // SAVING AND LOADING //
    ////////////////////////
    public Node getSaveNode(Document document, int x, int y, Node commentNode, boolean collapsed) {
      Element stubElement = document.createElement("BlockStub");
     
      Element parentNameElement = document.createElement("StubParentName");
      parentNameElement.appendChild(document.createTextNode(parentName));
      stubElement.appendChild(parentNameElement);
     
      Element parentGenusElement = document.createElement("StubParentGenus");
      parentGenusElement.appendChild(document.createTextNode(parentGenus));
      stubElement.appendChild(parentGenusElement);
     
      Node blockNode = super.getSaveNode(document, x, y, commentNode, collapsed);
      stubElement.appendChild(blockNode);
     
      return stubElement;
    }
   
}
TOP

Related Classes of edu.mit.blocks.codeblocks.BlockStub

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.