001package biweekly.io.json;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.LinkedHashMap;
007import java.util.List;
008import java.util.Map;
009
010import biweekly.util.ListMultimap;
011
012/*
013 Copyright (c) 2013-2015, 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 */
041public 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}