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