1 /*
2  * Copyright (C) 2011 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package com.google.gson.internal.bind;
18
19 import com.google.gson.JsonArray;
20 import com.google.gson.JsonElement;
21 import com.google.gson.JsonNull;
22 import com.google.gson.JsonObject;
23 import com.google.gson.JsonPrimitive;
24 import com.google.gson.stream.JsonWriter;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.util.ArrayList;
28 import java.util.List;
29
30 /**
31  * This writer creates a JsonElement.
32  */

33 public final class JsonTreeWriter extends JsonWriter {
34   private static final Writer UNWRITABLE_WRITER = new Writer() {
35     @Override public void write(char[] buffer, int offset, int counter) {
36       throw new AssertionError();
37     }
38     @Override public void flush() throws IOException {
39       throw new AssertionError();
40     }
41     @Override public void close() throws IOException {
42       throw new AssertionError();
43     }
44   };
45   /** Added to the top of the stack when this writer is closed to cause following ops to fail. */
46   private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed");
47
48   /** The JsonElements and JsonArrays under modification, outermost to innermost. */
49   private final List<JsonElement> stack = new ArrayList<JsonElement>();
50
51   /** The name for the next JSON object value. If non-null, the top of the stack is a JsonObject. */
52   private String pendingName;
53
54   /** the JSON element constructed by this writer. */
55   private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?;
56
57   public JsonTreeWriter() {
58     super(UNWRITABLE_WRITER);
59   }
60
61   /**
62    * Returns the top level object produced by this writer.
63    */

64   public JsonElement get() {
65     if (!stack.isEmpty()) {
66       throw new IllegalStateException("Expected one JSON element but was " + stack);
67     }
68     return product;
69   }
70
71   private JsonElement peek() {
72     return stack.get(stack.size() - 1);
73   }
74
75   private void put(JsonElement value) {
76     if (pendingName != null) {
77       if (!value.isJsonNull() || getSerializeNulls()) {
78         JsonObject object = (JsonObject) peek();
79         object.add(pendingName, value);
80       }
81       pendingName = null;
82     } else if (stack.isEmpty()) {
83       product = value;
84     } else {
85       JsonElement element = peek();
86       if (element instanceof JsonArray) {
87         ((JsonArray) element).add(value);
88       } else {
89         throw new IllegalStateException();
90       }
91     }
92   }
93
94   @Override public JsonWriter beginArray() throws IOException {
95     JsonArray array = new JsonArray();
96     put(array);
97     stack.add(array);
98     return this;
99   }
100
101   @Override public JsonWriter endArray() throws IOException {
102     if (stack.isEmpty() || pendingName != null) {
103       throw new IllegalStateException();
104     }
105     JsonElement element = peek();
106     if (element instanceof JsonArray) {
107       stack.remove(stack.size() - 1);
108       return this;
109     }
110     throw new IllegalStateException();
111   }
112
113   @Override public JsonWriter beginObject() throws IOException {
114     JsonObject object = new JsonObject();
115     put(object);
116     stack.add(object);
117     return this;
118   }
119
120   @Override public JsonWriter endObject() throws IOException {
121     if (stack.isEmpty() || pendingName != null) {
122       throw new IllegalStateException();
123     }
124     JsonElement element = peek();
125     if (element instanceof JsonObject) {
126       stack.remove(stack.size() - 1);
127       return this;
128     }
129     throw new IllegalStateException();
130   }
131
132   @Override public JsonWriter name(String name) throws IOException {
133     if (stack.isEmpty() || pendingName != null) {
134       throw new IllegalStateException();
135     }
136     JsonElement element = peek();
137     if (element instanceof JsonObject) {
138       pendingName = name;
139       return this;
140     }
141     throw new IllegalStateException();
142   }
143
144   @Override public JsonWriter value(String value) throws IOException {
145     if (value == null) {
146       return nullValue();
147     }
148     put(new JsonPrimitive(value));
149     return this;
150   }
151
152   @Override public JsonWriter nullValue() throws IOException {
153     put(JsonNull.INSTANCE);
154     return this;
155   }
156
157   @Override public JsonWriter value(boolean value) throws IOException {
158     put(new JsonPrimitive(value));
159     return this;
160   }
161
162   @Override public JsonWriter value(Boolean value) throws IOException {
163     if (value == null) {
164       return nullValue();
165     }
166     put(new JsonPrimitive(value));
167     return this;
168   }
169
170   @Override public JsonWriter value(double value) throws IOException {
171     if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) {
172       throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
173     }
174     put(new JsonPrimitive(value));
175     return this;
176   }
177
178   @Override public JsonWriter value(long value) throws IOException {
179     put(new JsonPrimitive(value));
180     return this;
181   }
182
183   @Override public JsonWriter value(Number value) throws IOException {
184     if (value == null) {
185       return nullValue();
186     }
187
188     if (!isLenient()) {
189       double d = value.doubleValue();
190       if (Double.isNaN(d) || Double.isInfinite(d)) {
191         throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
192       }
193     }
194
195     put(new JsonPrimitive(value));
196     return this;
197   }
198
199   @Override public void flush() throws IOException {
200   }
201
202   @Override public void close() throws IOException {
203     if (!stack.isEmpty()) {
204       throw new IOException("Incomplete document");
205     }
206     stack.add(SENTINEL_CLOSED);
207   }
208 }
209