View Javadoc
1   /*******************************************************************************
2    * jArduino: Arduino C++ Code Generation From Java
3    * Copyright 2020 Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   ******************************************************************************/
17  package net.sourceforge.jarduino.message;
18  
19  import java.util.List;
20  import java.util.Map.Entry;
21  
22  /**
23   * Format the c++ sketch.
24   */
25  public final class ArduinoGenerator {
26      /**
27       * Private constructor.
28       */
29      private ArduinoGenerator() {
30      }
31  
32      /**
33       * Format system header.
34       * @param pSystem the system
35       * @param pFilter the filter
36       * @return the formatted system
37       */
38      public static String formatHeader(final ArduinoSystem pSystem,
39                                        final ArduinoMsgFilter pFilter) {
40          /* Create builder  */
41          final StringBuilder myBuilder = new StringBuilder();
42  
43          /* Initialise the details */
44          final String myResult = updateTemplate(ArduinoTemplate.HEADER, pSystem);
45  
46          /* Loop through the messages */
47          for (ArduinoMessage myMessage : pSystem.getMessages()) {
48              /* If the message is selected */
49              if (pFilter.isSelected(myMessage.getId())) {
50                  /* format the message */
51                  myBuilder.append(formatMessageHeader(myMessage, pFilter));
52              }
53          }
54  
55          /* Update the template and return  */
56          return myResult.replace(ArduinoVar.HDRCLASSES, myBuilder.toString());
57      }
58  
59      /**
60       * Format system header.
61       * @param pSystem the system
62       * @param pFilter the filter
63       * @return the formatted system
64       */
65      public static String formatBody(final ArduinoSystem pSystem,
66                                      final ArduinoMsgFilter pFilter) {
67          /* Create builder  */
68          final StringBuilder myBuilder = new StringBuilder();
69  
70          /* Initialise the details */
71          final String myResult = updateTemplate(ArduinoTemplate.BODY, pSystem);
72  
73          /* Loop through the messages */
74          for (ArduinoMessage myMessage : pSystem.getMessages()) {
75              /* If the message is selected */
76              if (pFilter.isSelected(myMessage.getId())) {
77                  /* format the message */
78                  myBuilder.append(formatMessageBody(myMessage, pFilter));
79              }
80          }
81  
82          /* Update the template and return  */
83          return myResult.replace(ArduinoVar.BDYCLASSES, myBuilder.toString())
84                  .replace(ArduinoVar.MSGCASES, buildMsgCases(pSystem, pFilter));
85      }
86  
87      /**
88       * Format message header.
89       * @param pMessage the message
90       * @param pFilter the filter
91       * @return the formatted message
92       */
93      private static String formatMessageHeader(final ArduinoMessage pMessage,
94                                                final ArduinoMsgFilter pFilter) {
95          /* Create builder  */
96          final StringBuilder myBuilder = new StringBuilder();
97  
98          /* Determine the parts to format */
99          final boolean parseMsg = pFilter.isParsed(pMessage.getId());
100         final boolean buildMsg = pFilter.isBuilt(pMessage.getId());
101 
102         /* Update standard message details */
103         String myBase = ArduinoTemplate.HDRCLASS.replace(ArduinoVar.PARSEHDR, parseMsg ? ArduinoTemplate.HDRPARSECLASS : "");
104         myBase = myBase.replace(ArduinoVar.BUILDHDR, buildMsg ? ArduinoTemplate.HDRBUILDCLASS : "");
105         myBuilder.append(formatClassHeader(myBase, pFilter, pMessage, ArduinoSignal.MULTI_NONE, pMessage.getSignals()));
106 
107         /* If we are a multiplex message */
108         if (pMessage.hasMultiplex()) {
109             /* Loop through the multiplexes */
110             for (Entry<Long, List<ArduinoSignal>> myEntry : pMessage.getMultiplexMap().entrySet()) {
111                 /* Format the multiplex class */
112                 myBase = ArduinoTemplate.HDRMULTICLASS.replace(ArduinoVar.PARSEHDR, parseMsg ? ArduinoTemplate.HDRPARSECLASS : "");
113                 myBase = myBase.replace(ArduinoVar.BUILDHDR, buildMsg ? ArduinoTemplate.HDRBUILDMULTICLASS : "");
114                 myBuilder.append(formatClassHeader(myBase, pFilter, pMessage, myEntry.getKey(), myEntry.getValue()));
115             }
116         }
117 
118         /* Return the result */
119         return myBuilder.toString();
120     }
121 
122     /**
123      * Format message body.
124      * @param pMessage the message
125      * @param pFilter the filter
126      * @return the formatted message
127      */
128     private static String formatMessageBody(final ArduinoMessage pMessage,
129                                             final ArduinoMsgFilter pFilter) {
130         /* Create builder  */
131         final StringBuilder myBuilder = new StringBuilder();
132 
133         /* Determine the parts to format */
134         final boolean parseMsg = pFilter.isParsed(pMessage.getId());
135         final boolean buildMsg = pFilter.isBuilt(pMessage.getId());
136 
137         /* Update standard message details */
138         String myBase = ArduinoTemplate.BDYCLASS.replace(ArduinoVar.PARSEBDY + ArduinoChar.LF, parseMsg ? ArduinoTemplate.BDYPARSECLASS : "");
139         myBase = myBase.replace(ArduinoVar.BUILDBDY, buildMsg ? ArduinoTemplate.BDYBUILDCLASS : "");
140         myBuilder.append(formatClassBody(myBase, pMessage, ArduinoSignal.MULTI_NONE, pMessage.getSignals()));
141 
142         /* If we are a multiplex message */
143         if (pMessage.hasMultiplex()) {
144             /* Loop through the multiplexes */
145             for (Entry<Long, List<ArduinoSignal>> myEntry : pMessage.getMultiplexMap().entrySet()) {
146                 /* Format the multiplex class */
147                 myBase = ArduinoTemplate.BDYMULTICLASS.replace(ArduinoVar.PARSEBDY + ArduinoChar.LF, parseMsg ? ArduinoTemplate.BDYPARSEMULTICLASS : "");
148                 myBase = myBase.replace(ArduinoVar.BUILDBDY, buildMsg ? ArduinoTemplate.BDYBUILDMULTICLASS : "");
149                 myBuilder.append(formatClassBody(myBase, pMessage, myEntry.getKey(), myEntry.getValue()));
150             }
151         }
152 
153         /* Return the result */
154         return myBuilder.toString();
155     }
156 
157     /**
158      * Format class header.
159      * @param pTemplate the template
160      * @param pFilter the filter
161      * @param pMessage the message
162      * @param pMultiplex the multiplex value
163      * @param pSignals the signals
164      * @return the formatted class
165      */
166     private static String formatClassHeader(final String pTemplate,
167                                             final ArduinoMsgFilter pFilter,
168                                             final ArduinoMessage pMessage,
169                                             final Long pMultiplex,
170                                             final List<ArduinoSignal> pSignals) {
171         /* determine whether we have public fields */
172         final boolean pubFields = pFilter.publicFields();
173 
174         /* Update signal details */
175         String myResult = pTemplate.replace(ArduinoVar.HDRSETTERS, pubFields ? "" : formatSignals(ArduinoTemplate.HDRSETTER, pSignals));
176         myResult = myResult.replace(ArduinoVar.HDRGETTERS, pubFields ? "" : formatSignals(ArduinoTemplate.HDRGETTER, pSignals));
177         myResult = myResult.replace(ArduinoVar.PUBFIELDS, pubFields ? "private:\n  " : "");
178         myResult = myResult.replace(ArduinoVar.PRIVFIELDS, pubFields ? "\n" : "\n  private:\n");
179         myResult = myResult.replace(ArduinoVar.HDRFIELDS, formatSignals(ArduinoTemplate.HDRFIELD, pSignals));
180         myResult = myResult.replace(ArduinoVar.HDRFIELDDEFS, formatDefSignals(ArduinoTemplate.HDRFIELDDEF, pSignals));
181 
182         /* Determine the class name */
183         String myClass = pMessage.getName();
184         if (!ArduinoSignal.MULTI_NONE.equals(pMultiplex)) {
185             /* Adjust class name */
186             myClass += "_m" + pMultiplex;
187 
188             /* Override the multiplex setter */
189             myResult = myResult.replace(ArduinoVar.MUXSETTER, pubFields ? "" : updateTemplate(ArduinoTemplate.MUXSETTER, pMessage.getMultiplexSignal()));
190             myResult = myResult.replace(ArduinoVar.MUXSET, updateTemplate(pMessage.getMultiplexSignal(), pFilter, pMultiplex));
191         }
192 
193         /* Update the message/class */
194         myResult = updateTemplate(myResult, pMessage);
195         myResult = myResult.replace(ArduinoVar.CLASSNAME, myClass);
196 
197         /* Return the result */
198         return myResult;
199     }
200 
201     /**
202      * Format class body.
203      * @param pTemplate the template
204      * @param pMessage the message
205      * @param pMultiplex the multiplex value
206      * @param pSignals the signals
207      * @return the formatted class
208      */
209     private static String formatClassBody(final String pTemplate,
210                                           final ArduinoMessage pMessage,
211                                           final Long pMultiplex,
212                                           final List<ArduinoSignal> pSignals) {
213         /* Update signal details */
214         String myResult = pTemplate.replace(ArduinoVar.BDYREADERS, formatSignals(ArduinoTemplate.BDYPARSER, pSignals));
215         myResult = myResult.replace(ArduinoVar.BDYWRITERS, formatSignals(ArduinoTemplate.BDYWRITER, pSignals));
216         myResult = myResult.replace(ArduinoVar.BDYFIELDDEFS, formatDefSignals(ArduinoTemplate.BDYFIELDDEF, pSignals));
217 
218         /* Determine the class name */
219         String myClass = pMessage.getName();
220         if (!ArduinoSignal.MULTI_NONE.equals(pMultiplex)) {
221             /* Adjust class name */
222             myClass += "_m" + pMultiplex;
223         }
224 
225         /* Update message/class */
226         myResult = updateTemplate(myResult, pMessage);
227         myResult = myResult.replace(ArduinoVar.CLASSNAME, myClass);
228 
229         /* Return the result */
230         return myResult;
231     }
232 
233     /**
234      * Format signals.
235      * @param pTemplate the template
236      * @param pSignals the signals
237      * @return the formatted signals
238      */
239     private static String formatSignals(final String pTemplate,
240                                         final List<ArduinoSignal> pSignals) {
241         /* Create builder  */
242         final StringBuilder myBuilder = new StringBuilder();
243 
244         /* Loop through the signals */
245         for (ArduinoSignal mySignal : pSignals) {
246             myBuilder.append(updateTemplate(pTemplate, mySignal));
247         }
248 
249         /* Return the signals */
250         return myBuilder.toString();
251     }
252 
253     /**
254      * Format definitions for signals.
255      * @param pTemplate the template
256      * @param pSignals the signals
257      * @return the formatted signals
258      */
259     private static String formatDefSignals(final String pTemplate,
260                                            final List<ArduinoSignal> pSignals) {
261         /* Create builder  */
262         final StringBuilder myBuilder = new StringBuilder();
263 
264         /* Loop through the signals */
265         for (ArduinoSignal mySignal : pSignals) {
266             myBuilder.append(updateDefTemplate(pTemplate, mySignal));
267         }
268 
269         /* Return the signals */
270         return myBuilder.toString();
271     }
272 
273     /**
274      * Update a template for a system.
275      * @param pTemplate the template
276      * @param pSystem the system
277      * @return the updated template
278      */
279     private static String updateTemplate(final String pTemplate,
280                                          final ArduinoSystem pSystem) {
281         return pTemplate.replace(ArduinoVar.SYSNAME, pSystem.getName())
282                 .replace(ArduinoVar.SYSCNAME, pSystem.getCName());
283     }
284 
285     /**
286      * Update a template for a message.
287      * @param pTemplate the template
288      * @param pMessage the message
289      * @return the updated template
290      */
291     private static String updateTemplate(final String pTemplate,
292                                          final ArduinoMessage pMessage) {
293         return pTemplate.replace(ArduinoVar.MSGNAME, pMessage.getName())
294                 .replace(ArduinoVar.MSGID, pMessage.getId())
295                 .replace(ArduinoVar.MSGLEN, Integer.toString(pMessage.getLength()));
296     }
297 
298     /**
299      * Update a template for a signal.
300      * @param pTemplate the template
301      * @param pSignal the signal
302      * @return the updated template
303      */
304     private static String updateTemplate(final String pTemplate,
305                                          final ArduinoSignal pSignal) {
306         final boolean isFloat = pSignal.isFloat();
307         final String myClass = isFloat ? "Double" : "Long";
308         final boolean unBounded = pSignal.getRange().unBounded();
309         final String myBounds = unBounded ? "" : "Bounded";
310         return pTemplate.replace(ArduinoVar.FIELDNAME, pSignal.getName())
311                 .replace(ArduinoVar.FIELDCLASS, myClass)
312                 .replace(ArduinoVar.FIELDBOUND, myBounds)
313                 .replace(ArduinoVar.FIELDTYPE, getFieldType(pSignal));
314     }
315 
316     /**
317      * Update a template for a signal.
318      * @param pSignal the signal
319      * @param pFilter the filter
320      * @param pMultiplex the multiplex value
321      * @return the updated template
322      */
323     private static String updateTemplate(final ArduinoSignal pSignal,
324                                          final ArduinoMsgFilter pFilter,
325                                          final Long pMultiplex) {
326         /* determine whether we have public fields */
327         final boolean pubFields = pFilter.publicFields();
328 
329         final String myBase = updateTemplate(pubFields ? ArduinoTemplate.MUXSETPUB : ArduinoTemplate.MUXSETPRIV, pSignal);
330         return myBase.replace(ArduinoVar.MUXVALUE, pMultiplex.toString());
331     }
332 
333     /**
334      * Update a definition template for a signal.
335      * @param pTemplate the template
336      * @param pSignal the signal
337      * @return the updated template
338      */
339     private static String updateDefTemplate(final String pTemplate,
340                                             final ArduinoSignal pSignal) {
341         final boolean isFloat = pSignal.isFloat();
342         final String myClass = isFloat ? "Double" : "Long";
343         return pTemplate.replace(ArduinoVar.DEFSTART, Integer.toString(pSignal.getDefinition().getStartBit()))
344                 .replace(ArduinoVar.DEFLENGTH, Integer.toString(pSignal.getDefinition().getBitLength()))
345                 .replace(ArduinoVar.DEFENDIAN, Boolean.toString(pSignal.getDefinition().getDataType().isLittleEndian()))
346                 .replace(ArduinoVar.DEFSIGNED, Boolean.toString(pSignal.getDefinition().getDataType().isSigned()))
347                 .replace(ArduinoVar.DEFFACTOR, pSignal.getFactor().getFactor().toString())
348                 .replace(ArduinoVar.DEFOFFSET, pSignal.getFactor().getOffset().toString())
349                 .replace(ArduinoVar.FIELDNAME, pSignal.getName())
350                 .replace(ArduinoVar.FIELDCLASS, myClass)
351                 .replace(ArduinoVar.DEFMIN, pSignal.getRange().getMinimum().toString())
352                 .replace(ArduinoVar.DEFMAX, pSignal.getRange().getMaximum().toString());
353     }
354 
355     /**
356      * Build message cases for the parser.
357      * @param pSystem the system
358      * @param pFilter the filter
359      * @return the cases.
360      */
361     private static String buildMsgCases(final ArduinoSystem pSystem,
362                                         final ArduinoMsgFilter pFilter) {
363         /* Create the builder */
364         final StringBuilder myBuilder = new StringBuilder();
365 
366         /* Loop through the messages */
367         for (ArduinoMessage myMessage : pSystem.getMessages()) {
368             /* If the message is not parsed, skip it */
369             if (!pFilter.isParsed(myMessage.getId())) {
370                 continue;
371             }
372 
373             /* If this is a standard message */
374             if (myMessage.hasMultiplex()) {
375                 myBuilder.append(buildMultiplexCases(myMessage, pFilter));
376             } else {
377                 myBuilder.append(updateTemplate(ArduinoTemplate.PRSMSGCASE, myMessage));
378             }
379         }
380 
381         /* Return the parser */
382         return myBuilder.toString();
383     }
384 
385     /**
386      * Build multiplex message cases for the parser.
387      * @param pMessage the multiplex message
388      * @param pFilter the filter
389      * @return the case.
390      */
391     private static String buildMultiplexCases(final ArduinoMessage pMessage,
392                                               final ArduinoMsgFilter pFilter) {
393         /* Create the builder */
394         final StringBuilder myBuilder = new StringBuilder();
395 
396         /* determine whether we have public fields */
397         final boolean pubFields = pFilter.publicFields();
398 
399         /* Create the multiplex cases */
400         for (Long myId : pMessage.getMultiplexMap().keySet()) {
401             /* Add the case */
402             myBuilder.append(ArduinoTemplate.PRSMULTIMSGCASE.replace(ArduinoVar.MUXVALUE, myId.toString()));
403         }
404 
405         /* Fold in to the case */
406         final String myCase = ArduinoTemplate.PRSMULTIMSG
407                 .replace(ArduinoVar.MUXSWITCH, pubFields ? ArduinoTemplate.MUXSWITCHPUB : ArduinoTemplate.MUXSWITCHPRIV)
408                 .replace(ArduinoVar.MUXCASES, myBuilder.toString());
409         return updateTemplate(myCase, pMessage).replace(ArduinoVar.FIELDNAME, pMessage.getMultiplexSignal().getName());
410     }
411 
412     /**
413      * Obtain the fieldType.
414      * @param pSignal the signal
415      * @return the fieldType
416      */
417     private static String getFieldType(final ArduinoSignal pSignal) {
418         /* Handle float */
419         if (pSignal.isFloat()) {
420             return "double";
421         }
422 
423         /* Obtain bitLength of signal */
424         final int myBitLen = pSignal.getDefinition().getBitLength();
425         if (myBitLen == 1) {
426             return "bool";
427         }
428         final int myByteLen = 1 + ((myBitLen - 1) / Byte.SIZE);
429 
430         /* Return the correct sign/length of int */
431         final String mySign = pSignal.isSigned() ? "" : "unsigned ";
432         if (myByteLen == Byte.BYTES  || myByteLen == Short.BYTES) {
433             return mySign + "short";
434         } else if (myByteLen <= Integer.BYTES) {
435             return mySign + "long";
436         }
437         return mySign + "long long";
438     }
439 }