ArduinoNode.java

/*******************************************************************************
 * jArduino: Arduino C++ Code Generation From Java
 * Copyright 2020 Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package net.sourceforge.jarduino.message;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import net.sourceforge.jarduino.ArduinoException;
import net.sourceforge.jarduino.message.ArduinoAttribute.ArduinoAttrObject;
import net.sourceforge.jarduino.message.ArduinoParser.ArduinoParserException;

/**
 * ArduinoNode.
 */
public class ArduinoNode
        implements ArduinoNamedObject, ArduinoAttrObject {
    /**
     * The Marker.
     */
    static final String MARKER = "BU_";

    /**
     * The Marker.
     */
    static final String MARKER_TX = ArduinoMessage.MARKER + "TX_" + MARKER;

    /**
     * The null node.
     */
    public static final String NULL_NODE = "Vector__XXX";

    /**
     * The owning system.
     */
    private final ArduinoSystem theOwner;

    /**
     * The name of the Node.
     */
    private final String theName;

    /**
     * The list of messages sent by this node.
     */
    private final List<ArduinoMessage> theMessages;

    /**
     * The list of messages sent but not owned by this node.
     */
    private final List<ArduinoMessage> theBorrowed;

    /**
     * the Map of explicit attributes.
     */
    private final Map<ArduinoAttribute, Object> theAttributes;

    /**
     * the Map of relation attributes.
     */
    private final Map<ArduinoAttribute, Map<ArduinoAttrObject, Object>> theRelations;

    /**
     * Constructor.
     * @param pOwner the owner
     * @param pName the name.
     */
    ArduinoNode(final ArduinoSystem pOwner,
                final String pName) {
        /* Store parameters */
        theOwner = pOwner;
        theName = pName;

        /* Create lists/maps */
        theMessages = new ArrayList<>();
        theBorrowed = new ArrayList<>();
        theAttributes = new HashMap<>();
        theRelations = new HashMap<>();
    }

    /**
     * Obtain the owner.
     * @return the owner
     */
    public ArduinoSystem getOwner() {
        return theOwner;
    }

    @Override
    public String getName() {
        return theName;
    }

    /**
     * Add message to list.
     * @param pMessage the message
     */
    void addMessage(final ArduinoMessage pMessage) {
        theMessages.add(pMessage);
    }

    /**
     * Obtain the messages sent by this node.
     * @return the messages
     */
    public List<ArduinoMessage> getMessages() {
        return theMessages;
    }

    /**
     * Add potentially borrowed message to list.
     * @param pMessage the message
     */
    void addBorrowedMessage(final ArduinoMessage pMessage) {
        /* Only add to borrowed if it is not owned by this node */
        if (!theMessages.contains(pMessage)) {
            theBorrowed.add(pMessage);
        }
    }

    /**
     * Obtain the borrowed messages sent by this node.
     * @return the borrowed messages
     */
    public List<ArduinoMessage> getBorrowed() {
        return theBorrowed;
    }

    /**
     * Set value for attribute.
     * @param pAttr the attribute
     * @param pValue the value
     * @throws ArduinoParserException on error
     */
    void setAttrValue(final ArduinoAttribute pAttr,
                      final Object pValue) throws ArduinoParserException {
        /* Check that this is not a duplicate */
        if (theAttributes.containsKey(pAttr)) {
            throw new ArduinoParserException("Duplicate Attribute", pAttr.getName());
        }

        /* Store the value */
        theAttributes.put(pAttr, pValue);
    }

    @Override
    public Object getAttrValue(final ArduinoAttribute pAttr) {
        final Object myValue = theAttributes.get(pAttr);
        return myValue == null ? pAttr.getDefault() : myValue;
    }

    /**
     * Set value for relation attribute.
     * @param pAttr the attribute
     * @param pRelation the relation
     * @param pValue the value
     * @throws ArduinoParserException on error
     */
    void setRelationValue(final ArduinoAttribute pAttr,
                          final ArduinoAttrObject pRelation,
                          final Object pValue) throws ArduinoParserException {
        /* Access the map for this attribute and creat if it doesn't exist */
        final Map<ArduinoAttrObject, Object> myMap = theRelations.computeIfAbsent(pAttr, a -> new HashMap<>());

        /* Check that this is not a duplicate */
        if (myMap.containsKey(pRelation)) {
            throw new ArduinoParserException("Duplicate Relation Attribute", pAttr.getName());
        }

        /* Store the value */
        myMap.put(pRelation, pValue);
    }

    /**
     * Obtain value for relation attribute.
     * @param pAttr the attribute
     * @param pRelation the relation
     * @return the value
     */
    public Object getRelationValue(final ArduinoAttribute pAttr,
                                   final ArduinoAttrObject pRelation) {
        final Map<ArduinoAttrObject, Object> myMap = theRelations.get(pAttr);
        final Object myValue = myMap == null ? null : myMap.get(pRelation);
        return myValue == null ? pAttr.getDefault() : myValue;
    }

    /**
     * find message by id.
     * @param pId the message Id.
     * @return the message
     */
    ArduinoMessage findMessageById(final String pId) {
        /* Loop through the list */
        for (ArduinoMessage myMessage : theMessages) {
            if (myMessage.getId().equals(pId)) {
                return myMessage;
            }
        }
        return null;
    }

    /**
     * does the node send the message?
     * @param pMessage the message
     * @return true/false
     */
    public boolean sendsMessage(final ArduinoMessage pMessage) {
        /* Message must be owned or borrowed by node */
        return theMessages.contains(pMessage)
                || theBorrowed.contains(pMessage);
    }

    /**
     * does the node send the signal?
     * @param pSignal the signal
     * @return true/false
     */
    public boolean sendsSignal(final ArduinoSignal pSignal) {
        /* sends signal if it sends the owning message */
        final ArduinoMessage myMessage = pSignal.getOwner();
        return sendsMessage(myMessage);
    }

    /**
     * does the receive the message?
     * @param pMessage the message
     * @return true/false
     */
    public boolean receivesMessage(final ArduinoMessage pMessage) {
        /* Loop through the signals */
        for (ArduinoSignal mySignal : pMessage.getAllSignals()) {
            /* receives message if it receives any signal */
            if (receivesSignal(mySignal)) {
                return true;
            }
        }

        /* Doesn't receive it */
        return false;
    }

    /**
     * does the node receive the signal?
     * @param pSignal the signal
     * @return true/false
     */
    public boolean receivesSignal(final ArduinoSignal pSignal) {
        /* receives signal if it is the list of receivers */
        return pSignal.getReceivers().contains(this);
    }

    /**
     * Parse transmitters.
     * @param pSystem the system
     * @param pXmitDef the message representation
     * @throws ArduinoException on error
     */
    static void parseXmitNodes(final ArduinoSystem pSystem,
                               final String pXmitDef) throws ArduinoException {
        /* Split out header/xmitters */
        int myIndex = pXmitDef.indexOf(ArduinoChar.COLON);
        if (myIndex == -1) {
            throw new ArduinoException("Missing " + ArduinoChar.COLON + " separator", pXmitDef);
        }
        final String myHdr = pXmitDef.substring(0, myIndex);
        String myXmitters = pXmitDef.substring(myIndex + 1).trim();

        /* Marker is first token in header, id is remaining token */
        final String myMarker = ArduinoParser.nextToken(myHdr);
        final String myMsgId = ArduinoParser.stripToken(myHdr, myMarker);
        if (!myMarker.equals(MARKER_TX)) {
            throw new ArduinoException("Invalid marker", pXmitDef);
        }

        /* Check that the end character is semicolon */
        final int myLen = myXmitters.length();
        if (myXmitters.length() == 0 || myXmitters.charAt(myLen - 1) != ArduinoChar.SEMICOLON) {
            throw new ArduinoException("Missing Attr terminator", pXmitDef);
        }
        myXmitters = myXmitters.substring(0, myLen - 1);

        /* Protect against exceptions */
        try {
            /* Locate the message */
            final ArduinoMessage myMessage = pSystem.findMessageById(myMsgId);

            /* Loop through the tokens */
            while (myXmitters.length() > 0) {
                /* Split on comma separator */
                myIndex = myXmitters.indexOf(ArduinoChar.COMMA);
                if (myIndex == -1) {
                    pSystem.findNodeByName(myXmitters.trim()).addBorrowedMessage(myMessage);
                    return;
                }
                final String myNode = myXmitters.substring(0, myIndex).trim();
                pSystem.findNodeByName(myNode).addBorrowedMessage(myMessage);
                myXmitters = myXmitters.substring(myIndex + 1);
            }

            /* Handle parser exceptions */
        } catch (ArduinoParserException e) {
            throw new ArduinoException(e.getMessage() + ArduinoChar.COLON + e.getDetail(), pXmitDef);
        }
    }

    @Override
    public boolean equals(final Object pThat) {
        /* Handle trivial cases */
        if (pThat == this) {
            return true;
        } else if (!(pThat instanceof ArduinoNode)) {
            return false;
        }

        /* Access correctly */
        final ArduinoNode myThat = (ArduinoNode) pThat;

        /* Check name and owner */
        return theName.equals(myThat.getName())
                && theOwner.equals(myThat.getOwner());
    }

    @Override
    public int hashCode() {
        return Objects.hash(theName, theOwner);
    }

    @Override
    public String toString() {
        return theName;
    }
}