Home | History | Annotate | Download | only in representer
      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 
     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