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    }