• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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