001 package biweekly.io.json;
002
003 import java.util.ArrayList;
004 import java.util.Arrays;
005 import java.util.Collections;
006 import java.util.LinkedHashMap;
007 import java.util.List;
008 import java.util.Map;
009
010 import biweekly.util.ListMultimap;
011
012 /*
013 Copyright (c) 2013, Michael Angstadt
014 All rights reserved.
015
016 Redistribution and use in source and binary forms, with or without
017 modification, are permitted provided that the following conditions are met:
018
019 1. Redistributions of source code must retain the above copyright notice, this
020 list of conditions and the following disclaimer.
021 2. Redistributions in binary form must reproduce the above copyright notice,
022 this list of conditions and the following disclaimer in the documentation
023 and/or other materials provided with the distribution.
024
025 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
026 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
027 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
028 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
029 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
030 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
031 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
032 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
033 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
034 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
035 */
036
037 /**
038 * Holds the value of a jCal property.
039 * @author Michael Angstadt
040 */
041 public class JCalValue {
042 private final List<JsonValue> values;
043
044 /**
045 * Creates a new jCal value.
046 * @param values the values
047 */
048 public JCalValue(List<JsonValue> values) {
049 this.values = Collections.unmodifiableList(values);
050 }
051
052 /**
053 * Creates a new jCal value.
054 * @param values the values
055 */
056 public JCalValue(JsonValue... values) {
057 this.values = Arrays.asList(values); //unmodifiable
058 }
059
060 /**
061 * Creates a single-valued value.
062 * @param value the value
063 * @return the jCal value
064 */
065 public static JCalValue single(Object value) {
066 return new JCalValue(new JsonValue(value));
067 }
068
069 /**
070 * Creates a multi-valued value.
071 * @param values the values
072 * @return the jCal value
073 */
074 public static JCalValue multi(Object... values) {
075 return multi(Arrays.asList(values));
076 }
077
078 /**
079 * Creates a multi-valued value.
080 * @param values the values
081 * @return the jCal value
082 */
083 public static JCalValue multi(List<?> values) {
084 List<JsonValue> multiValues = new ArrayList<JsonValue>(values.size());
085 for (Object value : values) {
086 multiValues.add(new JsonValue(value));
087 }
088 return new JCalValue(multiValues);
089 }
090
091 /**
092 * <p>
093 * Creates a structured value.
094 * </p>
095 * <p>
096 * This method accepts a vararg of {@link Object} instances. {@link List}
097 * objects will be treated as multi-valued components. All other objects.
098 * Null values will be treated as empty components.
099 * </p>
100 * @param values the values
101 * @return the jCal value
102 */
103 public static JCalValue structured(Object... values) {
104 List<List<?>> valuesList = new ArrayList<List<?>>(values.length);
105 for (Object value : values) {
106 List<?> list = (value instanceof List) ? (List<?>) value : Arrays.asList(value);
107 valuesList.add(list);
108 }
109 return structured(valuesList);
110 }
111
112 /**
113 * Creates a structured value.
114 * @param values the values
115 * @return the jCal value
116 */
117 public static JCalValue structured(List<List<?>> values) {
118 List<JsonValue> array = new ArrayList<JsonValue>(values.size());
119
120 for (List<?> list : values) {
121 if (list.isEmpty()) {
122 array.add(new JsonValue(""));
123 continue;
124 }
125
126 if (list.size() == 1) {
127 Object value = list.get(0);
128 if (value == null) {
129 value = "";
130 }
131 array.add(new JsonValue(value));
132 continue;
133 }
134
135 List<JsonValue> subArray = new ArrayList<JsonValue>(list.size());
136 for (Object value : list) {
137 if (value == null) {
138 value = "";
139 }
140 subArray.add(new JsonValue(value));
141 }
142 array.add(new JsonValue(subArray));
143 }
144
145 return new JCalValue(new JsonValue(array));
146 }
147
148 /**
149 * Creates an object value.
150 * @param value the object
151 * @return the jCal value
152 */
153 public static JCalValue object(ListMultimap<String, Object> value) {
154 Map<String, JsonValue> object = new LinkedHashMap<String, JsonValue>();
155 for (Map.Entry<String, List<Object>> entry : value) {
156 String key = entry.getKey();
157 List<Object> list = entry.getValue();
158
159 JsonValue v;
160 if (list.size() == 1) {
161 v = new JsonValue(list.get(0));
162 } else {
163 List<JsonValue> array = new ArrayList<JsonValue>(list.size());
164 for (Object element : list) {
165 array.add(new JsonValue(element));
166 }
167 v = new JsonValue(array);
168 }
169 object.put(key, v);
170 }
171 return new JCalValue(new JsonValue(object));
172 }
173
174 /**
175 * Gets the raw JSON values. Use one of the "{@code as*}" methods to parse
176 * the values as one of the standard jCal values.
177 * @return the JSON values
178 */
179 public List<JsonValue> getValues() {
180 return values;
181 }
182
183 /**
184 * Parses this jCal value as a single-valued property value.
185 * @return the value or empty string if not found
186 */
187 public String asSingle() {
188 if (values.isEmpty()) {
189 return "";
190 }
191
192 JsonValue first = values.get(0);
193 if (first.isNull()) {
194 return "";
195 }
196
197 Object obj = first.getValue();
198 if (obj != null) {
199 return obj.toString();
200 }
201
202 //get the first element of the array
203 List<JsonValue> array = first.getArray();
204 if (array != null && !array.isEmpty()) {
205 obj = array.get(0).getValue();
206 if (obj != null) {
207 return obj.toString();
208 }
209 }
210
211 return "";
212 }
213
214 /**
215 * Parses this jCal value as a structured property value.
216 * @return the structured values or empty list if not found
217 */
218 public List<List<String>> asStructured() {
219 if (values.isEmpty()) {
220 return Collections.emptyList();
221 }
222
223 JsonValue first = values.get(0);
224
225 //["request-status", {}, "text", ["2.0", "Success"] ]
226 List<JsonValue> array = first.getArray();
227 if (array != null) {
228 List<List<String>> valuesStr = new ArrayList<List<String>>(array.size());
229 for (JsonValue value : array) {
230 if (value.isNull()) {
231 valuesStr.add(Arrays.asList(""));
232 continue;
233 }
234
235 Object obj = value.getValue();
236 if (obj != null) {
237 valuesStr.add(Arrays.asList(obj.toString()));
238 continue;
239 }
240
241 List<JsonValue> subArray = value.getArray();
242 if (subArray != null) {
243 List<String> subValuesStr = new ArrayList<String>(subArray.size());
244 for (JsonValue subArrayValue : subArray) {
245 if (subArrayValue.isNull()) {
246 subValuesStr.add("");
247 continue;
248 }
249
250 obj = subArrayValue.getValue();
251 if (obj != null) {
252 subValuesStr.add(obj.toString());
253 continue;
254 }
255 }
256 valuesStr.add(subValuesStr);
257 }
258 }
259 return valuesStr;
260 }
261
262 //get the first value if it's not enclosed in an array
263 //["request-status", {}, "text", "2.0"]
264 Object obj = first.getValue();
265 if (obj != null) {
266 List<List<String>> values = new ArrayList<List<String>>(1);
267 values.add(Arrays.asList(obj.toString()));
268 return values;
269 }
270
271 //["request-status", {}, "text", null]
272 if (first.isNull()) {
273 List<List<String>> values = new ArrayList<List<String>>(1);
274 values.add(Arrays.asList(""));
275 return values;
276 }
277
278 return Collections.emptyList();
279 }
280
281 /**
282 * Parses this jCal value as a multi-valued property value.
283 * @return the values or empty list if not found
284 */
285 public List<String> asMulti() {
286 if (values.isEmpty()) {
287 return Collections.emptyList();
288 }
289
290 List<String> multi = new ArrayList<String>(values.size());
291 for (JsonValue value : values) {
292 if (value.isNull()) {
293 multi.add("");
294 continue;
295 }
296
297 Object obj = value.getValue();
298 if (obj != null) {
299 multi.add(obj.toString());
300 continue;
301 }
302 }
303 return multi;
304 }
305
306 /**
307 * Parses this jCal value as an object property value.
308 * @return the object or an empty map if not found
309 */
310 public ListMultimap<String, String> asObject() {
311 if (values.isEmpty()) {
312 return new ListMultimap<String, String>(0);
313 }
314
315 Map<String, JsonValue> map = values.get(0).getObject();
316 if (map == null) {
317 return new ListMultimap<String, String>(0);
318 }
319
320 ListMultimap<String, String> values = new ListMultimap<String, String>();
321 for (Map.Entry<String, JsonValue> entry : map.entrySet()) {
322 String key = entry.getKey();
323 JsonValue value = entry.getValue();
324
325 if (value.isNull()) {
326 values.put(key, "");
327 continue;
328 }
329
330 Object obj = value.getValue();
331 if (obj != null) {
332 values.put(key, obj.toString());
333 continue;
334 }
335
336 List<JsonValue> array = value.getArray();
337 if (array != null) {
338 for (JsonValue element : array) {
339 if (element.isNull()) {
340 values.put(key, "");
341 continue;
342 }
343
344 obj = element.getValue();
345 if (obj != null) {
346 values.put(key, obj.toString());
347 }
348 }
349 }
350 }
351 return values;
352 }
353 }