001package ezvcard.io.json;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.List;
007
008import ezvcard.property.Categories;
009import ezvcard.property.Note;
010import ezvcard.property.StructuredName;
011
012/*
013 Copyright (c) 2012-2023, 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 The views and conclusions contained in the software and documentation are those
037 of the authors and should not be interpreted as representing official policies, 
038 either expressed or implied, of the FreeBSD Project.
039 */
040
041/**
042 * Holds the data type and value of a jCard property.
043 * @author Michael Angstadt
044 */
045public class JCardValue {
046        private final List<JsonValue> values;
047
048        /**
049         * Creates a new jCard value.
050         * @param values the values
051         */
052        public JCardValue(List<JsonValue> values) {
053                this.values = Collections.unmodifiableList(values);
054        }
055
056        /**
057         * Creates a new jCard value.
058         * @param values the values
059         */
060        public JCardValue(JsonValue... values) {
061                this.values = Arrays.asList(values); //unmodifiable
062        }
063
064        /**
065         * Creates a single-valued value.
066         * @param value the value
067         * @return the jCard value
068         */
069        public static JCardValue single(Object value) {
070                return new JCardValue(new JsonValue(value));
071        }
072
073        /**
074         * Creates a multi-valued value.
075         * @param values the values
076         * @return the jCard value
077         */
078        public static JCardValue multi(Object... values) {
079                return multi(Arrays.asList(values));
080        }
081
082        /**
083         * Creates a multi-valued value.
084         * @param values the values
085         * @return the jCard value
086         */
087        public static JCardValue multi(List<?> values) {
088                List<JsonValue> multiValues = new ArrayList<>(values.size());
089                for (Object value : values) {
090                        multiValues.add(new JsonValue(value));
091                }
092                return new JCardValue(multiValues);
093        }
094
095        /**
096         * <p>
097         * Creates a structured value.
098         * </p>
099         * <p>
100         * This method accepts a vararg of {@link Object} instances. {@link List}
101         * objects will be treated as multi-valued components. Null objects will be
102         * treated as empty components.
103         * </p>
104         * @param values the values
105         * @return the jCard value
106         */
107        public static JCardValue structured(Object... values) {
108                List<List<?>> valuesList = new ArrayList<>(values.length);
109                for (Object value : values) {
110                        List<?> list = (value instanceof List) ? (List<?>) value : Collections.singletonList(value);
111                        valuesList.add(list);
112                }
113                return structured(valuesList);
114        }
115
116        /**
117         * Creates a structured value.
118         * @param values the values
119         * @return the jCard value
120         */
121        public static JCardValue structured(List<List<?>> values) {
122                List<JsonValue> array = new ArrayList<>(values.size());
123
124                for (List<?> list : values) {
125                        if (list.isEmpty()) {
126                                array.add(new JsonValue(""));
127                                continue;
128                        }
129
130                        if (list.size() == 1) {
131                                Object value = list.get(0);
132                                if (value == null) {
133                                        value = "";
134                                }
135                                array.add(new JsonValue(value));
136                                continue;
137                        }
138
139                        List<JsonValue> subArray = new ArrayList<>(list.size());
140                        for (Object value : list) {
141                                if (value == null) {
142                                        value = "";
143                                }
144                                subArray.add(new JsonValue(value));
145                        }
146                        array.add(new JsonValue(subArray));
147                }
148
149                return new JCardValue(new JsonValue(array));
150        }
151
152        /**
153         * Gets all the JSON values.
154         * @return the JSON values
155         */
156        public List<JsonValue> getValues() {
157                return values;
158        }
159
160        /**
161         * Gets the value of a single-valued property (such as {@link Note}).
162         * @return the value or empty string if not found
163         */
164        public String asSingle() {
165                if (values.isEmpty()) {
166                        return "";
167                }
168
169                JsonValue first = values.get(0);
170                if (first.isNull()) {
171                        return "";
172                }
173
174                Object obj = first.getValue();
175                if (obj != null) {
176                        return obj.toString();
177                }
178
179                //get the first element of the array
180                List<JsonValue> array = first.getArray();
181                if (array != null && !array.isEmpty()) {
182                        obj = array.get(0).getValue();
183                        if (obj != null) {
184                                return obj.toString();
185                        }
186                }
187
188                return "";
189        }
190
191        /**
192         * Gets the value of a structured property (such as {@link StructuredName}).
193         * @return the values or empty list if not found
194         */
195        public List<List<String>> asStructured() {
196                if (values.isEmpty()) {
197                        return Collections.emptyList();
198                }
199
200                JsonValue first = values.get(0);
201
202                //["gender", {}, "text", ["M", "text"] ]
203                List<JsonValue> array = first.getArray();
204                if (array != null) {
205                        List<List<String>> components = new ArrayList<>(array.size());
206                        for (JsonValue value : array) {
207                                if (value.isNull()) {
208                                        components.add(Collections.<String>emptyList());
209                                        continue;
210                                }
211
212                                Object obj = value.getValue();
213                                if (obj != null) {
214                                        String s = obj.toString();
215                                        List<String> component = s.isEmpty() ? Collections.<String>emptyList() : Collections.singletonList(s);
216                                        components.add(component);
217                                        continue;
218                                }
219
220                                List<JsonValue> subArray = value.getArray();
221                                if (subArray != null) {
222                                        List<String> component = new ArrayList<>(subArray.size());
223                                        for (JsonValue subArrayValue : subArray) {
224                                                if (subArrayValue.isNull()) {
225                                                        component.add("");
226                                                        continue;
227                                                }
228
229                                                obj = subArrayValue.getValue();
230                                                if (obj != null) {
231                                                        component.add(obj.toString());
232                                                        continue;
233                                                }
234                                        }
235
236                                        if (component.size() == 1 && component.get(0).isEmpty()) {
237                                                component.clear();
238                                        }
239                                        components.add(component);
240                                }
241                        }
242                        return components;
243                }
244
245                //get the first value if it's not enclosed in an array
246                //["gender", {}, "text", "M"]
247                Object obj = first.getValue();
248                if (obj != null) {
249                        List<List<String>> components = new ArrayList<>(1);
250                        String s = obj.toString();
251                        List<String> component = s.isEmpty() ? Collections.<String>emptyList() : Collections.singletonList(s);
252                        components.add(component);
253                        return components;
254                }
255
256                //["gender", {}, "text", null]
257                if (first.isNull()) {
258                        List<List<String>> components = new ArrayList<>(1);
259                        components.add(Collections.<String>emptyList());
260                        return components;
261                }
262
263                return Collections.emptyList();
264        }
265
266        /**
267         * Gets the value of a multi-valued property (such as {@link Categories} ).
268         * @return the values or empty list if not found
269         */
270        public List<String> asMulti() {
271                if (values.isEmpty()) {
272                        return Collections.emptyList();
273                }
274
275                List<String> multi = new ArrayList<>(values.size());
276                for (JsonValue value : values) {
277                        if (value.isNull()) {
278                                multi.add("");
279                                continue;
280                        }
281
282                        Object obj = value.getValue();
283                        if (obj != null) {
284                                multi.add(obj.toString());
285                                continue;
286                        }
287                }
288                return multi;
289        }
290
291        @Override
292        public boolean equals(Object o) {
293                if (this == o) {
294                        return true;
295                }
296                if (o == null || getClass() != o.getClass()) {
297                        return false;
298                }
299
300                JCardValue that = (JCardValue) o;
301
302                if (values != null ? !values.equals(that.values) : that.values != null) {
303                        return false;
304                }
305
306                return true;
307        }
308
309        @Override
310        public int hashCode() {
311                return values != null ? values.hashCode() : 0;
312        }
313}