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