ArduinoSignal.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;

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

    /**
     * The multiplex1 character.
     */
    private static final char MULTI1 = 'M';

    /**
     * The multiplex2 character.
     */
    private static final char MULTI2 = 'm';

    /**
     * The null multiplexId.
     */
    public static final Long MULTI_NONE = -1L;

    /**
     * the owning message.
     */
    private final ArduinoMessage theOwner;

    /**
     * the name of the signal.
     */
    private final String theName;

    /**
     * the units of the signal.
     */
    private final String theUnits;

    /**
     * the definition of the signal.
     */
    private final ArduinoSignalDefinition theDefinition;

    /**
     * the factor of the signal.
     */
    private final ArduinoSignalFactor theFactor;

    /**
     * the range of the signal.
     */
    private final ArduinoSignalRange theRange;

    /**
     * the list of receiving Nodes.
     */
    private final List<ArduinoNode> theReceivers;

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

    /**
     * the values.
     */
    private ArduinoValues theValues;

    /**
     * is this the multiplex signal.
     */
    private boolean isMultiplex;

    /**
     * the multiplex id.
     */
    private Long theMultiplexId;

    /**
     * Constructor.
     * @param pOwner the owner
     * @param pName the name
     * @param pDef the definition
     * @param pFactor the factor
     * @param pRange the range
     * @param pUnits the units
     */
    private ArduinoSignal(final ArduinoMessage pOwner,
                          final String pName,
                          final ArduinoSignalDefinition pDef,
                          final ArduinoSignalFactor pFactor,
                          final ArduinoSignalRange pRange,
                          final String pUnits) {
        /* Store parameters */
        theOwner = pOwner;
        theName = pName;
        theDefinition = pDef;
        theFactor = pFactor;
        theRange = pRange;
        theUnits = pUnits;
        isMultiplex = false;
        theMultiplexId = MULTI_NONE;

        /* Create the lists and maps */
        theReceivers = new ArrayList<>();
        theAttributes  = new HashMap<>();
    }


    /**
     * Parse the receiver nodes.
     * @param pSystem the system
     * @param pReceivers the receivers string
     * @throws ArduinoParserException on error
     */
    private void parseReceivers(final ArduinoSystem pSystem,
                                final String pReceivers) throws ArduinoParserException {
        /* Loop through the tokens */
        String myReceivers = pReceivers;
        while (myReceivers.length() > 0) {
            /* Split on comma separator */
            final int myIndex = myReceivers.indexOf(ArduinoChar.COMMA);
            if (myIndex == -1) {
                theReceivers.add(pSystem.findNodeByName(myReceivers.trim()));
                return;
            }
            final String myNode = myReceivers.substring(0, myIndex).trim();
            theReceivers.add(pSystem.findNodeByName(myNode));
            myReceivers = myReceivers.substring(myIndex + 1);
        }
    }

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

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

    /**
     * Obtain the units.
     * @return the units
     */
    public String getUnits() {
        return theUnits;
    }

    /**
     * Obtain the definition.
     * @return the definition
     */
    public ArduinoSignalDefinition getDefinition() {
        return theDefinition;
    }

    /**
     * Obtain the factor.
     * @return the factor
     */
    public ArduinoSignalFactor getFactor() {
        return theFactor;
    }

    /**
     * Obtain the range.
     * @return the range
     */
    public ArduinoSignalRange getRange() {
        return theRange;
    }

    /**
     * Does this signal use floats?
     * @return true/false
     */
    public boolean isFloat() {
        return theFactor.isFloat() || theRange.isFloat();
    }

    /**
     * Does this signal use signed ints?
     * @return true/false
     */
    public boolean isSigned() {
        return !isFloat() && theDefinition.getDataType().isSigned();
    }

    /**
     * Obtain values.
     * @return the values
     */
    public ArduinoValues getValues() {
        return theValues;
    }

    /**
     * set values.
     * @param pValues the values
     */
    void setValues(final ArduinoValues pValues) {
        theValues = pValues;
    }

    /**
     * Is this the multiplex signal.
     * @return true/false
     */
    public boolean isMultiplex() {
        return isMultiplex;
    }

    /**
     * set multiplex.
     */
    private void setMultiplex() {
        isMultiplex = true;
    }

    /**
     * Obtain the multiplexId.
     * @return the Id
     */
    public Long getMultiplexId() {
        return theMultiplexId;
    }

    /**
     * Set the multiplexId.
     * @param pId the multiplexId
     */
    public void setMultiplexId(final Long pId) {
        theMultiplexId = pId;
    }

    /**
     * Obtain the receivers.
     * @return the receivers
     */
    public List<ArduinoNode> getReceivers() {
        return theReceivers;
    }

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

    /**
     * Parse signal.
     * @param pOwner the owning message
     * @param pSignalDef the signal representation
     * @throws ArduinoException on error
     */
    static void parseSignal(final ArduinoMessage pOwner,
                            final String pSignalDef) throws ArduinoException {
        /* Split out header/signal */
        final int myIndex = pSignalDef.indexOf(ArduinoChar.COLON);
        if (myIndex == -1) {
            throw new ArduinoException("Missing " + ArduinoChar.COLON + " separator", pSignalDef);
        }
        String myHdr = pSignalDef.substring(0, myIndex);
        String myLine = pSignalDef.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", pSignalDef);
        }

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

        /* Protect against exceptions */
        try {
            /* Definition is first token in line */
            final String myDataDef = ArduinoParser.nextToken(myLine);
            myLine = ArduinoParser.stripToken(myLine, myDataDef);
            final ArduinoSignalDefinition myDef = ArduinoSignalDefinition.parseDefinition(myDataDef);

            /* Factor is next token */
            final String myFactorDef = ArduinoParser.nextToken(myLine);
            myLine = ArduinoParser.stripToken(myLine, myFactorDef);
            final ArduinoSignalFactor myFactor = ArduinoSignalFactor.parseFactors(myFactorDef);

            /* Range is next token */
            final String myRangeDef = ArduinoParser.nextToken(myLine);
            myLine = ArduinoParser.stripToken(myLine, myRangeDef);
            final ArduinoSignalRange myRange = ArduinoSignalRange.parseRange(myRangeDef);

            /* Units is next token */
            final String myUnits = ArduinoParser.nextQuotedToken(myLine);
            myLine = ArduinoParser.stripQuotedToken(myLine, myUnits);

            /* Create the signal and parse the final token */
            final ArduinoSignal mySignal = new ArduinoSignal(pOwner, myName, myDef, myFactor, myRange, myUnits);
            mySignal.parseReceivers(pOwner.getSystem(), myLine);

            /* If we have a multiplex details */
            if (myHdr.length() > 0) {
                if (myHdr.charAt(0) == MULTI1) {
                    mySignal.setMultiplex();
                } else {
                    if (myHdr.charAt(0) != MULTI2) {
                        throw new ArduinoParserException("Invalid multiplex", myHdr);
                    }
                    mySignal.setMultiplexId((long) ArduinoParser.parseNumber(myHdr.substring(1)));
                }
            }

            /* Register with owner */
            pOwner.addSignal(mySignal);

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

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

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

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

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

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

        /* Handle multiplex */
        if (isMultiplex) {
            myBuilder.append(ArduinoChar.BLANK);
            myBuilder.append(MULTI1);
        } else if (!theMultiplexId.equals(MULTI_NONE)) {
            myBuilder.append(ArduinoChar.BLANK);
            myBuilder.append(MULTI2);
            myBuilder.append(theMultiplexId);
        }

        /* Append the detail */
        myBuilder.append(ArduinoChar.COLON);
        myBuilder.append(ArduinoChar.BLANK);
        myBuilder.append(theDefinition);
        myBuilder.append(ArduinoChar.BLANK);
        myBuilder.append(theFactor);
        myBuilder.append(ArduinoChar.BLANK);
        myBuilder.append(theRange);
        myBuilder.append(ArduinoChar.BLANK);
        myBuilder.append(ArduinoChar.QUOTE);
        myBuilder.append(theUnits);
        myBuilder.append(ArduinoChar.QUOTE);
        myBuilder.append(ArduinoChar.BLANK);

        /* Loop through the receivers */
        for (int i = 0; i < theReceivers.size(); i++) {
            if (i > 0) {
                myBuilder.append(ArduinoChar.COMMA);
            }
            myBuilder.append(theReceivers.get(i));
        }

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