1 /** 2 * Copyright (c) 2008, SnakeYAML 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package org.yaml.snakeyaml.representer; 15 16 import java.math.BigInteger; 17 import java.nio.charset.StandardCharsets; 18 import java.util.ArrayList; 19 import java.util.Arrays; 20 import java.util.Calendar; 21 import java.util.Date; 22 import java.util.HashMap; 23 import java.util.Iterator; 24 import java.util.LinkedHashMap; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Set; 28 import java.util.TimeZone; 29 import java.util.UUID; 30 import java.util.regex.Pattern; 31 import org.yaml.snakeyaml.DumperOptions; 32 import org.yaml.snakeyaml.error.YAMLException; 33 import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; 34 import org.yaml.snakeyaml.nodes.Node; 35 import org.yaml.snakeyaml.nodes.Tag; 36 import org.yaml.snakeyaml.reader.StreamReader; 37 38 /** 39 * Represent standard Java classes 40 */ 41 class SafeRepresenter extends BaseRepresenter { 42 43 protected Map<Class<? extends Object>, Tag> classTags; 44 protected TimeZone timeZone = null; 45 protected DumperOptions.NonPrintableStyle nonPrintableStyle; 46 SafeRepresenter()47 public SafeRepresenter() { 48 this(new DumperOptions()); 49 } 50 SafeRepresenter(DumperOptions options)51 public SafeRepresenter(DumperOptions options) { 52 this.nullRepresenter = new RepresentNull(); 53 this.representers.put(String.class, new RepresentString()); 54 this.representers.put(Boolean.class, new RepresentBoolean()); 55 this.representers.put(Character.class, new RepresentString()); 56 this.representers.put(UUID.class, new RepresentUuid()); 57 this.representers.put(byte[].class, new RepresentByteArray()); 58 59 Represent primitiveArray = new RepresentPrimitiveArray(); 60 representers.put(short[].class, primitiveArray); 61 representers.put(int[].class, primitiveArray); 62 representers.put(long[].class, primitiveArray); 63 representers.put(float[].class, primitiveArray); 64 representers.put(double[].class, primitiveArray); 65 representers.put(char[].class, primitiveArray); 66 representers.put(boolean[].class, primitiveArray); 67 68 this.multiRepresenters.put(Number.class, new RepresentNumber()); 69 this.multiRepresenters.put(List.class, new RepresentList()); 70 this.multiRepresenters.put(Map.class, new RepresentMap()); 71 this.multiRepresenters.put(Set.class, new RepresentSet()); 72 this.multiRepresenters.put(Iterator.class, new RepresentIterator()); 73 this.multiRepresenters.put(new Object[0].getClass(), new RepresentArray()); 74 this.multiRepresenters.put(Date.class, new RepresentDate()); 75 this.multiRepresenters.put(Enum.class, new RepresentEnum()); 76 this.multiRepresenters.put(Calendar.class, new RepresentDate()); 77 classTags = new HashMap<Class<? extends Object>, Tag>(); 78 this.nonPrintableStyle = options.getNonPrintableStyle(); 79 } 80 getTag(Class<?> clazz, Tag defaultTag)81 protected Tag getTag(Class<?> clazz, Tag defaultTag) { 82 if (classTags.containsKey(clazz)) { 83 return classTags.get(clazz); 84 } else { 85 return defaultTag; 86 } 87 } 88 89 /** 90 * Define a tag for the <code>Class</code> to serialize. 91 * 92 * @param clazz <code>Class</code> which tag is changed 93 * @param tag new tag to be used for every instance of the specified <code>Class</code> 94 * @return the previous tag associated with the <code>Class</code> 95 */ addClassTag(Class<? extends Object> clazz, Tag tag)96 public Tag addClassTag(Class<? extends Object> clazz, Tag tag) { 97 if (tag == null) { 98 throw new NullPointerException("Tag must be provided."); 99 } 100 return classTags.put(clazz, tag); 101 } 102 103 protected class RepresentNull implements Represent { 104 representData(Object data)105 public Node representData(Object data) { 106 return representScalar(Tag.NULL, "null"); 107 } 108 } 109 110 private static final Pattern MULTILINE_PATTERN = Pattern.compile("\n|\u0085|\u2028|\u2029"); 111 112 protected class RepresentString implements Represent { 113 representData(Object data)114 public Node representData(Object data) { 115 Tag tag = Tag.STR; 116 DumperOptions.ScalarStyle style = null;// not defined 117 String value = data.toString(); 118 if (nonPrintableStyle == DumperOptions.NonPrintableStyle.BINARY 119 && !StreamReader.isPrintable(value)) { 120 tag = Tag.BINARY; 121 char[] binary; 122 final byte[] bytes = value.getBytes(StandardCharsets.UTF_8); 123 // sometimes above will just silently fail - it will return incomplete data 124 // it happens when String has invalid code points 125 // (for example half surrogate character without other half) 126 final String checkValue = new String(bytes, StandardCharsets.UTF_8); 127 if (!checkValue.equals(value)) { 128 throw new YAMLException("invalid string value has occurred"); 129 } 130 binary = Base64Coder.encode(bytes); 131 value = String.valueOf(binary); 132 style = DumperOptions.ScalarStyle.LITERAL; 133 } 134 // if no other scalar style is explicitly set, use literal style for 135 // multiline scalars 136 if (defaultScalarStyle == DumperOptions.ScalarStyle.PLAIN 137 && MULTILINE_PATTERN.matcher(value).find()) { 138 style = DumperOptions.ScalarStyle.LITERAL; 139 } 140 return representScalar(tag, value, style); 141 } 142 } 143 144 protected class RepresentBoolean implements Represent { 145 representData(Object data)146 public Node representData(Object data) { 147 String value; 148 if (Boolean.TRUE.equals(data)) { 149 value = "true"; 150 } else { 151 value = "false"; 152 } 153 return representScalar(Tag.BOOL, value); 154 } 155 } 156 157 protected class RepresentNumber implements Represent { 158 representData(Object data)159 public Node representData(Object data) { 160 Tag tag; 161 String value; 162 if (data instanceof Byte || data instanceof Short || data instanceof Integer 163 || data instanceof Long || data instanceof BigInteger) { 164 tag = Tag.INT; 165 value = data.toString(); 166 } else { 167 Number number = (Number) data; 168 tag = Tag.FLOAT; 169 if (number.equals(Double.NaN)) { 170 value = ".NaN"; 171 } else if (number.equals(Double.POSITIVE_INFINITY)) { 172 value = ".inf"; 173 } else if (number.equals(Double.NEGATIVE_INFINITY)) { 174 value = "-.inf"; 175 } else { 176 value = number.toString(); 177 } 178 } 179 return representScalar(getTag(data.getClass(), tag), value); 180 } 181 } 182 183 protected class RepresentList implements Represent { 184 185 @SuppressWarnings("unchecked") representData(Object data)186 public Node representData(Object data) { 187 return representSequence(getTag(data.getClass(), Tag.SEQ), (List<Object>) data, 188 DumperOptions.FlowStyle.AUTO); 189 } 190 } 191 192 protected class RepresentIterator implements Represent { 193 194 @SuppressWarnings("unchecked") representData(Object data)195 public Node representData(Object data) { 196 Iterator<Object> iter = (Iterator<Object>) data; 197 return representSequence(getTag(data.getClass(), Tag.SEQ), new IteratorWrapper(iter), 198 DumperOptions.FlowStyle.AUTO); 199 } 200 } 201 202 private static class IteratorWrapper implements Iterable<Object> { 203 204 private final Iterator<Object> iter; 205 IteratorWrapper(Iterator<Object> iter)206 public IteratorWrapper(Iterator<Object> iter) { 207 this.iter = iter; 208 } 209 iterator()210 public Iterator<Object> iterator() { 211 return iter; 212 } 213 } 214 215 protected class RepresentArray implements Represent { 216 representData(Object data)217 public Node representData(Object data) { 218 Object[] array = (Object[]) data; 219 List<Object> list = Arrays.asList(array); 220 return representSequence(Tag.SEQ, list, DumperOptions.FlowStyle.AUTO); 221 } 222 } 223 224 /** 225 * Represents primitive arrays, such as short[] and float[], by converting them into equivalent 226 * List<Short> and List<Float> using the appropriate autoboxing type. 227 */ 228 protected class RepresentPrimitiveArray implements Represent { 229 representData(Object data)230 public Node representData(Object data) { 231 Class<?> type = data.getClass().getComponentType(); 232 233 if (byte.class == type) { 234 return representSequence(Tag.SEQ, asByteList(data), DumperOptions.FlowStyle.AUTO); 235 } else if (short.class == type) { 236 return representSequence(Tag.SEQ, asShortList(data), DumperOptions.FlowStyle.AUTO); 237 } else if (int.class == type) { 238 return representSequence(Tag.SEQ, asIntList(data), DumperOptions.FlowStyle.AUTO); 239 } else if (long.class == type) { 240 return representSequence(Tag.SEQ, asLongList(data), DumperOptions.FlowStyle.AUTO); 241 } else if (float.class == type) { 242 return representSequence(Tag.SEQ, asFloatList(data), DumperOptions.FlowStyle.AUTO); 243 } else if (double.class == type) { 244 return representSequence(Tag.SEQ, asDoubleList(data), DumperOptions.FlowStyle.AUTO); 245 } else if (char.class == type) { 246 return representSequence(Tag.SEQ, asCharList(data), DumperOptions.FlowStyle.AUTO); 247 } else if (boolean.class == type) { 248 return representSequence(Tag.SEQ, asBooleanList(data), DumperOptions.FlowStyle.AUTO); 249 } 250 251 throw new YAMLException("Unexpected primitive '" + type.getCanonicalName() + "'"); 252 } 253 asByteList(Object in)254 private List<Byte> asByteList(Object in) { 255 byte[] array = (byte[]) in; 256 List<Byte> list = new ArrayList<Byte>(array.length); 257 for (int i = 0; i < array.length; ++i) { 258 list.add(array[i]); 259 } 260 return list; 261 } 262 asShortList(Object in)263 private List<Short> asShortList(Object in) { 264 short[] array = (short[]) in; 265 List<Short> list = new ArrayList<Short>(array.length); 266 for (int i = 0; i < array.length; ++i) { 267 list.add(array[i]); 268 } 269 return list; 270 } 271 asIntList(Object in)272 private List<Integer> asIntList(Object in) { 273 int[] array = (int[]) in; 274 List<Integer> list = new ArrayList<Integer>(array.length); 275 for (int i = 0; i < array.length; ++i) { 276 list.add(array[i]); 277 } 278 return list; 279 } 280 asLongList(Object in)281 private List<Long> asLongList(Object in) { 282 long[] array = (long[]) in; 283 List<Long> list = new ArrayList<Long>(array.length); 284 for (int i = 0; i < array.length; ++i) { 285 list.add(array[i]); 286 } 287 return list; 288 } 289 asFloatList(Object in)290 private List<Float> asFloatList(Object in) { 291 float[] array = (float[]) in; 292 List<Float> list = new ArrayList<Float>(array.length); 293 for (int i = 0; i < array.length; ++i) { 294 list.add(array[i]); 295 } 296 return list; 297 } 298 asDoubleList(Object in)299 private List<Double> asDoubleList(Object in) { 300 double[] array = (double[]) in; 301 List<Double> list = new ArrayList<Double>(array.length); 302 for (int i = 0; i < array.length; ++i) { 303 list.add(array[i]); 304 } 305 return list; 306 } 307 asCharList(Object in)308 private List<Character> asCharList(Object in) { 309 char[] array = (char[]) in; 310 List<Character> list = new ArrayList<Character>(array.length); 311 for (int i = 0; i < array.length; ++i) { 312 list.add(array[i]); 313 } 314 return list; 315 } 316 asBooleanList(Object in)317 private List<Boolean> asBooleanList(Object in) { 318 boolean[] array = (boolean[]) in; 319 List<Boolean> list = new ArrayList<Boolean>(array.length); 320 for (int i = 0; i < array.length; ++i) { 321 list.add(array[i]); 322 } 323 return list; 324 } 325 } 326 327 protected class RepresentMap implements Represent { 328 329 @SuppressWarnings("unchecked") representData(Object data)330 public Node representData(Object data) { 331 return representMapping(getTag(data.getClass(), Tag.MAP), (Map<Object, Object>) data, 332 DumperOptions.FlowStyle.AUTO); 333 } 334 } 335 336 protected class RepresentSet implements Represent { 337 338 @SuppressWarnings("unchecked") representData(Object data)339 public Node representData(Object data) { 340 Map<Object, Object> value = new LinkedHashMap<Object, Object>(); 341 Set<Object> set = (Set<Object>) data; 342 for (Object key : set) { 343 value.put(key, null); 344 } 345 return representMapping(getTag(data.getClass(), Tag.SET), value, 346 DumperOptions.FlowStyle.AUTO); 347 } 348 } 349 350 protected class RepresentDate implements Represent { 351 representData(Object data)352 public Node representData(Object data) { 353 // because SimpleDateFormat ignores timezone we have to use Calendar 354 Calendar calendar; 355 if (data instanceof Calendar) { 356 calendar = (Calendar) data; 357 } else { 358 calendar = 359 Calendar.getInstance(getTimeZone() == null ? TimeZone.getTimeZone("UTC") : timeZone); 360 calendar.setTime((Date) data); 361 } 362 int years = calendar.get(Calendar.YEAR); 363 int months = calendar.get(Calendar.MONTH) + 1; // 0..12 364 int days = calendar.get(Calendar.DAY_OF_MONTH); // 1..31 365 int hour24 = calendar.get(Calendar.HOUR_OF_DAY); // 0..24 366 int minutes = calendar.get(Calendar.MINUTE); // 0..59 367 int seconds = calendar.get(Calendar.SECOND); // 0..59 368 int millis = calendar.get(Calendar.MILLISECOND); 369 StringBuilder buffer = new StringBuilder(String.valueOf(years)); 370 while (buffer.length() < 4) { 371 // ancient years 372 buffer.insert(0, "0"); 373 } 374 buffer.append("-"); 375 if (months < 10) { 376 buffer.append("0"); 377 } 378 buffer.append(months); 379 buffer.append("-"); 380 if (days < 10) { 381 buffer.append("0"); 382 } 383 buffer.append(days); 384 buffer.append("T"); 385 if (hour24 < 10) { 386 buffer.append("0"); 387 } 388 buffer.append(hour24); 389 buffer.append(":"); 390 if (minutes < 10) { 391 buffer.append("0"); 392 } 393 buffer.append(minutes); 394 buffer.append(":"); 395 if (seconds < 10) { 396 buffer.append("0"); 397 } 398 buffer.append(seconds); 399 if (millis > 0) { 400 if (millis < 10) { 401 buffer.append(".00"); 402 } else if (millis < 100) { 403 buffer.append(".0"); 404 } else { 405 buffer.append("."); 406 } 407 buffer.append(millis); 408 } 409 410 // Get the offset from GMT taking DST into account 411 int gmtOffset = calendar.getTimeZone().getOffset(calendar.getTime().getTime()); 412 if (gmtOffset == 0) { 413 buffer.append('Z'); 414 } else { 415 if (gmtOffset < 0) { 416 buffer.append('-'); 417 gmtOffset *= -1; 418 } else { 419 buffer.append('+'); 420 } 421 int minutesOffset = gmtOffset / (60 * 1000); 422 int hoursOffset = minutesOffset / 60; 423 int partOfHour = minutesOffset % 60; 424 425 if (hoursOffset < 10) { 426 buffer.append('0'); 427 } 428 buffer.append(hoursOffset); 429 buffer.append(':'); 430 if (partOfHour < 10) { 431 buffer.append('0'); 432 } 433 buffer.append(partOfHour); 434 } 435 436 return representScalar(getTag(data.getClass(), Tag.TIMESTAMP), buffer.toString(), 437 DumperOptions.ScalarStyle.PLAIN); 438 } 439 } 440 441 protected class RepresentEnum implements Represent { 442 representData(Object data)443 public Node representData(Object data) { 444 Tag tag = new Tag(data.getClass()); 445 return representScalar(getTag(data.getClass(), tag), ((Enum<?>) data).name()); 446 } 447 } 448 449 protected class RepresentByteArray implements Represent { 450 representData(Object data)451 public Node representData(Object data) { 452 char[] binary = Base64Coder.encode((byte[]) data); 453 return representScalar(Tag.BINARY, String.valueOf(binary), DumperOptions.ScalarStyle.LITERAL); 454 } 455 } 456 getTimeZone()457 public TimeZone getTimeZone() { 458 return timeZone; 459 } 460 setTimeZone(TimeZone timeZone)461 public void setTimeZone(TimeZone timeZone) { 462 this.timeZone = timeZone; 463 } 464 465 protected class RepresentUuid implements Represent { 466 representData(Object data)467 public Node representData(Object data) { 468 return representScalar(getTag(data.getClass(), new Tag(UUID.class)), data.toString()); 469 } 470 } 471 } 472