1   /* Copyright 2002-2021 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.parsing;
18  
19  import java.io.IOException;
20  import java.util.HashMap;
21  import java.util.Map;
22  
23  import org.hipparchus.exception.LocalizedCoreFormats;
24  import org.orekit.data.DataSource;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitInternalError;
27  import org.orekit.files.ccsds.utils.FileFormat;
28  import org.orekit.files.ccsds.utils.lexical.LexicalAnalyzerSelector;
29  import org.orekit.files.ccsds.utils.lexical.MessageParser;
30  import org.orekit.files.ccsds.utils.lexical.MessageVersionXmlTokenBuilder;
31  import org.orekit.files.ccsds.utils.lexical.ParseToken;
32  import org.orekit.files.ccsds.utils.lexical.XmlTokenBuilder;
33  
34  /** Parser for CCSDS messages.
35   * <p>
36   * Note than starting with Orekit 11.0, CCSDS message parsers are
37   * mutable objects that gather the data being parsed, until the
38   * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
39   * parseMessage} method has returned. This implies that parsers
40   * should <em>not</em> be used in a multi-thread context. The recommended
41   * way to use parsers is to either dedicate one parser for each message
42   * and drop it afterwards, or to use a single-thread loop.
43   * </p>
44   * @param <T> type of the file
45   * @author Luc Maisonobe
46   * @since 11.0
47   */
48  public abstract class AbstractMessageParser<T> implements MessageParser<T> {
49  
50      /** Safety limit for loop over processing states. */
51      private static final int MAX_LOOP = 100;
52  
53      /** Root element for XML files. */
54      private final String root;
55  
56      /** Key for format version. */
57      private final String formatVersionKey;
58  
59      /** Anticipated next processing state. */
60      private ProcessingState next;
61  
62      /** Current processing state. */
63      private ProcessingState current;
64  
65      /** Fallback processing state. */
66      private ProcessingState fallback;
67  
68      /** Format of the file ready to be parsed. */
69      private FileFormat format;
70  
71      /** Flag for XML end tag. */
72      private boolean endTagSeen;
73  
74      /** Simple constructor.
75       * @param root root element for XML files
76       * @param formatVersionKey key for format version
77       */
78      protected AbstractMessageParser(final String root, final String formatVersionKey) {
79          this.root             = root;
80          this.formatVersionKey = formatVersionKey;
81          this.current          = null;
82          setFallback(new ErrorState());
83      }
84  
85      /** Set fallback processing state.
86       * <p>
87       * The fallback processing state is used if anticipated state fails
88       * to parse the token.
89       * </p>
90       * @param fallback processing state to use if anticipated state does not work
91       */
92      public void setFallback(final ProcessingState fallback) {
93          this.fallback = fallback;
94      }
95  
96      /** Reset parser to initial state before parsing.
97       * @param fileFormat format of the file ready to be parsed
98       * @param initialState initial processing state
99       */
100     protected void reset(final FileFormat fileFormat, final ProcessingState initialState) {
101         format     = fileFormat;
102         current    = initialState;
103         endTagSeen = false;
104         anticipateNext(fallback);
105     }
106 
107     /** Set the flag for XML end tag.
108      * @param endTagSeen if true, the XML end tag has been seen
109      */
110     public void setEndTagSeen(final boolean endTagSeen) {
111         this.endTagSeen = endTagSeen;
112     }
113 
114     /** Check if XML end tag has been seen.
115      * @return true if XML end tag has been seen
116      */
117     public boolean wasEndTagSeen() {
118         return endTagSeen;
119     }
120 
121     /** Get the current processing state.
122      * @return current processing state
123      */
124     public ProcessingState getCurrent() {
125         return current;
126     }
127 
128     /** Get the file format.
129      * @return file format
130      */
131     protected FileFormat getFileFormat() {
132         return format;
133     }
134 
135     /** {@inheritDoc} */
136     @Override
137     public T parseMessage(final DataSource source) {
138         try {
139             return LexicalAnalyzerSelector.select(source).accept(this);
140         } catch (IOException ioe) {
141             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE,
142                                       ioe.getLocalizedMessage());
143         }
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public String getFormatVersionKey() {
149         return formatVersionKey;
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public Map<String, XmlTokenBuilder> getSpecialXmlElementsBuilders() {
155 
156         final HashMap<String, XmlTokenBuilder> builders = new HashMap<>();
157 
158         if (formatVersionKey != null) {
159             // special handling of root tag that contains the format version
160             builders.put(root, new MessageVersionXmlTokenBuilder());
161         }
162 
163         return builders;
164 
165     }
166 
167     /** Anticipate what next processing state should be.
168      * @param anticipated anticipated next processing state
169      */
170     public void anticipateNext(final ProcessingState anticipated) {
171         this.next = anticipated;
172     }
173 
174     /** {@inheritDoc} */
175     @Override
176     public void process(final ParseToken token) {
177 
178         // loop over the various states until one really processes the token
179         for (int i = 0; i < MAX_LOOP; ++i) {
180             if (current.processToken(token)) {
181                 return;
182             }
183             current = next;
184             next    = fallback;
185         }
186 
187         // this should never happen
188         throw new OrekitInternalError(null);
189 
190     }
191 
192 }