ArduinoMessage.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.LinkedHashMap;
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;

/**
 * Arduino Signal Message.
 */
public class ArduinoMessage
        implements ArduinoNamedObject, ArduinoAttrObject {
    /**
     * The Marker.
     */
    static final String MARKER = "BO_";

    /**
     * The id of the message.
     */
    private final String theId;

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

    /**
     * The length of the message.
     */
    private final int theLength;

    /**
     * The sending node.
     */
    private final ArduinoNode theSender;

    /**
     * the list of signals.
     */
    private final List<ArduinoSignal> theSignals;

    /**
     * Does the message have any float signals?
     */
    private boolean hasFloat;

    /**
     * the multiplex signal.
     */
    private ArduinoSignal theMultiplexSignal;

    /**
     * the map of multiplex signals.
     */
    private final Map<Long, List<ArduinoSignal>> theMultiplex;

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

    /**
     * Constructor.
     * @param pSender the sender node
     * @param pMsgId the messageId
     * @param pName the name
     * @param pLength the length of the message
     */
    ArduinoMessage(final ArduinoNode pSender,
                   final String pMsgId,
                   final String pName,
                   final int pLength) {
        /* Store parameters */
        theSender = pSender;
        theId = pMsgId;
        theName = pName;
        theLength = pLength;

        /* Create the lists and map */
        theSignals = new ArrayList<>();
        theMultiplex = new LinkedHashMap<>();
        theAttributes = new HashMap<>();
    }

    /**
     * Add signal.
     * @param pSignal the signal
     */
    public void addSignal(final ArduinoSignal pSignal) {
        /* Adjust float indication */
        hasFloat |= pSignal.isFloat() && !pSignal.getRange().unBounded();

        /* If this is a main signal */
        final Long myMulti = pSignal.getMultiplexId();
        if (myMulti.equals(ArduinoSignal.MULTI_NONE)) {
            /* Add to main list */
            theSignals.add(pSignal);

            /* Record multiplex signal (if any) */
            if (pSignal.isMultiplex()) {
                theMultiplexSignal = pSignal;
            }

            /* Else add to the appropriate multiplex list */
        } else {
            final List<ArduinoSignal> myList = theMultiplex.computeIfAbsent(myMulti, m -> new ArrayList<>());
            myList.add(pSignal);
        }
    }

    /**
     * Obtain the id.
     * @return the id
     */
    public String getId() {
        return theId;
    }

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

    /**
     * Obtain the length.
     * @return the length
     */
    public int getLength() {
        return theLength;
    }

    /**
     * Obtain the sender.
     * @return the sender
     */
    public ArduinoNode getSender() {
        return theSender;
    }

    /**
     * Obtain the system.
     * @return the system
     */
    public ArduinoSystem getSystem() {
        return theSender.getOwner();
    }

    /**
     * Obtain the non-multiplex signals.
     * @return the signals
     */
    public List<ArduinoSignal> getSignals() {
        return theSignals;
    }

    /**
     * Obtain a list of all the signals.
     * @return the signals
     */
    public List<ArduinoSignal> getAllSignals() {
        /* Create the list from the non-multiplex signals */
        final List<ArduinoSignal> myResult = new ArrayList<>(theSignals);

        /* Loop through the multiplex map */
        for (List<ArduinoSignal> myList : theMultiplex.values()) {
            /* Add the multiplex signals */
            myResult.addAll(myList);
        }

        /* Return the list */
        return myResult;
    }

    /**
     * Does this message have float signals?
     * @return true/false
     */
    public boolean hasFloat() {
        return hasFloat;
    }

    /**
     * Does the message have multiplex versions?
     * @return true/false
     */
    public boolean hasMultiplex() {
        return !theMultiplex.isEmpty();
    }

    /**
     * Obtain the multiplex signal.
     * @return the multiplex signal
     */
    public ArduinoSignal getMultiplexSignal() {
        return theMultiplexSignal;
    }

    /**
     * Obtain the multiplex map.
     * @return the map
     */
    public Map<Long, List<ArduinoSignal>> getMultiplexMap() {
        return theMultiplex;
    }

    /**
     * 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;
    }

    /**
     * find signal by name.
     * @param pName the signal name.
     * @return the signal
     * @throws ArduinoParserException on error
     */
    ArduinoSignal findSignalByName(final String pName) throws ArduinoParserException {
        /* Loop through the list */
        for (ArduinoSignal mySignal : theSignals) {
            if (pName.equals(mySignal.getName())) {
                return mySignal;
            }
        }

        /* Loop through the multiplex map */
        for (List<ArduinoSignal> myList : theMultiplex.values()) {
            /* Loop through the list */
            for (ArduinoSignal mySignal : myList) {
                if (pName.equals(mySignal.getName())) {
                    return mySignal;
                }
            }
        }

        /* Not found */
        throw new ArduinoParserException("Unknown signal", pName);
    }

    /**
     * Parse message.
     * @param pSystem the system
     * @param pMessageDef the message representation
     * @return the message
     * @throws ArduinoException on error
     */
    static ArduinoMessage parseMessage(final ArduinoSystem pSystem,
                                       final String pMessageDef) throws ArduinoException {
        /* Split out header/definition */
        final int myIndex = pMessageDef.indexOf(ArduinoChar.COLON);
        if (myIndex == -1) {
            throw new ArduinoException("Missing " + ArduinoChar.COLON + " separator", pMessageDef);
        }
        String myHdr = pMessageDef.substring(0, myIndex);
        final String myLine = pMessageDef.substring(myIndex + 1).trim();

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

        /* Id is next token */
        final String myId = ArduinoParser.nextToken(myHdr);
        myHdr = ArduinoParser.stripToken(myHdr, myId);

        /* Name is final token in header */
        final String myName = ArduinoParser.nextToken(myHdr);

        /* Protect against exceptions */
        try {
            /* Length is first token in line */
            final String myLen = ArduinoParser.nextToken(myLine);
            final String mySenderNode = ArduinoParser.stripToken(myLine, myLen);
            final int myLength = ArduinoParser.parseNumber(myLen).intValue();
            final ArduinoNode mySender = pSystem.findNodeByName(mySenderNode);

            /* Create the message and add to list */
            final ArduinoMessage myMessage = new ArduinoMessage(mySender, myId, myName, myLength);
            mySender.addMessage(myMessage);

            /* Return the message */
            return myMessage;

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

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

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

        /* Check message id/name */
        return theId.equals(myThat.getId())
                && theName.equals(myThat.getName())
                && theSender.equals(myThat.getSender());
    }

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

    @Override
    public String toString() {
        /* Handle headers */
        final StringBuilder myBuilder = new StringBuilder();
        myBuilder.append(MARKER);
        myBuilder.append(ArduinoChar.BLANK);
        myBuilder.append(theId);
        myBuilder.append(ArduinoChar.BLANK);
        myBuilder.append(theName);
        myBuilder.append(ArduinoChar.COLON);
        myBuilder.append(ArduinoChar.BLANK);
        myBuilder.append(theLength);
        myBuilder.append(ArduinoChar.BLANK);
        myBuilder.append(theSender);
        myBuilder.append(ArduinoChar.LF);

        /* Loop through the signals */
        for (ArduinoSignal theSignal : theSignals) {
            myBuilder.append(ArduinoChar.BLANK);
            myBuilder.append(theSignal);
            myBuilder.append(ArduinoChar.LF);
        }

        /* Loop through the map */
        for (List<ArduinoSignal> myList : theMultiplex.values()) {
            /* Loop through the signals */
            for (ArduinoSignal arduinoSignal : myList) {
                myBuilder.append(ArduinoChar.BLANK);
                myBuilder.append(arduinoSignal);
                myBuilder.append(ArduinoChar.LF);
            }
        }

        /* return the string */
        return myBuilder.toString();
    }
}