content: package android.view;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UnsupportedAppUsage;
  
  import java.io.ByteArrayOutputStream;
  import java.io.DataOutputStream;
  import java.io.IOException;
  import java.nio.charset.Charset;
  import java.util.HashMap;
  import java.util.Map;
  
  /**
   * {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out
   * view hierarchies (the view tree, along with the properties for each view) to a stream.
   *
   * It is typically used as follows:
   * <pre>
   *   ViewHierarchyEncoder e = new ViewHierarchyEncoder();
   *
   *   for (View view : views) {
   *      e.beginObject(view);
   *      e.addProperty("prop1", value);
   *      ...
   *      e.endObject();
   *   }
   *
   *   // repeat above snippet for each view, finally end with:
   *   e.endStream();
   * </pre>
   *
   * <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one
   * corresponding to each view) with the property name as the key and the property value
   * as the value.
   *
   * <p>Since the property names are practically the same across all views, rather than using
   * the property name directly as the key, we use a short integer id corresponding to each
   * property name as the key. A final map is added at the end which contains the mapping
   * from the integer to its property name.
   *
   * <p>A value is encoded as a single byte type identifier followed by the encoding of the
   * value. Only primitive types are supported as values, in addition to the Map type.
   *
   * @hide
   */
  public class ViewHierarchyEncoder {
      // Prefixes for simple primitives. These match the JNI definitions.
      private static final byte SIG_BOOLEAN = 'Z';
      private static final byte SIG_BYTE = 'B';
      private static final byte SIG_SHORT = 'S';
      private static final byte SIG_INT = 'I';
      private static final byte SIG_LONG = 'J';
      private static final byte SIG_FLOAT = 'F';
      private static final byte SIG_DOUBLE = 'D';
  
      // Prefixes for some commonly used objects
      private static final byte SIG_STRING = 'R';
  
      private static final byte SIG_MAP = 'M'; // a map with an short key
      private static final short SIG_END_MAP = 0;
  
      private final DataOutputStream mStream;
  
      private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200);
      private short mPropertyId = 1;
      private Charset mCharset = Charset.forName("utf-8");
  
      public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
          mStream = new DataOutputStream(stream);
      }
  
      public void beginObject(@NonNull Object o) {
          startPropertyMap();
          addProperty("meta:__name__", o.getClass().getName());
          addProperty("meta:__hash__", o.hashCode());
      }
  
      public void endObject() {
          endPropertyMap();
      }
  
      public void endStream() {
          // write out the string table
          startPropertyMap();
          addProperty("__name__", "propertyIndex");
          for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) {
              writeShort(entry.getValue());
              writeString(entry.getKey());
          }
          endPropertyMap();
      }
  
      @UnsupportedAppUsage
      public void addProperty(@NonNull String name, boolean v) {
          writeShort(createPropertyIndex(name));
          writeBoolean(v);
      }
  
      public void addProperty(@NonNull String name, short s) {
          writeShort(createPropertyIndex(name));
          writeShort(s);
      }
  
      @UnsupportedAppUsage
      public void addProperty(@NonNull String name, int v) {
          writeShort(createPropertyIndex(name));
          writeInt(v);
      }
  
      @UnsupportedAppUsage
      public void addProperty(@NonNull String name, float v) {
          writeShort(createPropertyIndex(name));
          writeFloat(v);
      }
  
      @UnsupportedAppUsage
      public void addProperty(@NonNull String name, @Nullable String s) {
          writeShort(createPropertyIndex(name));
          writeString(s);
      }
  
      /**
       * Writes the given name as the property name, and leaves it to the callee
       * to fill in value for this property.
       */
      public void addPropertyKey(@NonNull String name) {
          writeShort(createPropertyIndex(name));
      }
  
      private short createPropertyIndex(@NonNull String name) {
          Short index = mPropertyNames.get(name);
          if (index == null) {
              index = mPropertyId++;
              mPropertyNames.put(name, index);
          }
  
          return index;
      }
  
      private void startPropertyMap() {
          try {
              mStream.write(SIG_MAP);
          } catch (IOException e) {
              // does not happen since the stream simply wraps a ByteArrayOutputStream
          }
      }
  
      private void endPropertyMap() {
          writeShort(SIG_END_MAP);
      }
  
      private void writeBoolean(boolean v) {
          try {
              mStream.write(SIG_BOOLEAN);
              mStream.write(v ? 1 : 0);
          } catch (IOException e) {
              // does not happen since the stream simply wraps a ByteArrayOutputStream
          }
      }
  
      private void writeShort(short s) {
          try {
              mStream.write(SIG_SHORT);
              mStream.writeShort(s);
          } catch (IOException e) {
              // does not happen since the stream simply wraps a ByteArrayOutputStream
          }
      }
  
      private void writeInt(int i) {
          try {
              mStream.write(SIG_INT);
              mStream.writeInt(i);
          } catch (IOException e) {
              // does not happen since the stream simply wraps a ByteArrayOutputStream
          }
      }
  
      private void writeFloat(float v) {
          try {
              mStream.write(SIG_FLOAT);
              mStream.writeFloat(v);
          } catch (IOException e) {
              // does not happen since the stream simply wraps a ByteArrayOutputStream
          }
      }
  
      private void writeString(@Nullable String s) {
          if (s == null) {
              s = "";
          }
  
          try {
              mStream.write(SIG_STRING);
              byte[] bytes = s.getBytes(mCharset);
  
              short len = (short)Math.min(bytes.length, Short.MAX_VALUE);
              mStream.writeShort(len);
  
              mStream.write(bytes, 0, len);
          } catch (IOException e) {
              // does not happen since the stream simply wraps a ByteArrayOutputStream
          }
      }
  }
  