All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yaml.snakeyaml.representer.SafeRepresenter Maven / Gradle / Ivy

There is a newer version: 2.3
Show newest version
/**
 * Copyright (c) 2008, SnakeYAML
 *
 * 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.math.BigInteger;
import java.nio.charset.StandardCharsets;
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.UUID;
import java.util.regex.Pattern;
import org.yaml.snakeyaml.DumperOptions;
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, Tag> classTags;
  protected TimeZone timeZone = null;
  protected DumperOptions.NonPrintableStyle nonPrintableStyle;

  public SafeRepresenter(DumperOptions options) {
    if (options == null) {
      throw new NullPointerException("DumperOptions must be provided.");
    }
    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(UUID.class, new RepresentUuid());
    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, Tag>();
    this.nonPrintableStyle = options.getNonPrintableStyle();
  }

  protected Tag getTag(Class clazz, Tag defaultTag) {
    if (classTags.containsKey(clazz)) {
      return classTags.get(clazz);
    } else {
      return defaultTag;
    }
  }

  /**
   * Define a tag for the Class to serialize.
   *
   * @param clazz Class which tag is changed
   * @param tag new tag to be used for every instance of the specified Class
   * @return the previous tag associated with the Class
   */
  public Tag addClassTag(Class clazz, Tag tag) {
    if (tag == null) {
      throw new NullPointerException("Tag must be provided.");
    }
    return classTags.put(clazz, tag);
  }

  protected class RepresentNull implements Represent {

    public Node representData(Object data) {
      return representScalar(Tag.NULL, "null");
    }
  }

  private static final Pattern MULTILINE_PATTERN = Pattern.compile("\n|\u0085|\u2028|\u2029");

  protected class RepresentString implements Represent {

    public Node representData(Object data) {
      Tag tag = Tag.STR;
      DumperOptions.ScalarStyle style = null;// not defined
      String value = data.toString();
      if (nonPrintableStyle == DumperOptions.NonPrintableStyle.BINARY
          && !StreamReader.isPrintable(value)) {
        tag = Tag.BINARY;
        char[] binary;
        final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        // sometimes above will just silently fail - it will return incomplete data
        // it happens when String has invalid code points
        // (for example half surrogate character without other half)
        final String checkValue = new String(bytes, StandardCharsets.UTF_8);
        if (!checkValue.equals(value)) {
          throw new YAMLException("invalid string value has occurred");
        }
        binary = Base64Coder.encode(bytes);
        value = String.valueOf(binary);
        style = DumperOptions.ScalarStyle.LITERAL;
      }
      // if no other scalar style is explicitly set, use literal style for
      // multiline scalars
      if (defaultScalarStyle == DumperOptions.ScalarStyle.PLAIN
          && MULTILINE_PATTERN.matcher(value).find()) {
        style = DumperOptions.ScalarStyle.LITERAL;
      }
      return representScalar(tag, value, style);
    }
  }

  protected class RepresentBoolean implements Represent {

    public Node representData(Object data) {
      String value;
      if (Boolean.TRUE.equals(data)) {
        value = "true";
      } else {
        value = "false";
      }
      return representScalar(Tag.BOOL, value);
    }
  }

  protected class RepresentNumber implements Represent {

    public Node representData(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 {

    @SuppressWarnings("unchecked")
    public Node representData(Object data) {
      return representSequence(getTag(data.getClass(), Tag.SEQ), (List) data,
          DumperOptions.FlowStyle.AUTO);
    }
  }

  protected class RepresentIterator implements Represent {

    @SuppressWarnings("unchecked")
    public Node representData(Object data) {
      Iterator iter = (Iterator) data;
      return representSequence(getTag(data.getClass(), Tag.SEQ), new IteratorWrapper(iter),
          DumperOptions.FlowStyle.AUTO);
    }
  }

  private static class IteratorWrapper implements Iterable {

    private final Iterator iter;

    public IteratorWrapper(Iterator iter) {
      this.iter = iter;
    }

    public Iterator iterator() {
      return iter;
    }
  }

  protected class RepresentArray implements Represent {

    public Node representData(Object data) {
      Object[] array = (Object[]) data;
      List list = Arrays.asList(array);
      return representSequence(Tag.SEQ, list, DumperOptions.FlowStyle.AUTO);
    }
  }

  /**
   * Represents primitive arrays, such as short[] and float[], by converting them into equivalent
   * List and List using the appropriate autoboxing type.
   */
  protected class RepresentPrimitiveArray implements Represent {

    public Node representData(Object data) {
      Class type = data.getClass().getComponentType();

      if (byte.class == type) {
        return representSequence(Tag.SEQ, asByteList(data), DumperOptions.FlowStyle.AUTO);
      } else if (short.class == type) {
        return representSequence(Tag.SEQ, asShortList(data), DumperOptions.FlowStyle.AUTO);
      } else if (int.class == type) {
        return representSequence(Tag.SEQ, asIntList(data), DumperOptions.FlowStyle.AUTO);
      } else if (long.class == type) {
        return representSequence(Tag.SEQ, asLongList(data), DumperOptions.FlowStyle.AUTO);
      } else if (float.class == type) {
        return representSequence(Tag.SEQ, asFloatList(data), DumperOptions.FlowStyle.AUTO);
      } else if (double.class == type) {
        return representSequence(Tag.SEQ, asDoubleList(data), DumperOptions.FlowStyle.AUTO);
      } else if (char.class == type) {
        return representSequence(Tag.SEQ, asCharList(data), DumperOptions.FlowStyle.AUTO);
      } else if (boolean.class == type) {
        return representSequence(Tag.SEQ, asBooleanList(data), DumperOptions.FlowStyle.AUTO);
      }

      throw new YAMLException("Unexpected primitive '" + type.getCanonicalName() + "'");
    }

    private List asByteList(Object in) {
      byte[] array = (byte[]) in;
      List list = new ArrayList(array.length);
      for (int i = 0; i < array.length; ++i) {
        list.add(array[i]);
      }
      return list;
    }

    private List asShortList(Object in) {
      short[] array = (short[]) in;
      List list = new ArrayList(array.length);
      for (int i = 0; i < array.length; ++i) {
        list.add(array[i]);
      }
      return list;
    }

    private List asIntList(Object in) {
      int[] array = (int[]) in;
      List list = new ArrayList(array.length);
      for (int i = 0; i < array.length; ++i) {
        list.add(array[i]);
      }
      return list;
    }

    private List asLongList(Object in) {
      long[] array = (long[]) in;
      List list = new ArrayList(array.length);
      for (int i = 0; i < array.length; ++i) {
        list.add(array[i]);
      }
      return list;
    }

    private List asFloatList(Object in) {
      float[] array = (float[]) in;
      List list = new ArrayList(array.length);
      for (int i = 0; i < array.length; ++i) {
        list.add(array[i]);
      }
      return list;
    }

    private List asDoubleList(Object in) {
      double[] array = (double[]) in;
      List list = new ArrayList(array.length);
      for (int i = 0; i < array.length; ++i) {
        list.add(array[i]);
      }
      return list;
    }

    private List asCharList(Object in) {
      char[] array = (char[]) in;
      List list = new ArrayList(array.length);
      for (int i = 0; i < array.length; ++i) {
        list.add(array[i]);
      }
      return list;
    }

    private List asBooleanList(Object in) {
      boolean[] array = (boolean[]) in;
      List list = new ArrayList(array.length);
      for (int i = 0; i < array.length; ++i) {
        list.add(array[i]);
      }
      return list;
    }
  }

  protected class RepresentMap implements Represent {

    @SuppressWarnings("unchecked")
    public Node representData(Object data) {
      return representMapping(getTag(data.getClass(), Tag.MAP), (Map) data,
          DumperOptions.FlowStyle.AUTO);
    }
  }

  protected class RepresentSet implements Represent {

    @SuppressWarnings("unchecked")
    public Node representData(Object data) {
      Map value = new LinkedHashMap();
      Set set = (Set) data;
      for (Object key : set) {
        value.put(key, null);
      }
      return representMapping(getTag(data.getClass(), Tag.SET), value,
          DumperOptions.FlowStyle.AUTO);
    }
  }

  protected class RepresentDate implements Represent {

    public Node representData(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(months);
      buffer.append("-");
      if (days < 10) {
        buffer.append("0");
      }
      buffer.append(days);
      buffer.append("T");
      if (hour24 < 10) {
        buffer.append("0");
      }
      buffer.append(hour24);
      buffer.append(":");
      if (minutes < 10) {
        buffer.append("0");
      }
      buffer.append(minutes);
      buffer.append(":");
      if (seconds < 10) {
        buffer.append("0");
      }
      buffer.append(seconds);
      if (millis > 0) {
        if (millis < 10) {
          buffer.append(".00");
        } else if (millis < 100) {
          buffer.append(".0");
        } else {
          buffer.append(".");
        }
        buffer.append(millis);
      }

      // Get the offset from GMT taking DST into account
      int gmtOffset = calendar.getTimeZone().getOffset(calendar.getTime().getTime());
      if (gmtOffset == 0) {
        buffer.append('Z');
      } else {
        if (gmtOffset < 0) {
          buffer.append('-');
          gmtOffset *= -1;
        } else {
          buffer.append('+');
        }
        int minutesOffset = gmtOffset / (60 * 1000);
        int hoursOffset = minutesOffset / 60;
        int partOfHour = minutesOffset % 60;

        if (hoursOffset < 10) {
          buffer.append('0');
        }
        buffer.append(hoursOffset);
        buffer.append(':');
        if (partOfHour < 10) {
          buffer.append('0');
        }
        buffer.append(partOfHour);
      }

      return representScalar(getTag(data.getClass(), Tag.TIMESTAMP), buffer.toString(),
          DumperOptions.ScalarStyle.PLAIN);
    }
  }

  protected class RepresentEnum implements Represent {

    public Node representData(Object data) {
      Tag tag = new Tag(data.getClass());
      return representScalar(getTag(data.getClass(), tag), ((Enum) data).name());
    }
  }

  protected class RepresentByteArray implements Represent {

    public Node representData(Object data) {
      char[] binary = Base64Coder.encode((byte[]) data);
      return representScalar(Tag.BINARY, String.valueOf(binary), DumperOptions.ScalarStyle.LITERAL);
    }
  }

  public TimeZone getTimeZone() {
    return timeZone;
  }

  public void setTimeZone(TimeZone timeZone) {
    this.timeZone = timeZone;
  }

  protected class RepresentUuid implements Represent {

    public Node representData(Object data) {
      return representScalar(getTag(data.getClass(), new Tag(UUID.class)), data.toString());
    }
  }
}