View Javadoc
1   package com.exsoinn.util.epf;
2   
3   import com.google.gson.JsonArray;
4   import com.google.gson.JsonElement;
5   import com.google.gson.JsonObject;
6   import com.google.gson.JsonParser;
7   import net.jcip.annotations.Immutable;
8   
9   import java.util.*;
10  
11  /**
12   * Implementation of {@link AbstractContext} to operate on JSON structures, with the aid of third party
13   * <a href="https://google.github.io/gson/apidocs/">google JSON API</a>.
14   *
15   * Created by QuijadaJ on 5/4/2017.
16   */
17  @Immutable
18  class JsonContext extends AbstractContext {
19      private final JsonElement je;
20  
21  
22      /**
23       * In order to keep our invariants true, this constructor defensively re-generates the JsonElement object. See
24       * {@link JsonContext#generateBrandNewJsonElementObject(JsonElement)} for details.
25       * @param pJsonElement - pJsonELement
26       */
27      JsonContext(JsonElement pJsonElement) {
28          this(pJsonElement, false);
29      }
30  
31  
32      JsonContext(JsonElement pJsonElement, boolean pCreateCopy) {
33          if (pCreateCopy) {
34              je = generateBrandNewJsonElementObject(pJsonElement);
35          } else {
36              je = pJsonElement;
37          }
38      }
39  
40  
41      /**
42       * Takes the passed in {@code JsonElement} and generates a brand new object. This way the client code can't
43       * break the invariants of this class.
44       * This is done by converting the passed in {@link JsonElement} to string if it is a complex, and then invoking
45       * {@link JsonParser#parse(String)} to generate a brand new {@code JsonElement}.
46       * If the passed is {@code JsonElement} is a primitive then just return it as is,
47       * because a primitive is already a {@code String}, and {@code String}'s are immutable in Java. Besides JSON
48       * parser will not be able to parse primitives because they do not conform to JSON format.
49       * @param pJsonElem - pJsonElem
50       * @return - TODO
51       */
52      static JsonElement generateBrandNewJsonElementObject(JsonElement pJsonElem) {
53          if (pJsonElem.isJsonPrimitive()) {
54              return pJsonElem;
55          } else {
56              String jsonAsString;
57              try {
58                  /*
59                   * When calling JsonArray.getAsString(), the Google JSON API will "unwrap" from array if it's a
60                   * single element array and it is a primitive element. We don't want that for our purposes; we
61                   * still want it represented as an array even if just one element, hence the check below call
62                   * toString() in "else" when JsonArray contains just one element, and not getAsString().
63                   * See "https://google.github.io/gson/apidocs/com/google/gson/JsonArray.html#getAsString--"
64                   * for reference.
65                   */
66                  if (!pJsonElem.isJsonArray() || pJsonElem.getAsJsonArray().size() > 1) {
67                      jsonAsString = pJsonElem.getAsString();
68                  } else {
69                      jsonAsString = pJsonElem.toString();
70                  }
71              } catch (Exception e) {
72                  jsonAsString = pJsonElem.toString();
73              }
74              JsonParser jp = new JsonParser();
75              return jp.parse(jsonAsString);
76          }
77      }
78  
79      @Override
80      public boolean isPrimitive() {
81          return je.isJsonPrimitive();
82      }
83  
84      @Override
85      public boolean isRecursible() {
86          return je.isJsonObject();
87      }
88  
89      @Override
90      public boolean isArray() {
91          return je.isJsonArray();
92      }
93  
94      @Override
95      public Context entryFromArray(int pIdx)
96              throws IllegalStateException {
97          if (!isArray()) {
98              throw new IllegalStateException("This is not an array element, " + je);
99          }
100 
101         return new JsonContext(je.getAsJsonArray().get(pIdx));
102     }
103 
104     @Override
105     public String stringRepresentation() {
106         /*
107          * The Google JSON API says that this operation will not work for all element types,
108          * therefore to make our lives easier, silently catch problems if any, and fallback to toString()
109          * if things go awry.
110          */
111         try {
112             return je.getAsString();
113         } catch (Exception e) {
114             return je.toString();
115         }
116     }
117 
118 
119     @Override
120     public String toString() {
121         return je.toString();
122     }
123 
124 
125     /**
126      * TODO: Can perhaps return unmodifiable list of the same JsonArray entries. Would need to overload
127      * TODO: constructor to pass flag that tells it not to generate a new JsonElement
128      * @return - TODO
129      * @throws IllegalStateException - TODO
130      */
131     @Override
132     public List<Context> asArray() throws IllegalStateException {
133         if (!je.isJsonArray()) {
134             throw new IllegalStateException("Object is not a JSON array, therefore asArray() call is invalid: " + je);
135         }
136         List<Context> list = new ArrayList<>();
137         JsonArray ja = je.getAsJsonArray();
138 
139 
140         ja.iterator().forEachRemaining(e -> list.add(new JsonContext(e)));
141 
142         return Collections.unmodifiableList(list);
143     }
144 
145 
146     /**
147      *
148      * @param pElemName - pElemName
149      * @return - TODO
150      */
151     @Override
152     public boolean containsElement(String pElemName) {
153         if (!je.isJsonObject()) {
154             throw new IllegalStateException("Expected a JSON object, but found '" + je.getClass().getName()
155                     + "', therefore containsElement() call is invalid. Element was: " + je);
156         }
157 
158         return ((JsonObject)je).has(pElemName);
159     }
160 
161 
162     /**
163      * If this is a {@link JsonObject}, return an {@link Set} of {@link Map.Entry} of the members
164      * of this object.
165      * @return - TODO
166      * @throws IllegalStateException - TODO
167      */
168     @Override
169     public Set<Map.Entry<String, Context>> entrySet() throws IllegalStateException {
170         if (!je.isJsonObject()) {
171             throw new IllegalStateException("Object is not an JSON object, therefore entrySet() call is invalid: " + je);
172         }
173 
174         JsonObject jo = je.getAsJsonObject();
175         Set<Map.Entry<String, JsonElement>> ents = jo.entrySet();
176         Map<String, Context> newMap = new LinkedHashMap<>();
177         /**
178          * Defensively generate brand new JsonContext objects so that client cannot break
179          * the invariants of this class
180          */
181         ents.forEach(e -> newMap.put(e.getKey(), new JsonContext(e.getValue())));
182         return Collections.unmodifiableSet(newMap.entrySet());
183     }
184 
185     @Override
186     public Context memberValue(String pMemberName) throws IllegalStateException {
187         if (!je.isJsonObject()) {
188             throw new IllegalStateException("Object is not an JSON object, therefore memberValue() call is invalid: " + je);
189         }
190 
191         return new JsonContext(je.getAsJsonObject().get(pMemberName));
192     }
193 
194     @Override
195     public boolean arrayContains(String pVal) throws IllegalStateException {
196         if (!je.isJsonArray()) {
197             throw new IllegalStateException("Object is not an JSON array, therefore arrayContains call is invalid: " + je);
198         }
199 
200         for (Context c : asArray()) {
201             if (pVal.equals(c.stringRepresentation())) {
202                 return true;
203             }
204         }
205         return false;
206     }
207 
208 
209 
210     JsonElement unwrap() {
211         return je;
212     }
213 }