AbstractGenerator.java

  1. /* Copyright 2002-2022 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  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 org.orekit.files.ccsds.utils.generation;

  18. import java.io.IOException;
  19. import java.util.ArrayDeque;
  20. import java.util.Deque;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;

  24. import org.hipparchus.fraction.Fraction;
  25. import org.hipparchus.util.FastMath;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.errors.OrekitInternalError;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.files.ccsds.definitions.TimeConverter;
  30. import org.orekit.time.AbsoluteDate;
  31. import org.orekit.time.DateTimeComponents;
  32. import org.orekit.utils.AccurateFormatter;
  33. import org.orekit.utils.units.Parser;
  34. import org.orekit.utils.units.PowerTerm;
  35. import org.orekit.utils.units.Unit;

  36. /** Base class for both Key-Value Notation and eXtended Markup Language generators for CCSDS messages.
  37.  * @author Luc Maisonobe
  38.  * @since 11.0
  39.  */
  40. public abstract class AbstractGenerator implements Generator {

  41.     /** New line separator for output file. */
  42.     private static final char NEW_LINE = '\n';

  43.     /** Destination of generated output. */
  44.     private final Appendable output;

  45.     /** Output name for error messages. */
  46.     private final String outputName;

  47.     /** Flag for writing units. */
  48.     private final boolean writeUnits;

  49.     /** Sections stack. */
  50.     private final Deque<String> sections;

  51.     /** Map from SI Units name to CCSDS unit names. */
  52.     private final Map<String, String> siToCcsds;

  53.     /** Simple constructor.
  54.      * @param output destination of generated output
  55.      * @param outputName output name for error messages
  56.      * @param writeUnits if true, units must be written
  57.      */
  58.     public AbstractGenerator(final Appendable output, final String outputName, final boolean writeUnits) {
  59.         this.output     = output;
  60.         this.outputName = outputName;
  61.         this.writeUnits = writeUnits;
  62.         this.sections   = new ArrayDeque<>();
  63.         this.siToCcsds  = new HashMap<>();
  64.     }

  65.     /** {@inheritDoc} */
  66.     @Override
  67.     public String getOutputName() {
  68.         return outputName;
  69.     }

  70.     /** Check if unit must be written.
  71.      * @param unit entry unit
  72.      * @return true if units must be written
  73.      */
  74.     public boolean writeUnits(final Unit unit) {
  75.         return writeUnits &&
  76.                unit != null &&
  77.                !unit.getName().equals(Unit.NONE.getName()) &&
  78.                !unit.getName().equals(Unit.ONE.getName());
  79.     }

  80.     /** {@inheritDoc} */
  81.     @Override
  82.     public void close() throws IOException {

  83.         // get out from all sections properly
  84.         while (!sections.isEmpty()) {
  85.             exitSection();
  86.         }

  87.     }

  88.     /** {@inheritDoc} */
  89.     @Override
  90.     public void newLine() throws IOException {
  91.         output.append(NEW_LINE);
  92.     }

  93.     /** {@inheritDoc} */
  94.     @Override
  95.     public void writeEntry(final String key, final List<String> value, final boolean mandatory) throws IOException {
  96.         if (value == null || value.isEmpty()) {
  97.             complain(key, mandatory);
  98.         } else {
  99.             final StringBuilder builder = new StringBuilder();
  100.             boolean first = true;
  101.             for (final String v : value) {
  102.                 if (!first) {
  103.                     builder.append(',');
  104.                 }
  105.                 builder.append(v);
  106.                 first = false;
  107.             }
  108.             writeEntry(key, builder.toString(), null, mandatory);
  109.         }
  110.     }

  111.     /** {@inheritDoc} */
  112.     @Override
  113.     public void writeEntry(final String key, final Enum<?> value, final boolean mandatory) throws IOException {
  114.         writeEntry(key, value == null ? null : value.name(), null, mandatory);
  115.     }

  116.     /** {@inheritDoc} */
  117.     @Override
  118.     public void writeEntry(final String key, final TimeConverter converter, final AbsoluteDate date, final boolean mandatory)
  119.         throws IOException {
  120.         writeEntry(key, date == null ? (String) null : dateToString(converter, date), null, mandatory);
  121.     }

  122.     /** {@inheritDoc} */
  123.     @Override
  124.     public void writeEntry(final String key, final double value, final Unit unit, final boolean mandatory) throws IOException {
  125.         writeEntry(key, doubleToString(unit.fromSI(value)), unit, mandatory);
  126.     }

  127.     /** {@inheritDoc} */
  128.     @Override
  129.     public void writeEntry(final String key, final Double value, final Unit unit, final boolean mandatory) throws IOException {
  130.         writeEntry(key, value == null ? (String) null : doubleToString(unit.fromSI(value.doubleValue())), unit, mandatory);
  131.     }

  132.     /** {@inheritDoc} */
  133.     @Override
  134.     public void writeEntry(final String key, final char value, final boolean mandatory) throws IOException {
  135.         writeEntry(key, Character.toString(value), null, mandatory);
  136.     }

  137.     /** {@inheritDoc} */
  138.     @Override
  139.     public void writeEntry(final String key, final int value, final boolean mandatory) throws IOException {
  140.         writeEntry(key, Integer.toString(value), null, mandatory);
  141.     }

  142.     /** {@inheritDoc} */
  143.     @Override
  144.     public void writeRawData(final char data) throws IOException {
  145.         output.append(data);
  146.     }

  147.     /** {@inheritDoc} */
  148.     @Override
  149.     public void writeRawData(final CharSequence data) throws IOException {
  150.         output.append(data);
  151.     }

  152.     /** {@inheritDoc} */
  153.     @Override
  154.     public void enterSection(final String name) throws IOException {
  155.         sections.offerLast(name);
  156.     }

  157.     /** {@inheritDoc} */
  158.     @Override
  159.     public String exitSection() throws IOException {
  160.         return sections.pollLast();
  161.     }

  162.     /** Complain about a missing value.
  163.      * @param key the keyword to write
  164.      * @param mandatory if true, triggers en exception, otherwise do nothing
  165.      */
  166.     protected void complain(final String key, final boolean mandatory) {
  167.         if (mandatory) {
  168.             throw new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD, key, outputName);
  169.         }
  170.     }

  171.     /** {@inheritDoc} */
  172.     @Override
  173.     public String doubleToString(final double value) {
  174.         return Double.isNaN(value) ? null : AccurateFormatter.format(value);
  175.     }

  176.     /** {@inheritDoc} */
  177.     @Override
  178.     public String dateToString(final TimeConverter converter, final AbsoluteDate date) {
  179.         final DateTimeComponents dt = converter.components(date);
  180.         return dateToString(dt.getDate().getYear(), dt.getDate().getMonth(), dt.getDate().getDay(),
  181.                             dt.getTime().getHour(), dt.getTime().getMinute(), dt.getTime().getSecond());
  182.     }

  183.     /** {@inheritDoc} */
  184.     @Override
  185.     public String dateToString(final int year, final int month, final int day,
  186.                                final int hour, final int minute, final double seconds) {
  187.         return AccurateFormatter.format(year, month, day, hour, minute, seconds);
  188.     }

  189.     /** {@inheritDoc} */
  190.     @Override
  191.     public String unitsListToString(final List<Unit> units) {

  192.         if (units == null || units.isEmpty()) {
  193.             // nothing to output
  194.             return null;
  195.         }

  196.         final StringBuilder builder = new StringBuilder();
  197.         builder.append('[');
  198.         boolean first = true;
  199.         for (final Unit unit : units) {
  200.             if (!first) {
  201.                 builder.append(',');
  202.             }
  203.             builder.append(siToCcsdsName(unit.getName()));
  204.             first = false;
  205.         }
  206.         builder.append(']');
  207.         return builder.toString();

  208.     }

  209.     /** {@inheritDoc} */
  210.     @Override
  211.     public String siToCcsdsName(final String siName) {

  212.         if (!siToCcsds.containsKey(siName)) {

  213.             // build a name using only CCSDS syntax
  214.             final StringBuilder builder = new StringBuilder();

  215.             // parse the SI name that may contain fancy features like unicode superscripts, square roots sign…
  216.             final List<PowerTerm> terms = Parser.buildTermsList(siName);

  217.             if (terms == null) {
  218.                 builder.append("n/a");
  219.             } else {

  220.                 // put the positive exponent first
  221.                 boolean first = true;
  222.                 for (final PowerTerm term : terms) {
  223.                     if (term.getExponent().getNumerator() >= 0) {
  224.                         if (!first) {
  225.                             builder.append('*');
  226.                         }
  227.                         appendScale(builder, term.getScale());
  228.                         appendBase(builder, term.getBase());
  229.                         appendExponent(builder, term.getExponent());
  230.                         first = false;
  231.                     }
  232.                 }

  233.                 if (first) {
  234.                     // no positive exponents at all, we add "1" to get something like "1/s"
  235.                     builder.append('1');
  236.                 }

  237.                 // put the negative exponents last
  238.                 for (final PowerTerm term : terms) {
  239.                     if (term.getExponent().getNumerator() < 0) {
  240.                         builder.append('/');
  241.                         appendScale(builder, term.getScale());
  242.                         appendBase(builder, term.getBase());
  243.                         appendExponent(builder, term.getExponent().negate());
  244.                     }
  245.                 }

  246.             }

  247.             // put the converted name in the map for reuse
  248.             siToCcsds.put(siName, builder.toString());

  249.         }

  250.         return siToCcsds.get(siName);

  251.     }

  252.     /** Append a scaling factor.
  253.      * @param builder builder to which term must be added
  254.      * @param scale scaling factor
  255.      */
  256.     private void appendScale(final StringBuilder builder, final double scale) {
  257.         final int factor = (int) FastMath.rint(scale);
  258.         if (FastMath.abs(scale - factor) > 1.0e-12) {
  259.             // this should never happen with CCSDS units
  260.             throw new OrekitInternalError(null);
  261.         }
  262.         if (factor != 1) {
  263.             builder.append(factor);
  264.         }
  265.     }

  266.     /** Append a base term.
  267.      * @param builder builder to which term must be added
  268.      * @param base base term
  269.      */
  270.     private void appendBase(final StringBuilder builder, final CharSequence base) {
  271.         if ("°".equals(base) || "◦".equals(base)) {
  272.             builder.append("deg");
  273.         } else {
  274.             builder.append(base);
  275.         }
  276.     }

  277.     /** Append an exponent.
  278.      * @param builder builder to which term must be added
  279.      * @param exponent exponent to add
  280.      */
  281.     private void appendExponent(final StringBuilder builder, final Fraction exponent) {
  282.         if (!exponent.equals(Fraction.ONE)) {
  283.             builder.append("**");
  284.             builder.append(exponent.equals(Fraction.ONE_HALF) ? "0.5" : exponent);
  285.         }
  286.     }

  287. }