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