View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  package com.addthis.codec.config;
15  
16  import java.io.IOException;
17  import java.io.OutputStream;
18  
19  import java.math.BigDecimal;
20  import java.math.BigInteger;
21  
22  import com.fasterxml.jackson.core.Base64Variant;
23  import com.fasterxml.jackson.core.JsonLocation;
24  import com.fasterxml.jackson.core.JsonParseException;
25  import com.fasterxml.jackson.core.JsonParser;
26  import com.fasterxml.jackson.core.JsonStreamContext;
27  import com.fasterxml.jackson.core.JsonToken;
28  import com.fasterxml.jackson.core.ObjectCodec;
29  import com.fasterxml.jackson.core.Version;
30  import com.fasterxml.jackson.core.base.ParserMinimalBase;
31  import com.fasterxml.jackson.databind.JsonNode;
32  import com.fasterxml.jackson.databind.node.JsonNodeFactory;
33  import com.typesafe.config.ConfigList;
34  import com.typesafe.config.ConfigObject;
35  import com.typesafe.config.ConfigOrigin;
36  import com.typesafe.config.ConfigValue;
37  import com.typesafe.config.ConfigValueType;
38  
39  import static com.typesafe.config.ConfigValueType.LIST;
40  import static com.typesafe.config.ConfigValueType.OBJECT;
41  
42  public class ConfigTraversingParser extends ParserMinimalBase {
43      /*
44      /**********************************************************
45      /* Configuration
46      /**********************************************************
47       */
48  
49      protected ObjectCodec _objectCodec;
50  
51      /**
52       * Traversal context within tree
53       */
54      protected ConfigNodeCursor _nodeCursor;
55  
56      /*
57      /**********************************************************
58      /* State
59      /**********************************************************
60       */
61  
62      /**
63       * Sometimes parser needs to buffer a single look-ahead token; if so,
64       * it'll be stored here. This is currently used for handling
65       */
66      protected JsonToken _nextToken;
67  
68      /**
69       * Flag needed to handle recursion into contents of child
70       * Array/Object nodes.
71       */
72      protected boolean _startContainer;
73  
74      /**
75       * Flag that indicates whether parser is closed or not. Gets
76       * set when parser is either closed by explicit call
77       * ({@link #close}) or when end-of-input is reached.
78       */
79      protected boolean _closed;
80  
81      protected ConfigValue currentConfig;
82  
83      /*
84      /**********************************************************
85      /* Life-cycle
86      /**********************************************************
87       */
88  
89      public ConfigTraversingParser(ConfigValue n) { this(n, null); }
90  
91      public ConfigTraversingParser(ConfigValue n, ObjectCodec codec) {
92          super(0);
93          _objectCodec = codec;
94          currentConfig = n;
95          if (n.valueType() == LIST) {
96              _nextToken = JsonToken.START_ARRAY;
97              _nodeCursor = new ConfigNodeCursor.Array((ConfigList) n, null);
98          } else if (n.valueType() == OBJECT) {
99              _nextToken = JsonToken.START_OBJECT;
100             _nodeCursor = new ConfigNodeCursor.Object((ConfigObject) n, null);
101         } else { // value node
102             _nodeCursor = new ConfigNodeCursor.RootValue(n, null);
103         }
104     }
105 
106     @Override
107     public void setCodec(ObjectCodec c) {
108         _objectCodec = c;
109     }
110 
111     @Override
112     public ObjectCodec getCodec() {
113         return _objectCodec;
114     }
115 
116     @Override
117     public Version version() {
118         return Version.unknownVersion();
119     }
120     
121     /*
122     /**********************************************************
123     /* Closeable implementation
124     /**********************************************************
125      */
126 
127     @Override
128     public void close() {
129         if (!_closed) {
130             _closed = true;
131             _nodeCursor = null;
132             _currToken = null;
133         }
134     }
135 
136     /*
137     /**********************************************************
138     /* Public API, traversal
139     /**********************************************************
140      */
141 
142     public ConfigValue currentConfig() {
143         if (_nextToken != null) {
144             // haven't read or started reading root value yet
145             return null;
146         }
147         return currentConfig;
148     }
149 
150     @Override
151     public JsonToken nextToken() throws IOException, JsonParseException {
152         if (_nextToken != null) {
153             _currToken = _nextToken;
154             _nextToken = null;
155             return _currToken;
156         }
157         // are we to descend to a container child?
158         if (_startContainer) {
159             _startContainer = false;
160             // minor optimization: empty containers can be skipped
161             if (!_nodeCursor.currentHasChildren()) {
162                 _currToken = (_currToken == JsonToken.START_OBJECT) ?
163                     JsonToken.END_OBJECT : JsonToken.END_ARRAY;
164                 return _currToken;
165             }
166             _nodeCursor = _nodeCursor.iterateChildren();
167             _currToken = _nodeCursor.nextToken();
168             if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
169                 _startContainer = true;
170             }
171             currentConfig = currentNode();
172             return _currToken;
173         }
174         // No more content?
175         if (_nodeCursor == null) {
176             _closed = true; // if not already set
177             currentConfig = null;
178             return null;
179         }
180         // Otherwise, next entry from current cursor
181         _currToken = _nodeCursor.nextToken();
182         if (_currToken != null) {
183             currentConfig = currentNode();
184             if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
185                 _startContainer = true;
186             }
187             return _currToken;
188         }
189         // null means no more children; need to return end marker
190         _currToken = _nodeCursor.endToken();
191         _nodeCursor = _nodeCursor.getParent();
192         currentConfig = currentNode();
193         return _currToken;
194     }
195     
196     // default works well here:
197     //public JsonToken nextValue() throws IOException, JsonParseException
198 
199     @Override
200     public JsonParser skipChildren() throws IOException, JsonParseException {
201         if (_currToken == JsonToken.START_OBJECT) {
202             _startContainer = false;
203             _currToken = JsonToken.END_OBJECT;
204         } else if (_currToken == JsonToken.START_ARRAY) {
205             _startContainer = false;
206             _currToken = JsonToken.END_ARRAY;
207         }
208         return this;
209     }
210 
211     @Override
212     public boolean isClosed() {
213         return _closed;
214     }
215 
216     /*
217     /**********************************************************
218     /* Public API, token accessors
219     /**********************************************************
220      */
221 
222     @Override
223     public String getCurrentName() {
224         if (_nodeCursor == null) {
225             return null;
226         } else {
227             return _nodeCursor.getCurrentName();
228         }
229     }
230 
231     @Override
232     public void overrideCurrentName(String name) {
233         if (_nodeCursor != null) {
234             _nodeCursor.overrideCurrentName(name);
235         }
236     }
237     
238     @Override
239     public JsonStreamContext getParsingContext() {
240         return _nodeCursor;
241     }
242 
243     @Override
244     public JsonLocation getTokenLocation() {
245         ConfigValue current = currentConfig();
246         if (current == null) {
247             return JsonLocation.NA;
248         }
249         ConfigOrigin nodeOrigin = current.origin();
250         return new JsonLocation(current, -1, nodeOrigin.lineNumber(), -1);
251     }
252 
253     @Override
254     public JsonLocation getCurrentLocation() {
255         return getTokenLocation();
256     }
257 
258     /*
259     /**********************************************************
260     /* Public API, access to textual content
261     /**********************************************************
262      */
263 
264     @Override
265     public String getText() {
266         if (_closed) {
267             return null;
268         }
269         // need to separate handling a bit...
270         switch (_currToken) {
271         case FIELD_NAME:
272             return _nodeCursor.getCurrentName();
273         case VALUE_STRING:
274             return (String) currentNode().unwrapped();
275         case VALUE_NUMBER_INT:
276         case VALUE_NUMBER_FLOAT:
277             return String.valueOf(currentNode().unwrapped());
278         case VALUE_EMBEDDED_OBJECT:
279             // not supported and shouldn't be called, but emulating the 'null' result by not throwing
280         default:
281             if (_currToken == null) {
282                 return null;
283             } else {
284                 return _currToken.asString();
285             }
286         }
287     }
288 
289     @Override
290     public char[] getTextCharacters() throws IOException, JsonParseException {
291         return getText().toCharArray();
292     }
293 
294     @Override
295     public int getTextLength() throws IOException, JsonParseException {
296         return getText().length();
297     }
298 
299     @Override
300     public int getTextOffset() throws IOException, JsonParseException {
301         return 0;
302     }
303 
304     @Override
305     public boolean hasTextCharacters() {
306         // generally we do not have efficient access as char[], hence:
307         return false;
308     }
309     
310     /*
311     /**********************************************************
312     /* Public API, typed non-text access
313     /**********************************************************
314      */
315 
316     //public byte getByteValue() throws IOException, JsonParseException
317 
318     @Override
319     public NumberType getNumberType() throws IOException, JsonParseException {
320         JsonNode n = currentNumericNode();
321         return (n == null) ? null : n.numberType();
322     }
323 
324     @Override
325     public BigInteger getBigIntegerValue() throws IOException, JsonParseException {
326         return currentNumericNode().bigIntegerValue();
327     }
328 
329     @Override
330     public BigDecimal getDecimalValue() throws IOException, JsonParseException {
331         return currentNumericNode().decimalValue();
332     }
333 
334     @Override
335     public double getDoubleValue() throws IOException, JsonParseException {
336         return currentNumericNode().doubleValue();
337     }
338 
339     @Override
340     public float getFloatValue() throws IOException, JsonParseException {
341         return (float) currentNumericNode().doubleValue();
342     }
343 
344     @Override
345     public long getLongValue() throws IOException, JsonParseException {
346         return currentNumericNode().longValue();
347     }
348 
349     @Override
350     public int getIntValue() throws IOException, JsonParseException {
351         JsonNode numericNode = currentNumericNode();
352         if (numericNode.canConvertToInt()) {
353             return currentNumericNode().intValue();
354         }
355         throw _constructError("Numeric value ("+getText()+") out of range of Java short");
356     }
357 
358     @Override
359     public Number getNumberValue() throws IOException, JsonParseException {
360         return currentNumericNode().numberValue();
361     }
362 
363     @Override
364     public Object getEmbeddedObject() {
365         return null;
366     }
367 
368     /*
369     /**********************************************************
370     /* Public API, typed binary (base64) access
371     /**********************************************************
372      */
373 
374     @Override
375     public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException {
376         // otherwise return null to mark we have no binary content
377         return null;
378     }
379 
380 
381     @Override
382     public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException, JsonParseException {
383         return 0;
384     }
385 
386     /*
387     /**********************************************************
388     /* Internal methods
389     /**********************************************************
390      */
391 
392     protected ConfigValue currentNode() {
393         if (_closed || _nodeCursor == null) {
394             return null;
395         }
396         return _nodeCursor.currentNode();
397     }
398 
399     protected JsonNode currentNumericNode() throws JsonParseException {
400         ConfigValue configValue = currentNode();
401         if ((configValue == null) || (configValue.valueType() != ConfigValueType.NUMBER)) {
402             JsonToken t = (configValue == null) ? null : ConfigNodeCursor.forConfigValue(configValue);
403             throw _constructError("Current token ("+t+") not numeric, can not use numeric value accessors");
404         }
405         Number value = (Number) configValue.unwrapped();
406         if (value instanceof Double) {
407             return JsonNodeFactory.instance.numberNode((Double) value);
408         }
409         if (value instanceof Long) {
410             return JsonNodeFactory.instance.numberNode((Long) value);
411         }
412         if (value instanceof Integer) {
413             return JsonNodeFactory.instance.numberNode((Integer) value);
414         }
415         // only possible if Config has since added more numeric types
416         throw _constructError(value.getClass() + " is not a supported numeric config type");
417     }
418 
419     @Override
420     protected void _handleEOF() throws JsonParseException {
421         _throwInternal(); // should never get called
422     }
423 }