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  */
16 package org.yaml.snakeyaml.representer;
17 
18 import java.io.UnsupportedEncodingException;
19 import java.math.BigInteger;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Calendar;
23 import java.util.Date;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TimeZone;
31 import java.util.UUID;
32 import java.util.regex.Pattern;
33 
34 import org.yaml.snakeyaml.error.YAMLException;
35 import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
36 import org.yaml.snakeyaml.nodes.Node;
37 import org.yaml.snakeyaml.nodes.Tag;
38 import org.yaml.snakeyaml.reader.StreamReader;
39 
40 /**
41  * Represent standard Java classes
42  */
43 class SafeRepresenter extends BaseRepresenter {
44 
45     protected Map<Class<? extends Object>, Tag> classTags;
46     protected TimeZone timeZone = null;
47 
SafeRepresenter()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 
getTag(Class<?> clazz, Tag defaultTag)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      */
addClassTag(Class<? extends Object> clazz, Tag tag)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 {
representData(Object data)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 {
representData(Object data)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 {
representData(Object data)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 {
representData(Object data)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")
representData(Object data)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")
representData(Object data)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 
IteratorWrapper(Iterator<Object> iter)191         public IteratorWrapper(Iterator<Object> iter) {
192             this.iter = iter;
193         }
194 
iterator()195         public Iterator<Object> iterator() {
196             return iter;
197         }
198     }
199 
200     protected class RepresentArray implements Represent {
representData(Object data)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 {
representData(Object data)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 
asByteList(Object in)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 
asShortList(Object in)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 
asIntList(Object in)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 
asLongList(Object in)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 
asFloatList(Object in)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 
asDoubleList(Object in)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 
asCharList(Object in)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 
asBooleanList(Object in)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")
representData(Object data)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")
representData(Object data)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 {
representData(Object data)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