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 javax.annotation.Nullable;
17  
18  import java.util.Iterator;
19  import java.util.Map;
20  
21  import com.fasterxml.jackson.core.JsonStreamContext;
22  import com.fasterxml.jackson.core.JsonToken;
23  import com.typesafe.config.ConfigList;
24  import com.typesafe.config.ConfigObject;
25  import com.typesafe.config.ConfigValue;
26  import com.typesafe.config.ConfigValueType;
27  
28  import static com.typesafe.config.ConfigValueType.LIST;
29  import static com.typesafe.config.ConfigValueType.OBJECT;
30  
31  abstract class ConfigNodeCursor extends JsonStreamContext {
32      /**
33       * Parent cursor of this cursor, if any; null for root
34       * cursors.
35       */
36      protected final ConfigNodeCursor _parent;
37  
38      /**
39       * Current field name
40       */
41      @Nullable protected String _currentName;
42  
43      public ConfigNodeCursor(int contextType, ConfigNodeCursor p) {
44          super();
45          _type = contextType;
46          _index = -1;
47          _parent = p;
48      }
49  
50      /*
51      /**********************************************************
52      /* JsonStreamContext impl
53      /**********************************************************
54       */
55  
56      // note: co-variant return type
57      @Override
58      public final ConfigNodeCursor getParent() { return _parent; }
59  
60      @Override
61      @Nullable
62      public final String getCurrentName() {
63          return _currentName;
64      }
65  
66      /**
67       * @since 2.0
68       */
69      public void overrideCurrentName(String name) {
70          _currentName = name;
71      }
72      
73      /*
74      /**********************************************************
75      /* Extended API
76      /**********************************************************
77       */
78  
79      public abstract JsonToken nextToken();
80  
81      public abstract JsonToken nextValue();
82  
83      public abstract JsonToken endToken();
84  
85      public abstract ConfigValue currentNode();
86  
87      public abstract boolean currentHasChildren();
88  
89      /**
90       * Method called to create a new context for iterating all
91       * contents of the current structured value (JSON array or object)
92       */
93      public final ConfigNodeCursor iterateChildren() {
94          ConfigValue n = currentNode();
95          if (n == null) throw new IllegalStateException("No current node");
96          if (n.valueType() == LIST) { // false since we have already returned START_ARRAY
97              return new Array((ConfigList) n, this);
98          }
99          if (n.valueType() == OBJECT) {
100             return new Object((ConfigObject) n, this);
101         }
102         throw new IllegalStateException("Current node of type " + n.getClass().getName());
103     }
104 
105     static JsonToken forConfigValue(ConfigValue configValue) {
106         ConfigValueType valueType = configValue.valueType();
107         switch (valueType) {
108             case NUMBER:
109                 if (configValue.unwrapped() instanceof Double) {
110                     return JsonToken.VALUE_NUMBER_FLOAT;
111                 } else {
112                     return JsonToken.VALUE_NUMBER_INT;
113                 }
114             case BOOLEAN:
115                 if (configValue.unwrapped().equals(Boolean.TRUE)) {
116                     return JsonToken.VALUE_TRUE;
117                 } else {
118                     return JsonToken.VALUE_FALSE;
119                 }
120             case NULL:
121                 return JsonToken.VALUE_NULL;
122             case STRING:
123                 return JsonToken.VALUE_STRING;
124             case OBJECT:
125                 return JsonToken.START_OBJECT;
126             case LIST:
127                 return JsonToken.START_ARRAY;
128             default:
129                 // not possible unless the set of enums changes on us later
130                 throw new IllegalArgumentException(valueType.name() + " is not a supported ConfigValueType");
131         }
132     }
133 
134     /*
135     /**********************************************************
136     /* Concrete implementations
137     /**********************************************************
138      */
139 
140     /**
141      * Context matching root-level value nodes (i.e. anything other
142      * than JSON Object and Array).
143      * Note that context is NOT created for leaf values.
144      */
145     protected static final class RootValue extends ConfigNodeCursor {
146         @Nullable private ConfigValue _node;
147 
148         private boolean _done = false;
149 
150         public RootValue(ConfigValue n, ConfigNodeCursor p) {
151             super(JsonStreamContext.TYPE_ROOT, p);
152             _node = n;
153         }
154 
155         @Override
156         public void overrideCurrentName(String name) {}
157 
158         @Override
159         @Nullable
160         public JsonToken nextToken() {
161             if (!_done) {
162                 _done = true;
163                 return forConfigValue(_node);
164             }
165             _node = null;
166             return null;
167         }
168 
169         @Override
170         @Nullable
171         public JsonToken nextValue() { return nextToken(); }
172 
173         @Override
174         @Nullable
175         public JsonToken endToken() { return null; }
176 
177         @Override
178         public ConfigValue currentNode() { return _node; }
179 
180         @Override
181         public boolean currentHasChildren() { return false; }
182     }
183 
184     /**
185      * Cursor used for traversing non-empty JSON Array nodes
186      */
187     protected static final class Array extends ConfigNodeCursor {
188         private final Iterator<ConfigValue> _contents;
189 
190         @Nullable private ConfigValue _currentNode;
191 
192         public Array(ConfigList n, ConfigNodeCursor p) {
193             super(JsonStreamContext.TYPE_ARRAY, p);
194             _contents = n.iterator();
195         }
196 
197         @Override
198         @Nullable
199         public JsonToken nextToken() {
200             if (!_contents.hasNext()) {
201                 _currentNode = null;
202                 return null;
203             }
204             _currentNode = _contents.next();
205             return forConfigValue(_currentNode);
206         }
207 
208         @Override
209         public JsonToken nextValue() { return nextToken(); }
210 
211         @Override
212         public JsonToken endToken() { return JsonToken.END_ARRAY; }
213 
214         @Override
215         public ConfigValue currentNode() { return _currentNode; }
216 
217         @Override
218         public boolean currentHasChildren() {
219             // note: ONLY to be called for container nodes
220             ConfigValue currentValue = currentNode();
221             if (currentValue.valueType() == ConfigValueType.LIST) {
222                 return !((ConfigList) currentValue).isEmpty();
223             } else {
224                 return !((ConfigObject) currentValue).isEmpty();
225             }
226         }
227     }
228 
229     /**
230      * Cursor used for traversing non-empty JSON Object nodes
231      */
232     protected static final class Object extends ConfigNodeCursor {
233         private final Iterator<Map.Entry<String, ConfigValue>> _contents;
234 
235         @Nullable private Map.Entry<String, ConfigValue> _current;
236         private boolean _needEntry;
237 
238         public Object(ConfigObject n, ConfigNodeCursor p) {
239             super(JsonStreamContext.TYPE_OBJECT, p);
240             _contents = n.entrySet().iterator();
241             _needEntry = true;
242         }
243 
244         @Override
245         @Nullable
246         public JsonToken nextToken() {
247             // Need a new entry?
248             if (_needEntry) {
249                 if (!_contents.hasNext()) {
250                     _currentName = null;
251                     _current = null;
252                     return null;
253                 }
254                 _needEntry = false;
255                 _current = _contents.next();
256                 _currentName = (_current == null) ? null : _current.getKey();
257                 return JsonToken.FIELD_NAME;
258             }
259             _needEntry = true;
260             return forConfigValue(_current.getValue());
261         }
262 
263         @Override
264         public JsonToken nextValue() {
265             JsonToken t = nextToken();
266             if (t == JsonToken.FIELD_NAME) {
267                 t = nextToken();
268             }
269             return t;
270         }
271 
272         @Override
273         public JsonToken endToken() { return JsonToken.END_OBJECT; }
274 
275         @Override
276         public ConfigValue currentNode() {
277             if (_current == null) {
278                 return null;
279             } else {
280                 return _current.getValue();
281             }
282         }
283 
284         @Override
285         public boolean currentHasChildren() {
286             // note: ONLY to be called for container nodes
287             ConfigValue currentValue = currentNode();
288             if (currentValue.valueType() == ConfigValueType.LIST) {
289                 return !((ConfigList) currentValue).isEmpty();
290             } else {
291                 return !((ConfigObject) currentValue).isEmpty();
292             }
293         }
294     }
295 }