/** * Copyright (c) 2008-2013, http://www.snakeyaml.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.yaml.snakeyaml.representer; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.regex.Pattern; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.reader.StreamReader; /** * Represent standard Java classes */ class SafeRepresenter extends BaseRepresenter { protected Map<Class<? extends Object>, Tag> classTags; protected TimeZone timeZone = null; public SafeRepresenter() { this.nullRepresenter = new RepresentNull(); this.representers.put(String.class, new RepresentString()); this.representers.put(Boolean.class, new RepresentBoolean()); this.representers.put(Character.class, new RepresentString()); this.representers.put(byte[].class, new RepresentByteArray()); Represent primitiveArray = new RepresentPrimitiveArray(); representers.put(short[].class, primitiveArray); representers.put(int[].class, primitiveArray); representers.put(long[].class, primitiveArray); representers.put(float[].class, primitiveArray); representers.put(double[].class, primitiveArray); representers.put(char[].class, primitiveArray); representers.put(boolean[].class, primitiveArray); this.multiRepresenters.put(Number.class, new RepresentNumber()); this.multiRepresenters.put(List.class, new RepresentList()); this.multiRepresenters.put(Map.class, new RepresentMap()); this.multiRepresenters.put(Set.class, new RepresentSet()); this.multiRepresenters.put(Iterator.class, new RepresentIterator()); this.multiRepresenters.put(new Object[0].getClass(), new RepresentArray()); this.multiRepresenters.put(Date.class, new RepresentDate()); this.multiRepresenters.put(Enum.class, new RepresentEnum()); this.multiRepresenters.put(Calendar.class, new RepresentDate()); classTags = new HashMap<Class<? extends Object>, Tag>(); } protected Tag getTag(final Class<?> clazz, final Tag defaultTag) { if (classTags.containsKey(clazz)) { return classTags.get(clazz); } else { return defaultTag; } } /** * Define a tag for the <code>Class</code> to serialize * * @deprecated use Tag instead of String * @param clazz * <code>Class</code> which tag is changed * @param tag * new tag to be used for every instance of the specified <code>Class</code> * @return the previous tag associated with the <code>Class</code> */ @Deprecated public Tag addClassTag(final Class<? extends Object> clazz, final String tag) { return addClassTag(clazz, new Tag(tag)); } /** * Define a tag for the <code>Class</code> to serialize. * * @param clazz * <code>Class</code> which tag is changed * @param tag * new tag to be used for every instance of the specified <code>Class</code> * @return the previous tag associated with the <code>Class</code> */ public Tag addClassTag(final Class<? extends Object> clazz, final Tag tag) { if (tag == null) { throw new NullPointerException("Tag must be provided."); } return classTags.put(clazz, tag); } protected class RepresentNull implements Represent { @Override public Node representData(final Object data) { return representScalar(Tag.NULL, "null"); } } public static Pattern MULTILINE_PATTERN = Pattern.compile("\n|\u0085|\u2028|\u2029"); protected class RepresentString implements Represent { @Override public Node representData(final Object data) { Tag tag = Tag.STR; Character style = null; String value = data.toString(); if (StreamReader.NON_PRINTABLE.matcher(value).find()) { tag = Tag.BINARY; char[] binary; try { binary = Base64Coder.encode(value.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new YAMLException(e); } value = String.valueOf(binary); style = '|'; } // if no other scalar style is explicitly set, use literal style for // multiline scalars if (defaultScalarStyle == null && MULTILINE_PATTERN.matcher(value).find()) { style = '|'; } return representScalar(tag, value, style); } } protected class RepresentBoolean implements Represent { @Override public Node representData(final Object data) { String value; if (Boolean.TRUE.equals(data)) { value = "true"; } else { value = "false"; } return representScalar(Tag.BOOL, value); } } protected class RepresentNumber implements Represent { @Override public Node representData(final Object data) { Tag tag; String value; if (data instanceof Byte || data instanceof Short || data instanceof Integer || data instanceof Long || data instanceof BigInteger) { tag = Tag.INT; value = data.toString(); } else { Number number = (Number) data; tag = Tag.FLOAT; if (number.equals(Double.NaN)) { value = ".NaN"; } else if (number.equals(Double.POSITIVE_INFINITY)) { value = ".inf"; } else if (number.equals(Double.NEGATIVE_INFINITY)) { value = "-.inf"; } else { value = number.toString(); } } return representScalar(getTag(data.getClass(), tag), value); } } protected class RepresentList implements Represent { @Override @SuppressWarnings("unchecked") public Node representData(final Object data) { return representSequence(getTag(data.getClass(), Tag.SEQ), (List<Object>) data, null); } } protected class RepresentIterator implements Represent { @Override @SuppressWarnings("unchecked") public Node representData(final Object data) { Iterator<Object> iter = (Iterator<Object>) data; return representSequence(getTag(data.getClass(), Tag.SEQ), new IteratorWrapper(iter), null); } } private static class IteratorWrapper implements Iterable<Object> { private Iterator<Object> iter; public IteratorWrapper(final Iterator<Object> iter) { this.iter = iter; } @Override public Iterator<Object> iterator() { return iter; } } protected class RepresentArray implements Represent { @Override public Node representData(final Object data) { Object[] array = (Object[]) data; List<Object> list = Arrays.asList(array); return representSequence(Tag.SEQ, list, null); } } /** * Represents primitive arrays, such as short[] and float[], by converting them into equivalent List<Short> and List<Float> using the * appropriate autoboxing type. */ protected class RepresentPrimitiveArray implements Represent { @Override public Node representData(final Object data) { Class<?> type = data.getClass().getComponentType(); if (byte.class == type) { return representSequence(Tag.SEQ, asByteList(data), null); } else if (short.class == type) { return representSequence(Tag.SEQ, asShortList(data), null); } else if (int.class == type) { return representSequence(Tag.SEQ, asIntList(data), null); } else if (long.class == type) { return representSequence(Tag.SEQ, asLongList(data), null); } else if (float.class == type) { return representSequence(Tag.SEQ, asFloatList(data), null); } else if (double.class == type) { return representSequence(Tag.SEQ, asDoubleList(data), null); } else if (char.class == type) { return representSequence(Tag.SEQ, asCharList(data), null); } else if (boolean.class == type) { return representSequence(Tag.SEQ, asBooleanList(data), null); } throw new YAMLException("Unexpected primitive '" + type.getCanonicalName() + "'"); } private List<Byte> asByteList(final Object in) { byte[] array = (byte[]) in; List<Byte> list = new ArrayList<Byte>(array.length); for (int i = 0; i < array.length; ++i) list.add(array[i]); return list; } private List<Short> asShortList(final Object in) { short[] array = (short[]) in; List<Short> list = new ArrayList<Short>(array.length); for (int i = 0; i < array.length; ++i) list.add(array[i]); return list; } private List<Integer> asIntList(final Object in) { int[] array = (int[]) in; List<Integer> list = new ArrayList<Integer>(array.length); for (int i = 0; i < array.length; ++i) list.add(array[i]); return list; } private List<Long> asLongList(final Object in) { long[] array = (long[]) in; List<Long> list = new ArrayList<Long>(array.length); for (int i = 0; i < array.length; ++i) list.add(array[i]); return list; } private List<Float> asFloatList(final Object in) { float[] array = (float[]) in; List<Float> list = new ArrayList<Float>(array.length); for (int i = 0; i < array.length; ++i) list.add(array[i]); return list; } private List<Double> asDoubleList(final Object in) { double[] array = (double[]) in; List<Double> list = new ArrayList<Double>(array.length); for (int i = 0; i < array.length; ++i) list.add(array[i]); return list; } private List<Character> asCharList(final Object in) { char[] array = (char[]) in; List<Character> list = new ArrayList<Character>(array.length); for (int i = 0; i < array.length; ++i) list.add(array[i]); return list; } private List<Boolean> asBooleanList(final Object in) { boolean[] array = (boolean[]) in; List<Boolean> list = new ArrayList<Boolean>(array.length); for (int i = 0; i < array.length; ++i) list.add(array[i]); return list; } } protected class RepresentMap implements Represent { @Override @SuppressWarnings("unchecked") public Node representData(final Object data) { return representMapping(getTag(data.getClass(), Tag.MAP), (Map<Object, Object>) data, null); } } protected class RepresentSet implements Represent { @Override @SuppressWarnings("unchecked") public Node representData(final Object data) { Map<Object, Object> value = new LinkedHashMap<Object, Object>(); Set<Object> set = (Set<Object>) data; for (Object key : set) { value.put(key, null); } return representMapping(getTag(data.getClass(), Tag.SET), value, null); } } protected class RepresentDate implements Represent { @Override public Node representData(final Object data) { // because SimpleDateFormat ignores timezone we have to use Calendar Calendar calendar; if (data instanceof Calendar) { calendar = (Calendar) data; } else { calendar = Calendar.getInstance(getTimeZone() == null ? TimeZone.getTimeZone("UTC") : timeZone); calendar.setTime((Date) data); } int years = calendar.get(Calendar.YEAR); int months = calendar.get(Calendar.MONTH) + 1; // 0..12 int days = calendar.get(Calendar.DAY_OF_MONTH); // 1..31 int hour24 = calendar.get(Calendar.HOUR_OF_DAY); // 0..24 int minutes = calendar.get(Calendar.MINUTE); // 0..59 int seconds = calendar.get(Calendar.SECOND); // 0..59 int millis = calendar.get(Calendar.MILLISECOND); StringBuilder buffer = new StringBuilder(String.valueOf(years)); while (buffer.length() < 4) { // ancient years buffer.insert(0, "0"); } buffer.append("-"); if (months < 10) { buffer.append("0"); } buffer.append(String.valueOf(months)); buffer.append("-"); if (days < 10) { buffer.append("0"); } buffer.append(String.valueOf(days)); buffer.append("T"); if (hour24 < 10) { buffer.append("0"); } buffer.append(String.valueOf(hour24)); buffer.append(":"); if (minutes < 10) { buffer.append("0"); } buffer.append(String.valueOf(minutes)); buffer.append(":"); if (seconds < 10) { buffer.append("0"); } buffer.append(String.valueOf(seconds)); if (millis > 0) { if (millis < 10) { buffer.append(".00"); } else if (millis < 100) { buffer.append(".0"); } else { buffer.append("."); } buffer.append(String.valueOf(millis)); } if (TimeZone.getTimeZone("UTC").equals(calendar.getTimeZone())) { buffer.append("Z"); } else { // Get the Offset from GMT taking DST into account int gmtOffset = calendar.getTimeZone().getOffset(calendar.get(Calendar.ERA), calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.DAY_OF_WEEK), calendar.get(Calendar.MILLISECOND)); int minutesOffset = gmtOffset / (60 * 1000); int hoursOffset = minutesOffset / 60; int partOfHour = minutesOffset % 60; buffer.append((hoursOffset > 0 ? "+" : "") + hoursOffset + ":" + (partOfHour < 10 ? "0" + partOfHour : partOfHour)); } return representScalar(getTag(data.getClass(), Tag.TIMESTAMP), buffer.toString(), null); } } protected class RepresentEnum implements Represent { @Override public Node representData(final Object data) { Tag tag = new Tag(data.getClass()); return representScalar(getTag(data.getClass(), tag), ((Enum<?>) data).name()); } } protected class RepresentByteArray implements Represent { @Override public Node representData(final Object data) { char[] binary = Base64Coder.encode((byte[]) data); return representScalar(Tag.BINARY, String.valueOf(binary), '|'); } } public TimeZone getTimeZone() { return timeZone; } public void setTimeZone(final TimeZone timeZone) { this.timeZone = timeZone; } }