View Javadoc

1   /**
2    * Copyright (c) 2008-2012, 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.Arrays;
21  import java.util.Calendar;
22  import java.util.Date;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.TimeZone;
30  import java.util.regex.Pattern;
31  
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  
46      public SafeRepresenter() {
47          this.nullRepresenter = new RepresentNull();
48          this.representers.put(String.class, new RepresentString());
49          this.representers.put(Boolean.class, new RepresentBoolean());
50          this.representers.put(Character.class, new RepresentString());
51          this.representers.put(byte[].class, new RepresentByteArray());
52          this.multiRepresenters.put(Number.class, new RepresentNumber());
53          this.multiRepresenters.put(List.class, new RepresentList());
54          this.multiRepresenters.put(Map.class, new RepresentMap());
55          this.multiRepresenters.put(Set.class, new RepresentSet());
56          this.multiRepresenters.put(Iterator.class, new RepresentIterator());
57          this.multiRepresenters.put(new Object[0].getClass(), new RepresentArray());
58          this.multiRepresenters.put(Date.class, new RepresentDate());
59          this.multiRepresenters.put(Enum.class, new RepresentEnum());
60          this.multiRepresenters.put(Calendar.class, new RepresentDate());
61          classTags = new HashMap<Class<? extends Object>, Tag>();
62      }
63  
64      protected Tag getTag(Class<?> clazz, Tag defaultTag) {
65          if (classTags.containsKey(clazz)) {
66              return classTags.get(clazz);
67          } else {
68              return defaultTag;
69          }
70      }
71  
72      /**
73       * Define a tag for the <code>Class</code> to serialize
74       * 
75       * @deprecated use Tag instead of String
76       * @param clazz
77       *            <code>Class</code> which tag is changed
78       * @param tag
79       *            new tag to be used for every instance of the specified
80       *            <code>Class</code>
81       * @return the previous tag associated with the <code>Class</code>
82       */
83      public Tag addClassTag(Class<? extends Object> clazz, String tag) {
84          return addClassTag(clazz, new Tag(tag));
85      }
86  
87      /**
88       * Define a tag for the <code>Class</code> to serialize.
89       * 
90       * @param clazz
91       *            <code>Class</code> which tag is changed
92       * @param tag
93       *            new tag to be used for every instance of the specified
94       *            <code>Class</code>
95       * @return the previous tag associated with the <code>Class</code>
96       */
97      public Tag addClassTag(Class<? extends Object> clazz, Tag tag) {
98          if (tag == null) {
99              throw new NullPointerException("Tag must be provided.");
100         }
101         return classTags.put(clazz, tag);
102     }
103 
104     protected class RepresentNull implements Represent {
105         public Node representData(Object data) {
106             return representScalar(Tag.NULL, "null");
107         }
108     }
109 
110     public static Pattern MULTILINE_PATTERN = Pattern.compile("\n|\u0085|\u2028|\u2029");
111 
112     protected class RepresentString implements Represent {
113         public Node representData(Object data) {
114             Tag tag = Tag.STR;
115             Character style = null;
116             String value = data.toString();
117             if (StreamReader.NON_PRINTABLE.matcher(value).find()) {
118                 tag = Tag.BINARY;
119                 char[] binary;
120                 try {
121                     binary = Base64Coder.encode(value.getBytes("UTF-8"));
122                 } catch (UnsupportedEncodingException e) {
123                     throw new YAMLException(e);
124                 }
125                 value = String.valueOf(binary);
126                 style = '|';
127             }
128             // if no other scalar style is explicitly set, use literal style for
129             // multiline scalars
130             if (defaultScalarStyle == null && MULTILINE_PATTERN.matcher(value).find()) {
131                 style = '|';
132             }
133             return representScalar(tag, value, style);
134         }
135     }
136 
137     protected class RepresentBoolean implements Represent {
138         public Node representData(Object data) {
139             String value;
140             if (Boolean.TRUE.equals(data)) {
141                 value = "true";
142             } else {
143                 value = "false";
144             }
145             return representScalar(Tag.BOOL, value);
146         }
147     }
148 
149     protected class RepresentNumber implements Represent {
150         public Node representData(Object data) {
151             Tag tag;
152             String value;
153             if (data instanceof Byte || data instanceof Short || data instanceof Integer
154                     || data instanceof Long || data instanceof BigInteger) {
155                 tag = Tag.INT;
156                 value = data.toString();
157             } else {
158                 Number number = (Number) data;
159                 tag = Tag.FLOAT;
160                 if (number.equals(Double.NaN)) {
161                     value = ".NaN";
162                 } else if (number.equals(Double.POSITIVE_INFINITY)) {
163                     value = ".inf";
164                 } else if (number.equals(Double.NEGATIVE_INFINITY)) {
165                     value = "-.inf";
166                 } else {
167                     value = number.toString();
168                 }
169             }
170             return representScalar(getTag(data.getClass(), tag), value);
171         }
172     }
173 
174     protected class RepresentList implements Represent {
175         @SuppressWarnings("unchecked")
176         public Node representData(Object data) {
177             return representSequence(getTag(data.getClass(), Tag.SEQ), (List<Object>) data, null);
178         }
179     }
180 
181     protected class RepresentIterator implements Represent {
182         @SuppressWarnings("unchecked")
183         public Node representData(Object data) {
184             Iterator<Object> iter = (Iterator<Object>) data;
185             return representSequence(getTag(data.getClass(), Tag.SEQ), new IteratorWrapper(iter),
186                     null);
187         }
188     }
189 
190     private static class IteratorWrapper implements Iterable<Object> {
191         private Iterator<Object> iter;
192 
193         public IteratorWrapper(Iterator<Object> iter) {
194             this.iter = iter;
195         }
196 
197         public Iterator<Object> iterator() {
198             return iter;
199         }
200     }
201 
202     protected class RepresentArray implements Represent {
203         public Node representData(Object data) {
204             Object[] array = (Object[]) data;
205             List<Object> list = Arrays.asList(array);
206             return representSequence(Tag.SEQ, list, null);
207         }
208     }
209 
210     protected class RepresentMap implements Represent {
211         @SuppressWarnings("unchecked")
212         public Node representData(Object data) {
213             return representMapping(getTag(data.getClass(), Tag.MAP), (Map<Object, Object>) data,
214                     null);
215         }
216     }
217 
218     protected class RepresentSet implements Represent {
219         @SuppressWarnings("unchecked")
220         public Node representData(Object data) {
221             Map<Object, Object> value = new LinkedHashMap<Object, Object>();
222             Set<Object> set = (Set<Object>) data;
223             for (Object key : set) {
224                 value.put(key, null);
225             }
226             return representMapping(getTag(data.getClass(), Tag.SET), value, null);
227         }
228     }
229 
230     protected class RepresentDate implements Represent {
231         public Node representData(Object data) {
232             // because SimpleDateFormat ignores timezone we have to use Calendar
233             Calendar calendar;
234             if (data instanceof Calendar) {
235                 calendar = (Calendar) data;
236             } else {
237                 calendar = Calendar.getInstance(getTimeZone() == null ? TimeZone.getTimeZone("UTC")
238                         : timeZone);
239                 calendar.setTime((Date) data);
240             }
241             int years = calendar.get(Calendar.YEAR);
242             int months = calendar.get(Calendar.MONTH) + 1; // 0..12
243             int days = calendar.get(Calendar.DAY_OF_MONTH); // 1..31
244             int hour24 = calendar.get(Calendar.HOUR_OF_DAY); // 0..24
245             int minutes = calendar.get(Calendar.MINUTE); // 0..59
246             int seconds = calendar.get(Calendar.SECOND); // 0..59
247             int millis = calendar.get(Calendar.MILLISECOND);
248             StringBuilder buffer = new StringBuilder(String.valueOf(years));
249             while (buffer.length() < 4) {
250                 // ancient years
251                 buffer.insert(0, "0");
252             }
253             buffer.append("-");
254             if (months < 10) {
255                 buffer.append("0");
256             }
257             buffer.append(String.valueOf(months));
258             buffer.append("-");
259             if (days < 10) {
260                 buffer.append("0");
261             }
262             buffer.append(String.valueOf(days));
263             buffer.append("T");
264             if (hour24 < 10) {
265                 buffer.append("0");
266             }
267             buffer.append(String.valueOf(hour24));
268             buffer.append(":");
269             if (minutes < 10) {
270                 buffer.append("0");
271             }
272             buffer.append(String.valueOf(minutes));
273             buffer.append(":");
274             if (seconds < 10) {
275                 buffer.append("0");
276             }
277             buffer.append(String.valueOf(seconds));
278             if (millis > 0) {
279                 if (millis < 10) {
280                     buffer.append(".00");
281                 } else if (millis < 100) {
282                     buffer.append(".0");
283                 } else {
284                     buffer.append(".");
285                 }
286                 buffer.append(String.valueOf(millis));
287             }
288             if (TimeZone.getTimeZone("UTC").equals(calendar.getTimeZone())) {
289                 buffer.append("Z");
290             } else {
291                 // Get the Offset from GMT taking DST into account
292                 int gmtOffset = calendar.getTimeZone().getOffset(calendar.get(Calendar.ERA),
293                         calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
294                         calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.DAY_OF_WEEK),
295                         calendar.get(Calendar.MILLISECOND));
296                 int minutesOffset = gmtOffset / (60 * 1000);
297                 int hoursOffset = minutesOffset / 60;
298                 int partOfHour = minutesOffset % 60;
299                 buffer.append((hoursOffset > 0 ? "+" : "") + hoursOffset + ":"
300                         + (partOfHour < 10 ? "0" + partOfHour : partOfHour));
301             }
302             return representScalar(getTag(data.getClass(), Tag.TIMESTAMP), buffer.toString(), null);
303         }
304     }
305 
306     protected class RepresentEnum implements Represent {
307         public Node representData(Object data) {
308             Tag tag = new Tag(data.getClass());
309             return representScalar(getTag(data.getClass(), tag), ((Enum<?>) data).name());
310         }
311     }
312 
313     protected class RepresentByteArray implements Represent {
314         public Node representData(Object data) {
315             char[] binary = Base64Coder.encode((byte[]) data);
316             return representScalar(Tag.BINARY, String.valueOf(binary), '|');
317         }
318     }
319 
320     public TimeZone getTimeZone() {
321         return timeZone;
322     }
323 
324     public void setTimeZone(TimeZone timeZone) {
325         this.timeZone = timeZone;
326     }
327 }