ArduinoGenerator.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.List;
import java.util.Map.Entry;
/**
* Format the c++ sketch.
*/
public final class ArduinoGenerator {
/**
* Private constructor.
*/
private ArduinoGenerator() {
}
/**
* Format system header.
* @param pSystem the system
* @param pFilter the filter
* @return the formatted system
*/
public static String formatHeader(final ArduinoSystem pSystem,
final ArduinoMsgFilter pFilter) {
/* Create builder */
final StringBuilder myBuilder = new StringBuilder();
/* Initialise the details */
final String myResult = updateTemplate(ArduinoTemplate.HEADER, pSystem);
/* Loop through the messages */
for (ArduinoMessage myMessage : pSystem.getMessages()) {
/* If the message is selected */
if (pFilter.isSelected(myMessage.getId())) {
/* format the message */
myBuilder.append(formatMessageHeader(myMessage, pFilter));
}
}
/* Update the template and return */
return myResult.replace(ArduinoVar.HDRCLASSES, myBuilder.toString());
}
/**
* Format system header.
* @param pSystem the system
* @param pFilter the filter
* @return the formatted system
*/
public static String formatBody(final ArduinoSystem pSystem,
final ArduinoMsgFilter pFilter) {
/* Create builder */
final StringBuilder myBuilder = new StringBuilder();
/* Initialise the details */
final String myResult = updateTemplate(ArduinoTemplate.BODY, pSystem);
/* Loop through the messages */
for (ArduinoMessage myMessage : pSystem.getMessages()) {
/* If the message is selected */
if (pFilter.isSelected(myMessage.getId())) {
/* format the message */
myBuilder.append(formatMessageBody(myMessage, pFilter));
}
}
/* Update the template and return */
return myResult.replace(ArduinoVar.BDYCLASSES, myBuilder.toString())
.replace(ArduinoVar.MSGCASES, buildMsgCases(pSystem, pFilter));
}
/**
* Format message header.
* @param pMessage the message
* @param pFilter the filter
* @return the formatted message
*/
private static String formatMessageHeader(final ArduinoMessage pMessage,
final ArduinoMsgFilter pFilter) {
/* Create builder */
final StringBuilder myBuilder = new StringBuilder();
/* Determine the parts to format */
final boolean parseMsg = pFilter.isParsed(pMessage.getId());
final boolean buildMsg = pFilter.isBuilt(pMessage.getId());
/* Update standard message details */
String myBase = ArduinoTemplate.HDRCLASS.replace(ArduinoVar.PARSEHDR, parseMsg ? ArduinoTemplate.HDRPARSECLASS : "");
myBase = myBase.replace(ArduinoVar.BUILDHDR, buildMsg ? ArduinoTemplate.HDRBUILDCLASS : "");
myBuilder.append(formatClassHeader(myBase, pFilter, pMessage, ArduinoSignal.MULTI_NONE, pMessage.getSignals()));
/* If we are a multiplex message */
if (pMessage.hasMultiplex()) {
/* Loop through the multiplexes */
for (Entry<Long, List<ArduinoSignal>> myEntry : pMessage.getMultiplexMap().entrySet()) {
/* Format the multiplex class */
myBase = ArduinoTemplate.HDRMULTICLASS.replace(ArduinoVar.PARSEHDR, parseMsg ? ArduinoTemplate.HDRPARSECLASS : "");
myBase = myBase.replace(ArduinoVar.BUILDHDR, buildMsg ? ArduinoTemplate.HDRBUILDMULTICLASS : "");
myBuilder.append(formatClassHeader(myBase, pFilter, pMessage, myEntry.getKey(), myEntry.getValue()));
}
}
/* Return the result */
return myBuilder.toString();
}
/**
* Format message body.
* @param pMessage the message
* @param pFilter the filter
* @return the formatted message
*/
private static String formatMessageBody(final ArduinoMessage pMessage,
final ArduinoMsgFilter pFilter) {
/* Create builder */
final StringBuilder myBuilder = new StringBuilder();
/* Determine the parts to format */
final boolean parseMsg = pFilter.isParsed(pMessage.getId());
final boolean buildMsg = pFilter.isBuilt(pMessage.getId());
/* Update standard message details */
String myBase = ArduinoTemplate.BDYCLASS.replace(ArduinoVar.PARSEBDY + ArduinoChar.LF, parseMsg ? ArduinoTemplate.BDYPARSECLASS : "");
myBase = myBase.replace(ArduinoVar.BUILDBDY, buildMsg ? ArduinoTemplate.BDYBUILDCLASS : "");
myBuilder.append(formatClassBody(myBase, pMessage, ArduinoSignal.MULTI_NONE, pMessage.getSignals()));
/* If we are a multiplex message */
if (pMessage.hasMultiplex()) {
/* Loop through the multiplexes */
for (Entry<Long, List<ArduinoSignal>> myEntry : pMessage.getMultiplexMap().entrySet()) {
/* Format the multiplex class */
myBase = ArduinoTemplate.BDYMULTICLASS.replace(ArduinoVar.PARSEBDY + ArduinoChar.LF, parseMsg ? ArduinoTemplate.BDYPARSEMULTICLASS : "");
myBase = myBase.replace(ArduinoVar.BUILDBDY, buildMsg ? ArduinoTemplate.BDYBUILDMULTICLASS : "");
myBuilder.append(formatClassBody(myBase, pMessage, myEntry.getKey(), myEntry.getValue()));
}
}
/* Return the result */
return myBuilder.toString();
}
/**
* Format class header.
* @param pTemplate the template
* @param pFilter the filter
* @param pMessage the message
* @param pMultiplex the multiplex value
* @param pSignals the signals
* @return the formatted class
*/
private static String formatClassHeader(final String pTemplate,
final ArduinoMsgFilter pFilter,
final ArduinoMessage pMessage,
final Long pMultiplex,
final List<ArduinoSignal> pSignals) {
/* determine whether we have public fields */
final boolean pubFields = pFilter.publicFields();
/* Update signal details */
String myResult = pTemplate.replace(ArduinoVar.HDRSETTERS, pubFields ? "" : formatSignals(ArduinoTemplate.HDRSETTER, pSignals));
myResult = myResult.replace(ArduinoVar.HDRGETTERS, pubFields ? "" : formatSignals(ArduinoTemplate.HDRGETTER, pSignals));
myResult = myResult.replace(ArduinoVar.PUBFIELDS, pubFields ? "private:\n " : "");
myResult = myResult.replace(ArduinoVar.PRIVFIELDS, pubFields ? "\n" : "\n private:\n");
myResult = myResult.replace(ArduinoVar.HDRFIELDS, formatSignals(ArduinoTemplate.HDRFIELD, pSignals));
myResult = myResult.replace(ArduinoVar.HDRFIELDDEFS, formatDefSignals(ArduinoTemplate.HDRFIELDDEF, pSignals));
/* Determine the class name */
String myClass = pMessage.getName();
if (!ArduinoSignal.MULTI_NONE.equals(pMultiplex)) {
/* Adjust class name */
myClass += "_m" + pMultiplex;
/* Override the multiplex setter */
myResult = myResult.replace(ArduinoVar.MUXSETTER, pubFields ? "" : updateTemplate(ArduinoTemplate.MUXSETTER, pMessage.getMultiplexSignal()));
myResult = myResult.replace(ArduinoVar.MUXSET, updateTemplate(pMessage.getMultiplexSignal(), pFilter, pMultiplex));
}
/* Update the message/class */
myResult = updateTemplate(myResult, pMessage);
myResult = myResult.replace(ArduinoVar.CLASSNAME, myClass);
/* Return the result */
return myResult;
}
/**
* Format class body.
* @param pTemplate the template
* @param pMessage the message
* @param pMultiplex the multiplex value
* @param pSignals the signals
* @return the formatted class
*/
private static String formatClassBody(final String pTemplate,
final ArduinoMessage pMessage,
final Long pMultiplex,
final List<ArduinoSignal> pSignals) {
/* Update signal details */
String myResult = pTemplate.replace(ArduinoVar.BDYREADERS, formatSignals(ArduinoTemplate.BDYPARSER, pSignals));
myResult = myResult.replace(ArduinoVar.BDYWRITERS, formatSignals(ArduinoTemplate.BDYWRITER, pSignals));
myResult = myResult.replace(ArduinoVar.BDYFIELDDEFS, formatDefSignals(ArduinoTemplate.BDYFIELDDEF, pSignals));
/* Determine the class name */
String myClass = pMessage.getName();
if (!ArduinoSignal.MULTI_NONE.equals(pMultiplex)) {
/* Adjust class name */
myClass += "_m" + pMultiplex;
}
/* Update message/class */
myResult = updateTemplate(myResult, pMessage);
myResult = myResult.replace(ArduinoVar.CLASSNAME, myClass);
/* Return the result */
return myResult;
}
/**
* Format signals.
* @param pTemplate the template
* @param pSignals the signals
* @return the formatted signals
*/
private static String formatSignals(final String pTemplate,
final List<ArduinoSignal> pSignals) {
/* Create builder */
final StringBuilder myBuilder = new StringBuilder();
/* Loop through the signals */
for (ArduinoSignal mySignal : pSignals) {
myBuilder.append(updateTemplate(pTemplate, mySignal));
}
/* Return the signals */
return myBuilder.toString();
}
/**
* Format definitions for signals.
* @param pTemplate the template
* @param pSignals the signals
* @return the formatted signals
*/
private static String formatDefSignals(final String pTemplate,
final List<ArduinoSignal> pSignals) {
/* Create builder */
final StringBuilder myBuilder = new StringBuilder();
/* Loop through the signals */
for (ArduinoSignal mySignal : pSignals) {
myBuilder.append(updateDefTemplate(pTemplate, mySignal));
}
/* Return the signals */
return myBuilder.toString();
}
/**
* Update a template for a system.
* @param pTemplate the template
* @param pSystem the system
* @return the updated template
*/
private static String updateTemplate(final String pTemplate,
final ArduinoSystem pSystem) {
return pTemplate.replace(ArduinoVar.SYSNAME, pSystem.getName())
.replace(ArduinoVar.SYSCNAME, pSystem.getCName());
}
/**
* Update a template for a message.
* @param pTemplate the template
* @param pMessage the message
* @return the updated template
*/
private static String updateTemplate(final String pTemplate,
final ArduinoMessage pMessage) {
return pTemplate.replace(ArduinoVar.MSGNAME, pMessage.getName())
.replace(ArduinoVar.MSGID, pMessage.getId())
.replace(ArduinoVar.MSGLEN, Integer.toString(pMessage.getLength()));
}
/**
* Update a template for a signal.
* @param pTemplate the template
* @param pSignal the signal
* @return the updated template
*/
private static String updateTemplate(final String pTemplate,
final ArduinoSignal pSignal) {
final boolean isFloat = pSignal.isFloat();
final String myClass = isFloat ? "Double" : "Long";
final boolean unBounded = pSignal.getRange().unBounded();
final String myBounds = unBounded ? "" : "Bounded";
return pTemplate.replace(ArduinoVar.FIELDNAME, pSignal.getName())
.replace(ArduinoVar.FIELDCLASS, myClass)
.replace(ArduinoVar.FIELDBOUND, myBounds)
.replace(ArduinoVar.FIELDTYPE, getFieldType(pSignal));
}
/**
* Update a template for a signal.
* @param pSignal the signal
* @param pFilter the filter
* @param pMultiplex the multiplex value
* @return the updated template
*/
private static String updateTemplate(final ArduinoSignal pSignal,
final ArduinoMsgFilter pFilter,
final Long pMultiplex) {
/* determine whether we have public fields */
final boolean pubFields = pFilter.publicFields();
final String myBase = updateTemplate(pubFields ? ArduinoTemplate.MUXSETPUB : ArduinoTemplate.MUXSETPRIV, pSignal);
return myBase.replace(ArduinoVar.MUXVALUE, pMultiplex.toString());
}
/**
* Update a definition template for a signal.
* @param pTemplate the template
* @param pSignal the signal
* @return the updated template
*/
private static String updateDefTemplate(final String pTemplate,
final ArduinoSignal pSignal) {
final boolean isFloat = pSignal.isFloat();
final String myClass = isFloat ? "Double" : "Long";
return pTemplate.replace(ArduinoVar.DEFSTART, Integer.toString(pSignal.getDefinition().getStartBit()))
.replace(ArduinoVar.DEFLENGTH, Integer.toString(pSignal.getDefinition().getBitLength()))
.replace(ArduinoVar.DEFENDIAN, Boolean.toString(pSignal.getDefinition().getDataType().isLittleEndian()))
.replace(ArduinoVar.DEFSIGNED, Boolean.toString(pSignal.getDefinition().getDataType().isSigned()))
.replace(ArduinoVar.DEFFACTOR, pSignal.getFactor().getFactor().toString())
.replace(ArduinoVar.DEFOFFSET, pSignal.getFactor().getOffset().toString())
.replace(ArduinoVar.FIELDNAME, pSignal.getName())
.replace(ArduinoVar.FIELDCLASS, myClass)
.replace(ArduinoVar.DEFMIN, pSignal.getRange().getMinimum().toString())
.replace(ArduinoVar.DEFMAX, pSignal.getRange().getMaximum().toString());
}
/**
* Build message cases for the parser.
* @param pSystem the system
* @param pFilter the filter
* @return the cases.
*/
private static String buildMsgCases(final ArduinoSystem pSystem,
final ArduinoMsgFilter pFilter) {
/* Create the builder */
final StringBuilder myBuilder = new StringBuilder();
/* Loop through the messages */
for (ArduinoMessage myMessage : pSystem.getMessages()) {
/* If the message is not parsed, skip it */
if (!pFilter.isParsed(myMessage.getId())) {
continue;
}
/* If this is a standard message */
if (myMessage.hasMultiplex()) {
myBuilder.append(buildMultiplexCases(myMessage, pFilter));
} else {
myBuilder.append(updateTemplate(ArduinoTemplate.PRSMSGCASE, myMessage));
}
}
/* Return the parser */
return myBuilder.toString();
}
/**
* Build multiplex message cases for the parser.
* @param pMessage the multiplex message
* @param pFilter the filter
* @return the case.
*/
private static String buildMultiplexCases(final ArduinoMessage pMessage,
final ArduinoMsgFilter pFilter) {
/* Create the builder */
final StringBuilder myBuilder = new StringBuilder();
/* determine whether we have public fields */
final boolean pubFields = pFilter.publicFields();
/* Create the multiplex cases */
for (Long myId : pMessage.getMultiplexMap().keySet()) {
/* Add the case */
myBuilder.append(ArduinoTemplate.PRSMULTIMSGCASE.replace(ArduinoVar.MUXVALUE, myId.toString()));
}
/* Fold in to the case */
final String myCase = ArduinoTemplate.PRSMULTIMSG
.replace(ArduinoVar.MUXSWITCH, pubFields ? ArduinoTemplate.MUXSWITCHPUB : ArduinoTemplate.MUXSWITCHPRIV)
.replace(ArduinoVar.MUXCASES, myBuilder.toString());
return updateTemplate(myCase, pMessage).replace(ArduinoVar.FIELDNAME, pMessage.getMultiplexSignal().getName());
}
/**
* Obtain the fieldType.
* @param pSignal the signal
* @return the fieldType
*/
private static String getFieldType(final ArduinoSignal pSignal) {
/* Handle float */
if (pSignal.isFloat()) {
return "double";
}
/* Obtain bitLength of signal */
final int myBitLen = pSignal.getDefinition().getBitLength();
if (myBitLen == 1) {
return "bool";
}
final int myByteLen = 1 + ((myBitLen - 1) / Byte.SIZE);
/* Return the correct sign/length of int */
final String mySign = pSignal.isSigned() ? "" : "unsigned ";
if (myByteLen == Byte.BYTES || myByteLen == Short.BYTES) {
return mySign + "short";
} else if (myByteLen <= Integer.BYTES) {
return mySign + "long";
}
return mySign + "long long";
}
}