1   /* Copyright 2002-2019 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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;
18  
19  import java.util.Locale;
20  import java.util.regex.Matcher;
21  import java.util.regex.Pattern;
22  
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitMessages;
25  
26  /** Holder for key-value pair.
27   * <p>
28   * The syntax for key-value lines in CCSDS files is:
29   * </p>
30   * <pre>
31   * KEY = value [unit]
32   * </pre>
33   * <p>
34   * The "[unit]" part (with the square brackets included) is optional.
35   * The COMMENT keyword is an exception and does not have an '=' but directly
36   * the value as free form text. The META_START, META_STOP, COVARIANCE_START
37   * and COVARIANCE_STOP keywords are other exception and do not have anything
38   * else following them on the line.
39   * </p>
40   * @author Luc Maisonobe
41   * @since 6.1
42   */
43  class KeyValue {
44  
45      /** Regular expression for splitting lines. */
46      private final Pattern PATTERN =
47              Pattern.compile("\\p{Space}*([A-Z][A-Z_0-9]*)\\p{Space}*=?\\p{Space}*(.*?)\\p{Space}*(?:\\[.*\\])?\\p{Space}*");
48  
49      /** Regular expression for user defined keywords. */
50      private final Pattern USER_DEFINED_KEYWORDS =
51              Pattern.compile("USER_DEFINED_[A-Z][A-Z_]*");
52  
53      /** Line from which pair is extracted. */
54      private final String line;
55  
56      /** Number of the line from which pair is extracted. */
57      private final int lineNumber;
58  
59      /** Name of the file. */
60      private final String fileName;
61  
62      /** Keyword enum corresponding to parsed key. */
63      private final Keyword keyword;
64  
65      /** Key part of the pair. */
66      private final String key;
67  
68      /** Value part of the line. */
69      private final String value;
70  
71      /** Build a pair by splitting a key-value line.
72       * <p>
73       * The splitting is very basic and only extracts words using a regular
74       * expression ignoring the '=' sign and the optional unit. No attempt
75       * is made to recognize the special keywords. The key and value parts
76       * may be empty if not matched, and the keyword may be null.
77       * </p>
78       * <p> The value part may be upper case or lower case. This constructor
79       * converts all lower case values to upper case.
80       * @param line to split
81       * @param lineNumber number of the line in the CCSDS data message
82       * @param fileName name of the file
83       */
84      KeyValue(final String line, final int lineNumber, final String fileName) {
85  
86          this.line       = line;
87          this.lineNumber = lineNumber;
88          this.fileName   = fileName;
89  
90          final Matcher matcher = PATTERN.matcher(line);
91          if (matcher.matches()) {
92              key   = matcher.group(1);
93              final String rawValue = matcher.group(2);
94              Keyword recognized;
95              try {
96                  recognized = Keyword.valueOf(key);
97              } catch (IllegalArgumentException iae) {
98                  if (USER_DEFINED_KEYWORDS.matcher(key).matches()) {
99                      recognized = Keyword.USER_DEFINED_X;
100                 } else {
101                     recognized = null;
102                 }
103             }
104             keyword = recognized;
105             if (recognized == Keyword.COMMENT) {
106                 value = rawValue;
107             } else {
108                 value = rawValue.
109                         toUpperCase(Locale.US).
110                         replace('_', ' ').
111                         replaceAll("\\p{Space}+", " ");
112             }
113         } else {
114             key     = "";
115             value   = key;
116             keyword = null;
117         }
118     }
119 
120     /** Build a pair by giving the input arguments.
121      *  This is essentially used while parsing XML files.
122      *  It is made to be allow the use of the class KeyValue for both Keyvalue and XML file formats.
123      *  Thus common functions can be used for the parsing.
124      * <p>
125      * The splitting is very basic and only extracts words using a regular
126      * expression ignoring the '=' sign and the optional unit. No attempt
127      * is made to recognize the special keywords. The key and value parts
128      * may be empty if not matched, and the keyword may be null.
129      * </p>
130      * <p> The value part may be upper case or lower case. This constructor
131      * converts all lower case values to upper case.
132      * @param keyword the keyword
133      * @param value the value attached to the keyword
134      * @param line the line where the keyword was found
135      * @param lineNumber number of the line in the CCSDS data message
136      * @param fileName name of the file
137      */
138     KeyValue(final Keyword keyword, final String value,
139              final String line, final int lineNumber,
140              final String fileName) {
141         this.keyword = keyword;
142         this.key = keyword.name();
143         this.value = value;
144         this.lineNumber = lineNumber;
145         this.line = line;
146         this.fileName = fileName;
147     }
148 
149     /** Keyword corresponding to the parsed key.
150      * @return keyword corresponding to the parsed key
151      * (null if not recognized)
152      */
153     public Keyword getKeyword() {
154         return keyword;
155     }
156 
157     /** Get the key.
158      * @return key
159      */
160     public String getKey() {
161         return key;
162     }
163 
164     /** Get the value.
165      * @return value
166      */
167     public String getValue() {
168         return value;
169     }
170 
171     /** Get the value as a double number.
172      * @return value
173      */
174     public double getDoubleValue() {
175         try {
176             return Double.parseDouble(value);
177         } catch (NumberFormatException nfe) {
178             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
179                                       lineNumber, fileName, line);
180         }
181     }
182 
183     /** Get the value as an integer number.
184      * @return value
185      */
186     public int getIntegerValue() {
187         try {
188             return Integer.parseInt(value);
189         } catch (NumberFormatException nfe) {
190             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
191                                       lineNumber, fileName, line);
192         }
193     }
194 
195 }