ArduinoAttributes.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 net.sourceforge.jarduino.ArduinoException;
import net.sourceforge.jarduino.message.ArduinoAttribute.ArduinoAttrClass;
import net.sourceforge.jarduino.message.ArduinoAttribute.ArduinoAttrType;
import net.sourceforge.jarduino.message.ArduinoParser.ArduinoParserException;

/**
 * Attributes.
 */
public class ArduinoAttributes {
    /**
     * The Attribute Marker.
     */
    static final String MARKER = "BA_";

    /**
     * The Definition Marker.
     */
    static final String MARKER_DEF = "BA_DEF_";

    /**
     * The Default Marker.
     */
    static final String MARKER_DEFAULT = "BA_DEF_DEF_";

    /**
     * The Attribute Marker.
     */
    static final String MARKER_REL = MARKER + ArduinoAttribute.REL_MARKER;

    /**
     * The Definition Marker.
     */
    static final String MARKER_REL_DEF = MARKER_DEF + ArduinoAttribute.REL_MARKER;

    /**
     * The Default Marker.
     */
    static final String MARKER_REL_DEFAULT = MARKER_DEFAULT + ArduinoAttribute.REL_MARKER;

    /**
     * Attributes Map.
     */
    private final Map<String, ArduinoAttribute> theAttributes;

    /**
     * Attributes.
     */
    ArduinoAttributes() {
        /* Create the map */
        theAttributes = new HashMap<>();
    }

    /**
     * Obtain a list of all attributes in the system.
     * @return the attributes
     */
    public List<ArduinoAttribute> getAttributes() {
        return new ArrayList<>(theAttributes.values());
    }

    /**
      * Obtain attribute for name.
      * @param pName the name
      * @return the attribute
      */
    public ArduinoAttribute getAttributeForName(final String pName) {
        return theAttributes.get(pName);
    }

    /**
     * Store the attribute.
     * @param pAttr the attribute
     * @throws ArduinoException on error
     */
    void storeAttribute(final ArduinoAttribute pAttr) throws ArduinoException {
        /* Check that this is not a duplicate */
        if (theAttributes.containsKey(pAttr.getName())) {
            throw new ArduinoException("Duplicate Attribute " + pAttr.getName());
        }

        /* Store the attribute */
        theAttributes.put(pAttr.getName(), pAttr);
    }

    /**
     * parse the attribute definition.
     * @param pSystem the system
     * @param pMarker the marker
     * @param pAttrDef the attribute definition
     * @throws ArduinoException on error
     */
    public static void parseAttributeDef(final ArduinoSystem pSystem,
                                         final String pMarker,
                                         final String pAttrDef) throws ArduinoException {
        /* Marker is first token */
        final String myMarker = ArduinoParser.nextToken(pAttrDef);
        String myDef = ArduinoParser.stripToken(pAttrDef, myMarker);
        if (!myMarker.equals(pMarker)) {
            throw new ArduinoException("Invalid AttrDef marker", pAttrDef);
        }

        /* Attribute class is next token (if present) */
        final boolean isRelation = MARKER_REL_DEF.equals(pMarker);
        final String myClassDef = ArduinoParser.nextToken(myDef);
        final ArduinoAttrClass myClass = ArduinoAttrClass.parseAttrClass(myClassDef);
        if (myClass.isRelation() != isRelation) {
            throw new ArduinoException("Relation mismatch for AttrDef", pAttrDef);
        }
        if (myClass != ArduinoAttrClass.SYSTEM) {
            myDef = ArduinoParser.stripToken(myDef, myClassDef);
        }

        /* Protect against exceptions */
        try {
            /* Access name */
            final String myName = ArduinoParser.nextQuotedToken(myDef);
            myDef = ArduinoParser.stripQuotedToken(myDef, myName);

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

            /* Attribute type is next token */
            final String myTypeDef = ArduinoParser.nextToken(myDef);
            final ArduinoAttrType myType = ArduinoAttrType.parseAttrType(myTypeDef);
            myDef = ArduinoParser.stripToken(myDef, myTypeDef);

            /* Create the new attribute and add to the map */
            final ArduinoAttribute myAttr = new ArduinoAttribute(myName, myClass, myType);
            myAttr.setConstraints(ArduinoAttrConstraints.parseConstraints(myAttr, myDef));
            pSystem.storeAttribute(myAttr);

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

    /**
     * parse the attribute default.
     * @param pSystem the system
     * @param pMarker the marker
     * @param pAttrDef the attribute default definition
     * @throws ArduinoException on error
     */
    public static void parseAttributeDefault(final ArduinoSystem pSystem,
                                             final String pMarker,
                                             final String pAttrDef) throws ArduinoException {
        /* Marker is first token */
        final String myMarker = ArduinoParser.nextToken(pAttrDef);
        String myDef = ArduinoParser.stripToken(pAttrDef, myMarker);
        if (!myMarker.equals(pMarker)) {
            throw new ArduinoException("Invalid AttrDefault marker", pAttrDef);
        }

        /* Protect against exceptions */
        try {
            /* Access name */
            final boolean isRelation = MARKER_REL_DEFAULT.equals(pMarker);
            final String myName = ArduinoParser.nextQuotedToken(myDef);
            myDef = ArduinoParser.stripQuotedToken(myDef, myName);

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

            /* Access the attribute */
            final ArduinoAttribute myAttr = pSystem.findAttributeByName(myName);
            if (myAttr.getAttrClass().isRelation() != isRelation) {
                throw new ArduinoException("Relation mismatch for AttrDefault", pAttrDef);
            }
            final Object myDefault = parseAttributeValue(myAttr, myDef);

            /* Set the default */
            myAttr.setDefault(myDefault);

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

    /**
     * parse the attribute value.
     * @param pAttr the attribute
     * @param pValueDef the attribute definition
     * @throws ArduinoParserException on error
     * @return the parsed value
     */
    private static Object parseAttributeValue(final ArduinoAttribute pAttr,
                                              final String pValueDef) throws ArduinoParserException {
        /* Switch on attribute type */
        Object myValue;
        switch (pAttr.getAttrType()) {
            case FLOAT:
            case INT:
            case HEX:
                myValue = ArduinoParser.parseNumber(pValueDef);
                break;
            case STRING:
                myValue = ArduinoParser.nextQuotedToken(pValueDef);
                break;
            case ENUM:
            default:
                myValue = pValueDef.charAt(0) == ArduinoChar.QUOTE
                        ? ArduinoParser.nextQuotedToken(pValueDef)
                        : ArduinoParser.parseNumber(ArduinoParser.nextToken(pValueDef));
                break;
        }

        /* If there are constraints */
        final ArduinoAttrConstraints myConstraints = pAttr.getConstraints();
        if (myConstraints != null) {
            /* Check against the constraints */
            myValue = myConstraints.checkValue(myValue);
        }

        /* Return the value */
        return myValue;
    }

    /**
     * parse the attribute.
     * @param pSystem the system
     * @param pMarker the marker
     * @param pAttrDef the attribute definition
     * @throws ArduinoException on error
     */
    public static void parseAttribute(final ArduinoSystem pSystem,
                                      final String pMarker,
                                      final String pAttrDef) throws ArduinoException {
        /* Marker is first token */
        final String myMarker = ArduinoParser.nextToken(pAttrDef);
        String myDef = ArduinoParser.stripToken(pAttrDef, myMarker);
        if (!myMarker.equals(pMarker)) {
            throw new ArduinoException("Invalid Attr marker", pAttrDef);
        }

        /* Protect against exceptions */
        try {
            /* Access name */
            final boolean isRelation = MARKER_REL.equals(pMarker);
            final String myName = ArduinoParser.nextQuotedToken(myDef);
            myDef = ArduinoParser.stripQuotedToken(myDef, myName);

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

            /* Access the attribute */
            final ArduinoAttribute myAttr = pSystem.findAttributeByName(myName);
            if (myAttr.getAttrClass().isRelation() != isRelation) {
                throw new ArduinoException("Relation mismatch for Attr", pAttrDef);
            }

            /* split processing based on class */
            switch (myAttr.getAttrClass()) {
                case NODE:
                    parseNodeAttribute(pSystem, myAttr, myDef);
                    break;
                case MESSAGE:
                    parseMessageAttribute(pSystem, myAttr, myDef);
                    break;
                case SIGNAL:
                    parseSignalAttribute(pSystem, myAttr, myDef);
                    break;
                case NODE2MSG:
                    parseNode2MsgAttribute(pSystem, myAttr, myDef);
                    break;
                case NODE2SIGNAL:
                    parseNode2SignalAttribute(pSystem, myAttr, myDef);
                    break;
                case SYSTEM:
                default:
                    parseSystemAttribute(pSystem, myAttr, myDef);
                    break;
            }

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

    /**
     * parse the system attribute.
     * @param pSystem the system
     * @param pAttr the attribute
     * @param pAttrDef the attribute definition
     * @throws ArduinoParserException on error
     */
    private static void parseSystemAttribute(final ArduinoSystem pSystem,
                                             final ArduinoAttribute pAttr,
                                             final String pAttrDef) throws ArduinoParserException {
        /* Parse and set value */
        final Object myValue = parseAttributeValue(pAttr, pAttrDef);
        pSystem.setAttrValue(pAttr, myValue);
    }

    /**
     * parse the node attribute.
     * @param pSystem the system
     * @param pAttr the attribute
     * @param pAttrDef the attribute definition
     * @throws ArduinoParserException on error
     */
    private static void parseNodeAttribute(final ArduinoSystem pSystem,
                                           final ArduinoAttribute pAttr,
                                           final String pAttrDef) throws ArduinoParserException {
        /* Marker is first token */
        final String myMarker = ArduinoParser.nextToken(pAttrDef);
        String myDef = ArduinoParser.stripToken(pAttrDef, myMarker);
        if (!myMarker.equals(ArduinoNode.MARKER)) {
            throw new ArduinoParserException("Invalid Node marker", pAttrDef);
        }

        /* Access name */
        final String myName = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myName);

        /* Locate the node and set the value */
        final ArduinoNode myNode = pSystem.findNodeByName(myName);
        final Object myValue = parseAttributeValue(pAttr, myDef);
        myNode.setAttrValue(pAttr, myValue);
    }

    /**
     * parse the message attribute.
     * @param pSystem the system
     * @param pAttr the attribute
     * @param pAttrDef the attribute definition
     * @throws ArduinoParserException on error
     */
    private static void parseMessageAttribute(final ArduinoSystem pSystem,
                                              final ArduinoAttribute pAttr,
                                              final String pAttrDef) throws ArduinoParserException {
        /* Marker is first token */
        final String myMarker = ArduinoParser.nextToken(pAttrDef);
        String myDef = ArduinoParser.stripToken(pAttrDef, myMarker);
        if (!myMarker.equals(ArduinoMessage.MARKER)) {
            throw new ArduinoParserException("Invalid Message marker", pAttrDef);
        }

        /* Access msgId */
        final String myMsgId = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myMsgId);

        /* Locate the message and set the value */
        final ArduinoMessage myMessage = pSystem.findMessageById(myMsgId);
        final Object myValue = parseAttributeValue(pAttr, myDef);
        myMessage.setAttrValue(pAttr, myValue);
    }

    /**
     * parse the node2message attribute.
     * @param pSystem the system
     * @param pAttr the attribute
     * @param pAttrDef the attribute definition
     * @throws ArduinoParserException on error
     */
    private static void parseNode2MsgAttribute(final ArduinoSystem pSystem,
                                               final ArduinoAttribute pAttr,
                                               final String pAttrDef) throws ArduinoParserException {
        /* Marker is first token */
        String myMarker = ArduinoParser.nextToken(pAttrDef);
        String myDef = ArduinoParser.stripToken(pAttrDef, myMarker);
        if (!myMarker.equals(ArduinoAttribute.NODE2MSG_MARKER)) {
            throw new ArduinoParserException("Invalid Node2Message marker", pAttrDef);
        }

        /* Access node */
        final String myName = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myName);
        final ArduinoNode myNode = pSystem.findNodeByName(myName);

        /* Access messagel marker */
        myMarker = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myMarker);
        if (!myMarker.equals(ArduinoMessage.MARKER)) {
            throw new ArduinoParserException("Invalid Node2Msg message marker", pAttrDef);
        }

        /* Access msgId */
        final String myMsgId = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myMsgId);

        /* Locate the message and set the value */
        final ArduinoMessage myMessage = pSystem.findMessageById(myMsgId);
        final Object myValue = parseAttributeValue(pAttr, myDef);
        if (!myNode.receivesMessage(myMessage)) {
            throw new ArduinoParserException("Impossible Node2Msg relationship", pAttrDef);
        }
        myNode.setRelationValue(pAttr, myMessage, myValue);
    }

    /**
     * parse the signal attribute.
     * @param pSystem the system
     * @param pAttr the attribute
     * @param pAttrDef the attribute definition
     * @throws ArduinoParserException on error
     */
    private static void parseSignalAttribute(final ArduinoSystem pSystem,
                                             final ArduinoAttribute pAttr,
                                             final String pAttrDef) throws ArduinoParserException {
        /* Marker is first token */
        final String myMarker = ArduinoParser.nextToken(pAttrDef);
        String myDef = ArduinoParser.stripToken(pAttrDef, myMarker);
        if (!myMarker.equals(ArduinoSignal.MARKER)) {
            throw new ArduinoParserException("Invalid Signal marker", pAttrDef);
        }

        /* Access msgId */
        final String myMsgId = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myMsgId);

        /* Access signal name */
        final String myName = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myName);

        /* Locate the signal */
        final ArduinoSignal mySignal = pSystem.findSignalByIdAndName(myMsgId, myName);
        final Object myValue = parseAttributeValue(pAttr, myDef);
        mySignal.setAttrValue(pAttr, myValue);
    }

    /**
     * parse the node2signal attribute.
     * @param pSystem the system
     * @param pAttr the attribute
     * @param pAttrDef the attribute definition
     * @throws ArduinoParserException on error
     */
    private static void parseNode2SignalAttribute(final ArduinoSystem pSystem,
                                                  final ArduinoAttribute pAttr,
                                                  final String pAttrDef) throws ArduinoParserException {
        /* Marker is first token */
        String myMarker = ArduinoParser.nextToken(pAttrDef);
        String myDef = ArduinoParser.stripToken(pAttrDef, myMarker);
        if (!myMarker.equals(ArduinoAttribute.NODE2SIG_MARKER)) {
            throw new ArduinoParserException("Invalid Node2Signal marker", pAttrDef);
        }

        /* Access node */
        String myName = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myName);
        final ArduinoNode myNode = pSystem.findNodeByName(myName);

        /* Access signal marker */
        myMarker = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myMarker);
        if (!myMarker.equals(ArduinoSignal.MARKER)) {
            throw new ArduinoParserException("Invalid Node2Signal signal marker", pAttrDef);
        }

        /* Access msgId */
        final String myMsgId = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myMsgId);

        /* Access signal name */
        myName = ArduinoParser.nextToken(myDef);
        myDef = ArduinoParser.stripToken(myDef, myName);

        /* Locate the signal */
        final ArduinoSignal mySignal = pSystem.findSignalByIdAndName(myMsgId, myName);
        final Object myValue = parseAttributeValue(pAttr, myDef);
        if (!myNode.receivesSignal(mySignal)) {
            throw new ArduinoParserException("Impossible Node2Signal relationship", pAttrDef);
        }
        myNode.setRelationValue(pAttr, mySignal, myValue);
    }
}