/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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.apache.geode.internal;

import java.io.BufferedReader;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.NotSerializableException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UTFDataFormatException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URL;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.Logger;

import org.apache.geode.CancelException;
import org.apache.geode.CanonicalInstantiator;
import org.apache.geode.DataSerializable;
import org.apache.geode.DataSerializer;
import org.apache.geode.GemFireConfigException;
import org.apache.geode.GemFireIOException;
import org.apache.geode.GemFireRethrowable;
import org.apache.geode.Instantiator;
import org.apache.geode.InternalGemFireError;
import org.apache.geode.SerializationException;
import org.apache.geode.SystemFailure;
import org.apache.geode.ToDataException;
import org.apache.geode.cache.CacheClosedException;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.distributed.internal.ClusterDistributionManager;
import org.apache.geode.distributed.internal.DMStats;
import org.apache.geode.distributed.internal.DistributedSystemService;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.distributed.internal.LonerDistributionManager;
import org.apache.geode.distributed.internal.SerialDistributionMessage;
import org.apache.geode.i18n.StringId;
import org.apache.geode.internal.cache.EnumListenerEvent;
import org.apache.geode.internal.cache.EventID;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.PoolManagerImpl;
import org.apache.geode.internal.cache.tier.sockets.CacheClientNotifier;
import org.apache.geode.internal.cache.tier.sockets.CacheServerHelper;
import org.apache.geode.internal.cache.tier.sockets.ClientDataSerializerMessage;
import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID;
import org.apache.geode.internal.cache.tier.sockets.OldClientSupportService;
import org.apache.geode.internal.cache.tier.sockets.Part;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.lang.ClassUtils;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.logging.log4j.LocalizedMessage;
import org.apache.geode.internal.logging.log4j.LogMarker;
import org.apache.geode.internal.util.concurrent.CopyOnWriteHashMap;
import org.apache.geode.pdx.NonPortableClassException;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.pdx.PdxSerializable;
import org.apache.geode.pdx.PdxSerializer;
import org.apache.geode.pdx.internal.AutoSerializableManager;
import org.apache.geode.pdx.internal.AutoSerializableManager.AutoClassInfo;
import org.apache.geode.pdx.internal.EnumInfo;
import org.apache.geode.pdx.internal.PdxInputStream;
import org.apache.geode.pdx.internal.PdxInstanceEnum;
import org.apache.geode.pdx.internal.PdxInstanceImpl;
import org.apache.geode.pdx.internal.PdxOutputStream;
import org.apache.geode.pdx.internal.PdxReaderImpl;
import org.apache.geode.pdx.internal.PdxType;
import org.apache.geode.pdx.internal.PdxWriterImpl;
import org.apache.geode.pdx.internal.TypeRegistry;

/**
 * Contains static methods for data serializing instances of internal GemFire classes. It also
 * contains the implementation of the distribution messaging (and shared memory management) needed
 * to support data serialization.
 *
 * @since GemFire 3.5
 */
public abstract class InternalDataSerializer extends DataSerializer implements DSCODE {
  private static final Logger logger = LogService.getLogger();

  /**
   * Maps Class names to their DataSerializer. This is used to find a DataSerializer during
   * serialization.
   */
  private static final Map<String, DataSerializer> classesToSerializers = new ConcurrentHashMap<>();


  /**
   * This list contains classes that Geode's classes subclass, such as antlr AST classes which
   * are used by our Object Query Language. It also contains certain
   * classes that are DataSerializable but end up being serialized as part of other serializable
   * objects. VersionedObjectList, for instance, is serialized as part of a
   * partial putAll exception object.
   * <p>
   * Do not java-serialize objects that Geode does not have complete control over. This
   * leaves us open to security attacks such as Gadget Chains and compromises the ability
   * to do a rolling upgrade from one version of Geode to the next.
   * <p>
   * In general you shouldn't use java serialization and you should implement
   * DataSerializableFixedID
   * for internal Geode objects. This gives you better control over backward-compatibility.
   * <p>
   * Do not add to this list unless absolutely necessary. Instead put your classes either
   * in the sanctionedSerializables file for your module or in its excludedClasses file.
   * Run AnalyzeSerializables to generate the content for the file.
   * <p>
   */
  private static final String SANCTIONED_SERIALIZABLES_DEPENDENCIES_PATTERN =
      "java.**;javax.management.**" + ";javax.print.attribute.EnumSyntax" // used for some old enums
          + ";antlr.**" // query AST objects

          // old Admin API
          + ";org.apache.commons.modeler.AttributeInfo" + ";org.apache.commons.modeler.FeatureInfo"
          + ";org.apache.commons.modeler.ManagedBean"
          + ";org.apache.geode.distributed.internal.DistributionConfigSnapshot"
          + ";org.apache.geode.distributed.internal.RuntimeDistributionConfigImpl"
          + ";org.apache.geode.distributed.internal.DistributionConfigImpl"

          // WindowedExportFunction, RegionSnapshotService
          + ";org.apache.geode.distributed.internal.membership.InternalDistributedMember"
          // putAll
          + ";org.apache.geode.internal.cache.persistence.PersistentMemberID" // putAll
          + ";org.apache.geode.internal.cache.persistence.DiskStoreID" // putAll
          + ";org.apache.geode.internal.cache.tier.sockets.VersionedObjectList" // putAll

          // security services
          + ";org.apache.shiro.*;org.apache.shiro.authz.*;org.apache.shiro.authc.*"

          // export logs
          + ";org.apache.logging.log4j.Level" + ";org.apache.logging.log4j.spi.StandardLevel"

          // jar deployment
          + ";com.sun.proxy.$Proxy*" + ";com.healthmarketscience.rmiio.RemoteInputStream"

          + ";org.apache.geode.connectors.jdbc.internal.**" // TODO - remove this! See GEODE-4752

          // geode-modules
          + ";org.apache.geode.modules.util.SessionCustomExpiry" + ";";


  private static InputStreamFilter defaultSerializationFilter = new EmptyInputStreamFilter();

  /**
   * A deserialization filter for ObjectInputStreams
   */
  private static InputStreamFilter serializationFilter = defaultSerializationFilter;

  private static final String serializationVersionTxt =
      System.getProperty(DistributionConfig.GEMFIRE_PREFIX + "serializationVersion");

  /**
   * support for old GemFire clients and WAN sites - needed to enable moving from GemFire to Geode
   */
  private static OldClientSupportService oldClientSupportService;

  /**
   * For backward compatibility we must swizzle the package of some classes that had to be moved
   * when GemFire was open- sourced. This preserves backward-compatibility.
   *
   * @param name the fully qualified class name
   * @return the name of the class in this implementation
   */
  public static String processIncomingClassName(String name) {
    // TCPServer classes are used before a cache exists and support for old clients has been
    // initialized
    String oldPackage = "com.gemstone.org.jgroups.stack.tcpserver";
    String newPackage = "org.apache.geode.distributed.internal.tcpserver";
    if (name.startsWith(oldPackage)) {
      return newPackage + name.substring(oldPackage.length());
    }
    OldClientSupportService svc = getOldClientSupportService();
    if (svc != null) {
      return svc.processIncomingClassName(name);
    }
    return name;
  }

  /**
   * For backward compatibility we must swizzle the package of some classes that had to be moved
   * when GemFire was open- sourced. This preserves backward-compatibility.
   *
   * @param name the fully qualified class name
   * @param out the consumer of the serialized object
   * @return the name of the class in this implementation
   */
  public static String processOutgoingClassName(String name, DataOutput out) {
    // TCPServer classes are used before a cache exists and support for old clients has been
    // initialized
    String oldPackage = "com.gemstone.org.jgroups.stack.tcpserver";
    String newPackage = "org.apache.geode.distributed.internal.tcpserver";
    if (name.startsWith(newPackage)) {
      return oldPackage + name.substring(newPackage.length());
    }
    OldClientSupportService svc = getOldClientSupportService();
    if (svc != null) {
      return svc.processOutgoingClassName(name, out);
    }
    return name;
  }

  /**
   * Initializes the optional serialization "white list" if the user has requested it in the
   * DistributionConfig
   *
   * @param distributionConfig the DistributedSystem configuration
   * @param services DistributedSystem services that might have classes to white-list
   */
  public static void initialize(DistributionConfig distributionConfig,
      Collection<DistributedSystemService> services) {
    logger.info("initializing InternalDataSerializer with {} services", services.size());
    if (distributionConfig.getValidateSerializableObjects()) {
      if (!ClassUtils.isClassAvailable("sun.misc.ObjectInputFilter")
          && !ClassUtils.isClassAvailable("java.io.ObjectInputFilter")) {
        throw new GemFireConfigException(
            "A serialization filter has been specified but this version of Java does not support serialization filters - ObjectInputFilter is not available");
      }
      serializationFilter =
          new ObjectInputStreamFilterWrapper(SANCTIONED_SERIALIZABLES_DEPENDENCIES_PATTERN
              + distributionConfig.getSerializableObjectFilter() + ";!*", services);
    } else {
      clearSerializationFilter();
    }
  }

  private static void clearSerializationFilter() {
    serializationFilter = defaultSerializationFilter;
  }


  /**
   * {@link DistributedSystemService}s that need to whitelist Serializable objects can use this to
   * read them from a file and then return them via
   * {@link DistributedSystemService#getSerializationWhitelist}
   */
  public static Collection<String> loadClassNames(URL sanctionedSerializables) throws IOException {
    Collection<String> result = new ArrayList(1000);
    InputStream inputStream = sanctionedSerializables.openStream();
    InputStreamReader reader = new InputStreamReader(inputStream);
    BufferedReader in = new BufferedReader(reader);
    try {
      String line;
      while ((line = in.readLine()) != null) {
        line = line.trim();
        if (line.startsWith("#") || line.startsWith("//")) {
          // comment line
        } else {
          line = line.replaceAll("/", ".");
          result.add(line.substring(0, line.indexOf(',')));
        }
      }
    } finally {
      inputStream.close();
    }
    // logger.info("loaded {} class names from {}", result.size(), sanctionedSerializables);
    return result;

  }


  /**
   * Any time new serialization format is added then a new enum needs to be added here.
   *
   * @since GemFire 6.6.2
   */
  private enum SERIALIZATION_VERSION {
    vINVALID,
    // includes 6.6.0.x and 6.6.1.x. Note that no serialization changes were made in 6.6 until 6.6.2
    v660,
    // 6.6.2.x or later NOTE if you add a new constant make sure and update "latestVersion".
    v662
  }

  /**
   * Change this constant to be the last one in SERIALIZATION_VERSION
   */
  private static final SERIALIZATION_VERSION latestVersion = SERIALIZATION_VERSION.v662;

  private static SERIALIZATION_VERSION calculateSerializationVersion() {
    if (serializationVersionTxt == null || serializationVersionTxt.isEmpty()) {
      return latestVersion;
    } else if (serializationVersionTxt.startsWith("6.6.0")
        || serializationVersionTxt.startsWith("6.6.1")) {
      return SERIALIZATION_VERSION.v660;
    } else if (serializationVersionTxt.startsWith("6.6.2")) {
      return SERIALIZATION_VERSION.v662;
    } else {
      return SERIALIZATION_VERSION.vINVALID;
    }
  }

  private static final SERIALIZATION_VERSION serializationVersion = calculateSerializationVersion();

  public static boolean is662SerializationEnabled() {
    return serializationVersion.ordinal() >= SERIALIZATION_VERSION.v662.ordinal();
  }

  public static void checkSerializationVersion() {
    if (serializationVersion == SERIALIZATION_VERSION.vINVALID) {
      throw new IllegalArgumentException(
          "The system property \"gemfire.serializationVersion\" was set to \""
              + serializationVersionTxt
              + "\" which is not a valid serialization version. Valid versions must start with \"6.6.0\", \"6.6.1\", or \"6.6.2\"");
    }
  }

  static {
    initializeWellKnownSerializers();
  }

  private static void initializeWellKnownSerializers() {
    // ArrayBlockingQueue does not have zero-arg constructor
    // LinkedBlockingQueue does have zero-arg constructor but no way to get capacity

    classesToSerializers.put("java.lang.String", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        try {
          writeString((String) o, out);
        } catch (UTFDataFormatException ex) {
          // See bug 30428
          String s = "While writing a String of length " + ((String) o).length();
          UTFDataFormatException ex2 = new UTFDataFormatException(s);
          ex2.initCause(ex);
          throw ex2;
        }
        return true;
      }
    });
    classesToSerializers.put("java.net.InetAddress", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        InetAddress address = (InetAddress) o;
        out.writeByte(INET_ADDRESS);
        writeInetAddress(address, out);
        return true;
      }
    });
    classesToSerializers.put("java.net.Inet4Address", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        InetAddress address = (InetAddress) o;
        out.writeByte(INET_ADDRESS);
        writeInetAddress(address, out);
        return true;
      }
    });
    classesToSerializers.put("java.net.Inet6Address", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        InetAddress address = (InetAddress) o;
        out.writeByte(INET_ADDRESS);
        writeInetAddress(address, out);
        return true;
      }
    });
    classesToSerializers.put("java.lang.Class", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Class c = (Class) o;
        if (c.isPrimitive()) {
          writePrimitiveClass(c, out);
        } else {
          out.writeByte(CLASS);
          writeClass(c, out);
        }
        return true;
      }
    });
    classesToSerializers.put("java.lang.Boolean", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Boolean value = (Boolean) o;
        out.writeByte(BOOLEAN);
        writeBoolean(value, out);
        return true;
      }
    });
    classesToSerializers.put("java.lang.Character", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Character value = (Character) o;
        out.writeByte(CHARACTER);
        writeCharacter(value, out);
        return true;
      }
    });
    classesToSerializers.put("java.lang.Byte", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Byte value = (Byte) o;
        out.writeByte(BYTE);
        writeByte(value, out);
        return true;
      }
    });
    classesToSerializers.put("java.lang.Short", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Short value = (Short) o;
        out.writeByte(SHORT);
        writeShort(value, out);
        return true;
      }
    });
    classesToSerializers.put("java.lang.Integer", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Integer value = (Integer) o;
        out.writeByte(INTEGER);
        writeInteger(value, out);
        return true;
      }
    });
    classesToSerializers.put("java.lang.Long", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Long value = (Long) o;
        out.writeByte(LONG);
        writeLong(value, out);
        return true;
      }
    });
    classesToSerializers.put("java.lang.Float", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Float value = (Float) o;
        out.writeByte(FLOAT);
        writeFloat(value, out);
        return true;
      }
    });
    classesToSerializers.put("java.lang.Double", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Double value = (Double) o;
        out.writeByte(DOUBLE);
        writeDouble(value, out);
        return true;
      }
    });
    classesToSerializers.put("[Z", // boolean[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            out.writeByte(BOOLEAN_ARRAY);
            writeBooleanArray((boolean[]) o, out);
            return true;
          }
        });
    classesToSerializers.put("[B", // byte[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            byte[] array = (byte[]) o;
            out.writeByte(BYTE_ARRAY);
            writeByteArray(array, out);
            return true;
          }
        });
    classesToSerializers.put("[C", // char[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            out.writeByte(CHAR_ARRAY);
            writeCharArray((char[]) o, out);
            return true;
          }
        });
    classesToSerializers.put("[D", // double[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            double[] array = (double[]) o;
            out.writeByte(DOUBLE_ARRAY);
            writeDoubleArray(array, out);
            return true;
          }
        });
    classesToSerializers.put("[F", // float[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            float[] array = (float[]) o;
            out.writeByte(FLOAT_ARRAY);
            writeFloatArray(array, out);
            return true;
          }
        });
    classesToSerializers.put("[I", // int[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            int[] array = (int[]) o;
            out.writeByte(INT_ARRAY);
            writeIntArray(array, out);
            return true;
          }
        });
    classesToSerializers.put("[J", // long[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            long[] array = (long[]) o;
            out.writeByte(LONG_ARRAY);
            writeLongArray(array, out);
            return true;
          }
        });
    classesToSerializers.put("[S", // short[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            short[] array = (short[]) o;
            out.writeByte(SHORT_ARRAY);
            writeShortArray(array, out);
            return true;
          }
        });
    classesToSerializers.put("[Ljava.lang.String;", // String[]
        new WellKnownPdxDS() {
          @Override
          public boolean toData(Object o, DataOutput out) throws IOException {
            String[] array = (String[]) o;
            out.writeByte(STRING_ARRAY);
            writeStringArray(array, out);
            return true;
          }
        });
    classesToSerializers.put(TimeUnit.NANOSECONDS.getClass().getName(), new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(TIME_UNIT);
        out.writeByte(TIME_UNIT_NANOSECONDS);
        return true;
      }
    });
    classesToSerializers.put(TimeUnit.MICROSECONDS.getClass().getName(), new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(TIME_UNIT);
        out.writeByte(TIME_UNIT_MICROSECONDS);
        return true;
      }
    });
    classesToSerializers.put(TimeUnit.MILLISECONDS.getClass().getName(), new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(TIME_UNIT);
        out.writeByte(TIME_UNIT_MILLISECONDS);
        return true;
      }
    });
    classesToSerializers.put(TimeUnit.SECONDS.getClass().getName(), new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(TIME_UNIT);
        out.writeByte(TIME_UNIT_SECONDS);
        return true;
      }
    });
    classesToSerializers.put("java.util.Date", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Date date = (Date) o;
        out.writeByte(DATE);
        writeDate(date, out);
        return true;
      }
    });
    classesToSerializers.put("java.io.File", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        File file = (File) o;
        out.writeByte(FILE);
        writeFile(file, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.ArrayList", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        ArrayList list = (ArrayList) o;
        out.writeByte(ARRAY_LIST);
        writeArrayList(list, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.LinkedList", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        LinkedList list = (LinkedList) o;
        out.writeByte(LINKED_LIST);
        writeLinkedList(list, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.Vector", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(VECTOR);
        writeVector((Vector) o, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.Stack", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(STACK);
        writeStack((Stack) o, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.HashSet", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        HashSet list = (HashSet) o;
        out.writeByte(HASH_SET);
        writeHashSet(list, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.LinkedHashSet", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(LINKED_HASH_SET);
        writeLinkedHashSet((LinkedHashSet) o, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.HashMap", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        HashMap list = (HashMap) o;
        out.writeByte(HASH_MAP);
        writeHashMap(list, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.IdentityHashMap", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(IDENTITY_HASH_MAP);
        writeIdentityHashMap((IdentityHashMap) o, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.Hashtable", new WellKnownPdxDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(HASH_TABLE);
        writeHashtable((Hashtable) o, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.Properties", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        Properties props = (Properties) o;
        out.writeByte(PROPERTIES);
        writeProperties(props, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.TreeMap", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(TREE_MAP);
        writeTreeMap((TreeMap) o, out);
        return true;
      }
    });
    classesToSerializers.put("java.util.TreeSet", new WellKnownDS() {
      @Override
      public boolean toData(Object o, DataOutput out) throws IOException {
        out.writeByte(TREE_SET);
        writeTreeSet((TreeSet) o, out);
        return true;
      }
    });
    if (is662SerializationEnabled()) {
      classesToSerializers.put("java.math.BigInteger", new WellKnownDS() {
        @Override
        public boolean toData(Object o, DataOutput out) throws IOException {
          out.writeByte(BIG_INTEGER);
          writeBigInteger((BigInteger) o, out);
          return true;
        }
      });
      classesToSerializers.put("java.math.BigDecimal", new WellKnownDS() {
        @Override
        public boolean toData(Object o, DataOutput out) throws IOException {
          out.writeByte(BIG_DECIMAL);
          writeBigDecimal((BigDecimal) o, out);
          return true;
        }
      });
      classesToSerializers.put("java.util.UUID", new WellKnownDS() {
        @Override
        public boolean toData(Object o, DataOutput out) throws IOException {
          out.writeByte(UUID);
          writeUUID((UUID) o, out);
          return true;
        }
      });
      classesToSerializers.put("java.sql.Timestamp", new WellKnownDS() {
        @Override
        public boolean toData(Object o, DataOutput out) throws IOException {
          out.writeByte(TIMESTAMP);
          writeTimestamp((Timestamp) o, out);
          return true;
        }
      });
    }
  }

  /**
   * Maps the id of a serializer to its {@code DataSerializer}.
   */
  private static final ConcurrentMap/* <Integer, DataSerializer|Marker> */ idsToSerializers =
      new ConcurrentHashMap();

  /**
   * Contains the classnames of the data serializers (and not the supported classes) not yet loaded
   * into the vm as keys and their corresponding holder instances as values.
   */
  private static final ConcurrentHashMap<String, SerializerAttributesHolder> dsClassesToHolders =
      new ConcurrentHashMap<>();

  /**
   * Contains the id of the data serializers not yet loaded into the vm as keys and their
   * corresponding holder instances as values.
   */
  private static final ConcurrentHashMap<Integer, SerializerAttributesHolder> idsToHolders =
      new ConcurrentHashMap<>();

  /**
   * Contains the classnames of supported classes as keys and their corresponding
   * SerializerAttributesHolder instances as values. This applies only to the data serializers which
   * have not been loaded into the vm.
   */
  private static final ConcurrentHashMap<String, SerializerAttributesHolder> supportedClassesToHolders =
      new ConcurrentHashMap<>();

  /**
   * {@code RegistrationListener}s that receive callbacks when {@code DataSerializer}s and
   * {@code Instantiator}s are registered. Note: copy-on-write access used for this set
   */
  private static volatile Set listeners = new HashSet();

  private static final Object listenersSync = new Object();

  /**
   * Convert the given unsigned byte to an int. The returned value will be in the range [0..255]
   * inclusive
   */
  private static int ubyteToInt(byte ub) {
    return ub & 0xFF;
  }

  public static void setOldClientSupportService(final OldClientSupportService svc) {
    oldClientSupportService = svc;
  }

  public static OldClientSupportService getOldClientSupportService() {
    return oldClientSupportService;
  }

  /**
   * Instantiates an instance of {@code DataSerializer}
   *
   * @throws IllegalArgumentException If the class can't be instantiated
   *
   * @see DataSerializer#register(Class)
   */
  private static DataSerializer newInstance(Class c) {
    if (!DataSerializer.class.isAssignableFrom(c)) {
      throw new IllegalArgumentException(
          LocalizedStrings.DataSerializer_0_DOES_NOT_EXTEND_DATASERIALIZER
              .toLocalizedString(c.getName()));
    }

    Constructor init;
    try {
      init = c.getDeclaredConstructor(new Class[0]);

    } catch (NoSuchMethodException ignored) {
      StringId s = LocalizedStrings.DataSerializer_CLASS_0_DOES_NOT_HAVE_A_ZEROARGUMENT_CONSTRUCTOR;
      Object[] args = new Object[] {c.getName()};
      if (c.getDeclaringClass() != null) {
        s = LocalizedStrings.DataSerializer_CLASS_0_DOES_NOT_HAVE_A_ZEROARGUMENT_CONSTRUCTOR_IT_IS_AN_INNER_CLASS_OF_1_SHOULD_IT_BE_A_STATIC_INNER_CLASS;
        args = new Object[] {c.getName(), c.getDeclaringClass()};
      }
      throw new IllegalArgumentException(s.toLocalizedString(args));
    }

    DataSerializer s;
    try {
      init.setAccessible(true);
      s = (DataSerializer) init.newInstance(new Object[0]);

    } catch (IllegalAccessException ignored) {
      throw new IllegalArgumentException(
          LocalizedStrings.DataSerializer_COULD_NOT_INSTANTIATE_AN_INSTANCE_OF_0
              .toLocalizedString(c.getName()));

    } catch (InstantiationException ex) {
      throw new IllegalArgumentException(
          LocalizedStrings.DataSerializer_COULD_NOT_INSTANTIATE_AN_INSTANCE_OF_0
              .toLocalizedString(c.getName()),
          ex);

    } catch (InvocationTargetException ex) {
      throw new IllegalArgumentException(
          LocalizedStrings.DataSerializer_WHILE_INSTANTIATING_AN_INSTANCE_OF_0
              .toLocalizedString(c.getName()),
          ex);
    }

    return s;
  }

  public static DataSerializer register(Class c, boolean distribute, EventID eventId,
      ClientProxyMembershipID context) {
    DataSerializer s = newInstance(c);
    // This method is only called when server connection and CacheClientUpdaterThread
    s.setEventId(eventId);
    s.setContext(context);
    return _register(s, distribute);
  }

  /**
   * Registers a {@code DataSerializer} instance with the data serialization framework.
   *
   * @param distribute Should the registered {@code DataSerializer} be distributed to other members
   *        of the distributed system?
   *
   * @see DataSerializer#register(Class)
   */
  public static DataSerializer register(Class c, boolean distribute) {
    final DataSerializer s = newInstance(c);
    return _register(s, distribute);
  }

  public static DataSerializer _register(DataSerializer s, boolean distribute) {
    final int id = s.getId();
    DataSerializer dsForMarkers = s;
    if (id == 0) {
      throw new IllegalArgumentException(
          LocalizedStrings.InternalDataSerializer_CANNOT_CREATE_A_DATASERIALIZER_WITH_ID_0
              .toLocalizedString());
    }
    final Class[] classes = s.getSupportedClasses();
    if (classes == null || classes.length == 0) {
      final StringId msg =
          LocalizedStrings.InternalDataSerializer_THE_DATASERIALIZER_0_HAS_NO_SUPPORTED_CLASSES_ITS_GETSUPPORTEDCLASSES_METHOD_MUST_RETURN_AT_LEAST_ONE_CLASS;
      throw new IllegalArgumentException(msg.toLocalizedString(s.getClass().getName()));
    }

    for (Class aClass : classes) {
      if (aClass == null) {
        final StringId msg =
            LocalizedStrings.InternalDataSerializer_THE_DATASERIALIZER_GETSUPPORTEDCLASSES_METHOD_FOR_0_RETURNED_AN_ARRAY_THAT_CONTAINED_A_NULL_ELEMENT;
        throw new IllegalArgumentException(msg.toLocalizedString(s.getClass().getName()));
      } else if (aClass.isArray()) {
        final StringId msg =
            LocalizedStrings.InternalDataSerializer_THE_DATASERIALIZER_GETSUPPORTEDCLASSES_METHOD_FOR_0_RETURNED_AN_ARRAY_THAT_CONTAINED_AN_ARRAY_CLASS_WHICH_IS_NOT_ALLOWED_SINCE_ARRAYS_HAVE_BUILTIN_SUPPORT;
        throw new IllegalArgumentException(msg.toLocalizedString(s.getClass().getName()));
      }
    }

    final Integer idx = id;
    boolean retry;
    Marker oldMarker = null;
    final Marker m = new InitMarker();
    do {
      retry = false;
      Object oldSerializer = idsToSerializers.putIfAbsent(idx, m);
      if (oldSerializer != null) {
        if (oldSerializer instanceof Marker) {
          retry = !idsToSerializers.replace(idx, oldSerializer, m);
          if (!retry) {
            oldMarker = (Marker) oldSerializer;
          }
        } else if (oldSerializer.getClass().equals(s.getClass())) {
          // We've already got one of these registered
          if (distribute) {
            sendRegistrationMessage(s);
          }
          return (DataSerializer) oldSerializer;
        } else {
          DataSerializer other = (DataSerializer) oldSerializer;
          throw new IllegalStateException(
              LocalizedStrings.InternalDataSerializer_A_DATASERIALIZER_OF_CLASS_0_IS_ALREADY_REGISTERED_WITH_ID_1_SO_THE_DATASERIALIZER_OF_CLASS_2_COULD_NOT_BE_REGISTERED
                  .toLocalizedString(new Object[] {other.getClass().getName(), other.getId()}));
        }
      }
    } while (retry);

    try {
      for (int i = 0; i < classes.length; i++) {
        DataSerializer oldS = classesToSerializers.putIfAbsent(classes[i].getName(), s);
        if (oldS != null) {
          if (!s.equals(oldS)) {
            // cleanup the ones we have already added
            for (int j = 0; j < i; j++) {
              classesToSerializers.remove(classes[j].getName(), s);
            }
            dsForMarkers = null;
            String oldMsg;
            if (oldS.getId() == 0) {
              oldMsg = "DataSerializer has built-in support for class ";
            } else {
              oldMsg = "A DataSerializer of class " + oldS.getClass().getName()
                  + " is already registered to support class ";
            }
            String msg = oldMsg + classes[i].getName() + " so the DataSerializer of class "
                + s.getClass().getName() + " could not be registered.";
            if (oldS.getId() == 0) {
              throw new IllegalArgumentException(msg);
            } else {
              throw new IllegalStateException(msg);
            }
          }
        }
      }
    } finally {
      if (dsForMarkers == null) {
        idsToSerializers.remove(idx, m);
      } else {
        idsToSerializers.replace(idx, m, dsForMarkers);
      }
      if (oldMarker != null) {
        oldMarker.setSerializer(dsForMarkers);
      }
      m.setSerializer(dsForMarkers);
    }

    // if dataserializer is getting registered for first time
    // its EventID will be null, so generate a new event id
    // the the distributed system is connected
    InternalCache cache = GemFireCacheImpl.getInstance();
    if (cache != null && s.getEventId() == null) {
      s.setEventId(new EventID(cache.getDistributedSystem()));
    }

    if (distribute) {
      // send a message to other peers telling them about a newly-registered
      // dataserializer, it also send event id of the originator along with the
      // dataserializer
      sendRegistrationMessage(s);
      // send it to cache servers if it is a client
      sendRegistrationMessageToServers(s);
    }
    // send it to all cache clients irrelevant of distribute
    // bridge servers send it all the clients irrelevant of
    // originator VM
    sendRegistrationMessageToClients(s);

    fireNewDataSerializer(s);

    return s;
  }

  /**
   * Marks a {@code DataSerializer} className for registration with the data serialization
   * framework. Does not necessarily load the classes into this VM.
   *
   * @param className Name of the DataSerializer class.
   * @param distribute If true, distribute this data serializer.
   * @param eventId Event id
   * @param proxyId proxy id
   * @see DataSerializer#register(Class)
   */
  public static void register(String className, boolean distribute, EventID eventId,
      ClientProxyMembershipID proxyId, int id) {
    register(className, distribute,
        new SerializerAttributesHolder(className, eventId, proxyId, id));
  }

  /**
   * Marks a {@code DataSerializer} className for registration with the data serialization
   * framework. Does not necessarily load the classes into this VM.
   *
   * @param distribute If true, distribute this data serializer.
   * @see DataSerializer#register(Class)
   */
  public static void register(String className, boolean distribute) {
    register(className, distribute, new SerializerAttributesHolder());
  }

  private static void register(String className, boolean distribute,
      SerializerAttributesHolder holder) {
    if (StringUtils.isBlank(className)) {
      throw new IllegalArgumentException("Class name cannot be null or empty.");
    }

    SerializerAttributesHolder oldValue = dsClassesToHolders.putIfAbsent(className, holder);
    if (oldValue != null) {
      if (oldValue.getId() != 0 && holder.getId() != 0 && oldValue.getId() != holder.getId()) {
        throw new IllegalStateException(
            LocalizedStrings.InternalDataSerializer_A_DATASERIALIZER_OF_CLASS_0_IS_ALREADY_REGISTERED_WITH_ID_1_SO_THE_DATASERIALIZER_OF_CLASS_2_COULD_NOT_BE_REGISTERED
                .toLocalizedString(new Object[] {oldValue.getClass().getName(), oldValue.getId()}));
      }
    }

    idsToHolders.putIfAbsent(holder.getId(), holder);

    Object ds = idsToSerializers.get(holder.getId());
    if (ds instanceof Marker) {
      synchronized (ds) {
        ((Marker) ds).notifyAll();
      }
    }

    if (distribute) {
      sendRegistrationMessageToServers(holder);
    }
  }

  public static void updateSupportedClassesMap(HashMap<Integer, ArrayList<String>> map) {
    for (Entry<Integer, ArrayList<String>> e : map.entrySet()) {
      for (String supportedClassName : e.getValue()) {
        supportedClassesToHolders.putIfAbsent(supportedClassName, idsToHolders.get(e.getKey()));
      }
    }
  }

  public static void updateSupportedClassesMap(String dsClassName, String supportedClassName) {
    supportedClassesToHolders.putIfAbsent(supportedClassName, dsClassesToHolders.get(dsClassName));
  }

  public static class SerializerAttributesHolder {
    private String className = "";
    private EventID eventId = null;
    private ClientProxyMembershipID proxyId = null;
    private int id = 0;

    SerializerAttributesHolder() {}

    SerializerAttributesHolder(String name, EventID event, ClientProxyMembershipID proxy, int id) {
      this.className = name;
      this.eventId = event;
      this.proxyId = proxy;
      this.id = id;
    }

    /**
     *
     * @return String the classname of the data serializer this instance represents.
     */
    public String getClassName() {
      return this.className;
    }

    public EventID getEventId() {
      return this.eventId;
    }

    public ClientProxyMembershipID getProxyId() {
      return this.proxyId;
    }

    public int getId() {
      return this.id;
    }

    @Override
    public String toString() {
      return "SerializerAttributesHolder[name=" + this.className + ",id=" + this.id + ",eventId="
          + this.eventId + ']';
    }
  }

  private static void sendRegistrationMessageToServers(DataSerializer dataSerializer) {
    PoolManagerImpl.allPoolsRegisterDataSerializers(dataSerializer);
  }

  private static void sendRegistrationMessageToServers(SerializerAttributesHolder holder) {
    PoolManagerImpl.allPoolsRegisterDataSerializers(holder);
  }

  private static void sendRegistrationMessageToClients(DataSerializer dataSerializer) {
    InternalCache cache = GemFireCacheImpl.getInstance();
    if (cache == null) {
      // A cache has not yet been created.
      // we can't propagate it to clients
      return;
    }
    byte[][] serializedDataSerializer = new byte[2][];
    try {
      serializedDataSerializer[0] =
          CacheServerHelper.serialize(dataSerializer.getClass().toString().substring(6));

      byte[] idBytes = new byte[4];
      Part.encodeInt(dataSerializer.getId(), idBytes);
      serializedDataSerializer[1] = idBytes;
    } catch (IOException ignored) {
      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER,
            "InternalDataSerializer encountered an IOException while serializing DataSerializer :{}",
            dataSerializer);
      }
    }
    ClientDataSerializerMessage clientDataSerializerMessage =
        new ClientDataSerializerMessage(EnumListenerEvent.AFTER_REGISTER_DATASERIALIZER,
            serializedDataSerializer, (ClientProxyMembershipID) dataSerializer.getContext(),
            (EventID) dataSerializer.getEventId(),
            new Class[][] {dataSerializer.getSupportedClasses()});
    // Deliver it to all the clients
    CacheClientNotifier.routeClientMessage(clientDataSerializerMessage);
  }

  public static EventID generateEventId() {
    InternalCache cache = GemFireCacheImpl.getInstance();
    if (cache == null) {
      // A cache has not yet created
      return null;
    }
    return new EventID(InternalDistributedSystem.getAnyInstance());
  }

  /**
   * Unregisters a {@code Serializer} that was previously registered with the data serialization
   * framework.
   */
  public static void unregister(int id) {
    final Integer idx = id;
    Object o = idsToSerializers.remove(idx);
    if (o != null) {
      if (o instanceof InitMarker) {
        o = ((Marker) o).getSerializer();
      }
    }
    if (o instanceof DataSerializer) {
      DataSerializer s = (DataSerializer) o;
      Class[] classes = s.getSupportedClasses();
      for (Class aClass : classes) {
        classesToSerializers.remove(aClass.getName(), s);
        supportedClassesToHolders.remove(aClass.getName());
      }
      dsClassesToHolders.remove(s.getClass().getName());
      idsToHolders.remove(idx);
    }
  }

  // testHook used to clean up any registered DataSerializers
  public static void reinitialize() {
    idsToSerializers.clear();
    classesToSerializers.clear();
    supportedClassesToHolders.clear();
    dsClassesToHolders.clear();
    idsToHolders.clear();
    initializeWellKnownSerializers();
  }

  /**
   * Returns the {@code DataSerializer} for the given class. If no class has been registered,
   * {@code null} is returned. Remember that it is okay to return {@code null} in this case. This
   * method is invoked when writing an object. If a serializer isn't available, then its the user's
   * fault.
   */
  private static DataSerializer getSerializer(Class c) {
    DataSerializer ds = classesToSerializers.get(c.getName());
    if (ds == null) {
      SerializerAttributesHolder sah = supportedClassesToHolders.get(c.getName());
      if (sah != null) {
        Class dsClass = null;
        try {
          dsClass = getCachedClass(sah.getClassName());

          DataSerializer serializer = register(dsClass, false);
          dsClassesToHolders.remove(dsClass.getName());
          idsToHolders.remove(serializer.getId());
          for (Class clazz : serializer.getSupportedClasses()) {
            supportedClassesToHolders.remove(clazz.getName());
          }
          return serializer;
        } catch (ClassNotFoundException ignored) {
          logger.info(LogMarker.SERIALIZER,
              LocalizedMessage.create(
                  LocalizedStrings.InternalDataSerializer_COULD_NOT_LOAD_DATASERIALIZER_CLASS_0,
                  dsClass));
        }
      }
    }
    return ds;
  }

  /**
   * Returns the {@code DataSerializer} with the given id.
   */
  public static DataSerializer getSerializer(int id) {
    final Integer idx = id;
    final GetMarker marker = new GetMarker();
    DataSerializer result = null;
    boolean timedOut = false;
    SerializerAttributesHolder sah = idsToHolders.get(idx);
    while (result == null && !timedOut && sah == null) {
      Object o = idsToSerializers.putIfAbsent(idx, marker);
      if (o == null) {
        result = marker.getSerializer();
        if (result == null) {
          // timed out
          timedOut = true;
          idsToSerializers.remove(idx, marker);
        }
      } else if (o instanceof Marker) {
        result = ((Marker) o).getSerializer();
      } else {
        result = (DataSerializer) o;
      }
    }
    if (result == null) {
      if (sah != null) {
        Class dsClass = null;
        try {
          dsClass = getCachedClass(sah.getClassName());

          DataSerializer ds = register(dsClass, false);
          dsClassesToHolders.remove(sah.getClassName());
          idsToHolders.remove(id);
          for (Class clazz : ds.getSupportedClasses()) {
            supportedClassesToHolders.remove(clazz.getName());
          }
          return ds;
        } catch (ClassNotFoundException ignored) {
          logger.info(LogMarker.SERIALIZER,
              LocalizedMessage.create(
                  LocalizedStrings.InternalDataSerializer_COULD_NOT_LOAD_DATASERIALIZER_CLASS_0,
                  dsClass));
        }
      }
    }
    return result;
  }

  /**
   * Returns all of the currently registered serializers
   */
  public static DataSerializer[] getSerializers() {
    final int size = idsToSerializers.size();
    Collection coll = new ArrayList(size);
    for (Object v : idsToSerializers.values()) {
      if (v instanceof InitMarker) {
        v = ((Marker) v).getSerializer();
      }
      if (v instanceof DataSerializer) {
        coll.add(v);
      }
    }

    Iterator<Entry<String, SerializerAttributesHolder>> iterator =
        dsClassesToHolders.entrySet().iterator();
    while (iterator.hasNext()) {
      Entry<String, SerializerAttributesHolder> entry = iterator.next();
      String name = entry.getKey();
      SerializerAttributesHolder holder = entry.getValue();
      try {
        Class cl = getCachedClass(name);
        DataSerializer ds;
        if (holder.getEventId() != null) {
          ds = register(cl, false, holder.getEventId(), holder.getProxyId());
        } else {
          ds = register(cl, false);
        }
        coll.add(ds);
        iterator.remove();
        idsToHolders.remove(ds.getId());
        for (Class clazz : ds.getSupportedClasses()) {
          supportedClassesToHolders.remove(clazz.getName());
        }
      } catch (ClassNotFoundException ignored) {
        logger.info(LogMarker.SERIALIZER, LocalizedMessage.create(
            LocalizedStrings.InternalDataSerializer_COULD_NOT_LOAD_DATASERIALIZER_CLASS_0, name));
      }
    }

    return (DataSerializer[]) coll.toArray(new DataSerializer[coll.size()]);
  }

  /**
   * Returns all the data serializers in this vm. This method, unlike {@link #getSerializers()},
   * does not force loading of the data serializers which were not loaded in the vm earlier.
   *
   * @return Array of {@link SerializerAttributesHolder}
   */
  public static SerializerAttributesHolder[] getSerializersForDistribution() {

    final int size = idsToSerializers.size() + dsClassesToHolders.size();
    Collection<SerializerAttributesHolder> coll = new ArrayList<>(size);

    for (Object v : idsToSerializers.values()) {
      if (v instanceof InitMarker) {
        v = ((Marker) v).getSerializer();
      }
      if (v instanceof DataSerializer) {
        DataSerializer s = (DataSerializer) v;
        coll.add(new SerializerAttributesHolder(s.getClass().getName(), (EventID) s.getEventId(),
            (ClientProxyMembershipID) s.getContext(), s.getId()));
      }
    }

    for (final Entry<String, SerializerAttributesHolder> stringSerializerAttributesHolderEntry : dsClassesToHolders
        .entrySet()) {
      SerializerAttributesHolder v = stringSerializerAttributesHolderEntry.getValue();
      coll.add(v);
    }

    return coll.toArray(new SerializerAttributesHolder[coll.size()]);
  }

  /**
   * Persist this class's map to out TODO: saveRegistrations is unused
   */
  public static void saveRegistrations(DataOutput out) throws IOException {
    for (Object v : idsToSerializers.values()) {
      if (v instanceof InitMarker) {
        v = ((Marker) v).getSerializer();
      }
      if (v instanceof DataSerializer) {
        DataSerializer ds = (DataSerializer) v;
        out.writeInt(ds.getId()); // since 5.7 an int instead of a byte
        DataSerializer.writeClass(ds.getClass(), out);
      }
    }
    if (!dsClassesToHolders.isEmpty()) {
      Iterator<Entry<String, SerializerAttributesHolder>> iterator =
          dsClassesToHolders.entrySet().iterator();
      Class dsClass = null;
      while (iterator.hasNext()) {
        try {
          dsClass = getCachedClass(iterator.next().getKey());
        } catch (ClassNotFoundException ignored) {
          logger.info(LogMarker.SERIALIZER,
              LocalizedMessage.create(
                  LocalizedStrings.InternalDataSerializer_COULD_NOT_LOAD_DATASERIALIZER_CLASS_0,
                  dsClass));
          continue;
        }
        DataSerializer ds = register(dsClass, false);
        iterator.remove();
        idsToHolders.remove(ds.getId());
        for (Class clazz : ds.getSupportedClasses()) {
          supportedClassesToHolders.remove(clazz.getName());
        }

        out.writeInt(ds.getId()); // since 5.7 an int instead of a byte
        DataSerializer.writeClass(ds.getClass(), out);
      }
    }
    // We know that DataSerializer's id must be > 0 so write a zero
    // to mark the end of the ds list.
    out.writeInt(0); // since 5.7 an int instead of a byte
  }

  /**
   * Read the data from in and register it with this class. TODO: loadRegistrations is unused
   *
   * @throws IllegalArgumentException if a registration fails
   */
  public static void loadRegistrations(DataInput in) throws IOException {
    while (in.readInt() != 0) {
      Class dsClass = null;
      boolean skip = false;
      try {
        dsClass = DataSerializer.readClass(in);
      } catch (ClassNotFoundException ignored) {
        skip = true;
      }
      if (skip) {
        continue;
      }
      register(dsClass, /* dsId, */ true);
    }
  }

  /**
   * Adds a {@code RegistrationListener} that will receive callbacks when {@code DataSerializer}s
   * and {@code Instantiator}s are registered.
   */
  public static void addRegistrationListener(RegistrationListener l) {
    synchronized (listenersSync) {
      Set newSet = new HashSet(listeners);
      newSet.add(l);
      listeners = newSet;
    }
  }

  /**
   * Removes a {@code RegistrationListener} so that it no longer receives callbacks.
   */
  public static void removeRegistrationListener(RegistrationListener l) {
    synchronized (listenersSync) {
      Set newSet = new HashSet(listeners);
      newSet.remove(l);
      listeners = newSet;
    }
  }

  /**
   * Alerts all {@code RegistrationListener}s that a new {@code DataSerializer} has been registered
   *
   * @see InternalDataSerializer.RegistrationListener#newDataSerializer
   */
  private static void fireNewDataSerializer(DataSerializer ds) {
    for (Object listener1 : listeners) {
      RegistrationListener listener = (RegistrationListener) listener1;
      listener.newDataSerializer(ds);
    }
  }

  /**
   * Alerts all {@code RegistrationListener}s that a new {@code Instantiator} has been registered
   *
   * @see InternalDataSerializer.RegistrationListener#newInstantiator
   */
  static void fireNewInstantiator(Instantiator instantiator) {
    for (Object listener1 : listeners) {
      RegistrationListener listener = (RegistrationListener) listener1;
      listener.newInstantiator(instantiator);
    }
  }

  /**
   * If we are connected to a distributed system, send a message to other members telling them about
   * a newly-registered serializer.
   */
  private static void sendRegistrationMessage(DataSerializer s) {
    InternalDistributedSystem system = InternalDistributedSystem.getConnectedInstance();
    if (system != null) {
      RegistrationMessage m = new RegistrationMessage(s);
      system.getDistributionManager().putOutgoing(m);
    }
  }

  // Writes just the header of a DataSerializableFixedID to out.
  public static void writeDSFIDHeader(int dsfid, DataOutput out) throws IOException {
    if (dsfid == DataSerializableFixedID.ILLEGAL) {
      throw new IllegalStateException(
          LocalizedStrings.InternalDataSerializer_ATTEMPTED_TO_SERIALIZE_ILLEGAL_DSFID
              .toLocalizedString());
    }
    if (dsfid <= Byte.MAX_VALUE && dsfid >= Byte.MIN_VALUE) {
      out.writeByte(DS_FIXED_ID_BYTE);
      out.writeByte(dsfid);
    } else if (dsfid <= Short.MAX_VALUE && dsfid >= Short.MIN_VALUE) {
      out.writeByte(DS_FIXED_ID_SHORT);
      out.writeShort(dsfid);
    } else {
      out.writeByte(DS_FIXED_ID_INT);
      out.writeInt(dsfid);
    }
  }

  public static void writeDSFID(DataSerializableFixedID o, DataOutput out) throws IOException {
    int dsfid = o.getDSFID();
    if (dsfidToClassMap != null && logger.isTraceEnabled(LogMarker.DEBUG_DSFID)) {
      logger.trace(LogMarker.DEBUG_DSFID, "writeDSFID {} class={}", dsfid, o.getClass());
      if (dsfid != DataSerializableFixedID.NO_FIXED_ID
          && dsfid != DataSerializableFixedID.ILLEGAL) {
        // consistency check to make sure that the same DSFID is not used
        // for two different classes
        String newClassName = o.getClass().getName();
        String existingClassName = (String) dsfidToClassMap.putIfAbsent(dsfid, newClassName);
        if (existingClassName != null && !existingClassName.equals(newClassName)) {
          logger.trace(LogMarker.DEBUG_DSFID, "dsfid={} is used for class {} and class {}", dsfid,
              existingClassName, newClassName);
        }
      }
    }
    if (dsfid == DataSerializableFixedID.NO_FIXED_ID) {
      out.writeByte(DS_NO_FIXED_ID);
      DataSerializer.writeClass(o.getClass(), out);
    } else {
      writeDSFIDHeader(dsfid, out);
    }
    try {
      invokeToData(o, out);
    } catch (IOException | CancelException | ToDataException | GemFireRethrowable io) {
      // Note: this is not a user code toData but one from our
      // internal code since only GemFire product code implements DSFID

      // Serializing a PDX can result in a cache closed exception. Just rethrow

      throw io;
    } catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    } catch (Throwable t) {
      // Whenever you catch Error or Throwable, you must also
      // catch VirtualMachineError (see above). However, there is
      // _still_ a possibility that you are dealing with a cascading
      // error condition, so you also need to check to see if the JVM
      // is still usable:
      SystemFailure.checkFailure();
      throw new ToDataException("toData failed on dsfid=" + dsfid + " msg:" + t.getMessage(), t);
    }
  }

  /**
   * Data serializes an instance of a well-known class to the given {@code DataOutput}.
   *
   * @return {@code true} if {@code o} was actually written to {@code out}
   */
  private static boolean writeWellKnownObject(Object o, DataOutput out,
      boolean ensurePdxCompatibility) throws IOException {
    return writeUserObject(o, out, ensurePdxCompatibility);
  }

  /**
   * Data serializes an instance of a "user class" (that is, a class that can be handled by a
   * registered {@code DataSerializer}) to the given {@code DataOutput}.
   *
   * @return {@code true} if {@code o} was written to {@code out}.
   */
  private static boolean writeUserObject(Object o, DataOutput out, boolean ensurePdxCompatibility)
      throws IOException {

    final Class<?> c = o.getClass();
    final DataSerializer serializer = InternalDataSerializer.getSerializer(c);
    if (serializer != null) {
      int id = serializer.getId();
      if (id != 0) {
        checkPdxCompatible(o, ensurePdxCompatibility);
        // id will be 0 if it is a WellKnowDS
        if (id <= Byte.MAX_VALUE && id >= Byte.MIN_VALUE) {
          out.writeByte(USER_CLASS);
          out.writeByte((byte) id);
        } else if (id <= Short.MAX_VALUE && id >= Short.MIN_VALUE) {
          out.writeByte(USER_CLASS_2);
          out.writeShort(id);
        } else {
          out.writeByte(USER_CLASS_4);
          out.writeInt(id);
        }
      } else {
        if (ensurePdxCompatibility) {
          if (!(serializer instanceof WellKnownPdxDS)) {
            checkPdxCompatible(o, ensurePdxCompatibility);
          }
        }
      }
      boolean toDataResult;
      try {
        toDataResult = serializer.toData(o, out);
      } catch (IOException io) {
        if (serializer instanceof WellKnownDS) {
          // this is not user code so throw IOException
          throw io; // see bug 44659
        } else {
          // We no longer rethrow IOException here
          // because if user code throws an IOException we want
          // to create a ToDataException to report it as a problem
          // with the plugin code.
          throw new ToDataException(
              "toData failed on DataSerializer with id=" + id + " for class " + c, io);
        }
      } catch (CancelException | ToDataException | GemFireRethrowable ex) {
        // Serializing a PDX can result in a cache closed exception. Just rethrow
        throw ex;
      } catch (VirtualMachineError err) {
        SystemFailure.initiateFailure(err);
        // If this ever returns, rethrow the error. We're poisoned
        // now, so don't let this thread continue.
        throw err;
      } catch (Throwable t) {
        // Whenever you catch Error or Throwable, you must also
        // catch VirtualMachineError (see above). However, there is
        // _still_ a possibility that you are dealing with a cascading
        // error condition, so you also need to check to see if the JVM
        // is still usable:
        SystemFailure.checkFailure();
        throw new ToDataException(
            "toData failed on DataSerializer with id=" + id + " for class " + c, t);
      }
      if (toDataResult) {
        return true;
      } else {
        throw new ToDataException(
            LocalizedStrings.DataSerializer_SERIALIZER_0_A_1_SAID_THAT_IT_COULD_SERIALIZE_AN_INSTANCE_OF_2_BUT_ITS_TODATA_METHOD_RETURNED_FALSE
                .toLocalizedString(serializer.getId(), serializer.getClass().getName(),
                    o.getClass().getName()));
      }
      // Do byte[][] and Object[] here to fix bug 44060
    } else if (o instanceof byte[][]) {
      byte[][] byteArrays = (byte[][]) o;
      out.writeByte(ARRAY_OF_BYTE_ARRAYS);
      writeArrayOfByteArrays(byteArrays, out);
      return true;
    } else if (o instanceof Object[]) {
      Object[] array = (Object[]) o;
      out.writeByte(OBJECT_ARRAY);
      writeObjectArray(array, out, ensurePdxCompatibility);
      return true;
    } else if (is662SerializationEnabled()
        && (o.getClass().isEnum()/* for bug 52271 */ || (o.getClass().getSuperclass() != null
            && o.getClass().getSuperclass().isEnum()))) {
      if (isPdxSerializationInProgress()) {
        writePdxEnum((Enum<?>) o, out);
      } else {
        checkPdxCompatible(o, ensurePdxCompatibility);
        writeGemFireEnum((Enum<?>) o, out);
      }
      return true;
    } else {
      PdxSerializer pdxSerializer = TypeRegistry.getPdxSerializer();
      return pdxSerializer != null && writePdx(out, null, o, pdxSerializer);
    }
  }


  public static boolean autoSerialized(Object o, DataOutput out) throws IOException {
    AutoSerializableManager asm = TypeRegistry.getAutoSerializableManager();
    if (asm != null) {
      AutoClassInfo aci = asm.getExistingClassInfo(o.getClass());
      if (aci != null) {
        InternalCache internalCache = GemFireCacheImpl
            .getForPdx("PDX registry is unavailable because the Cache has been closed.");
        TypeRegistry tr = internalCache.getPdxRegistry();

        PdxOutputStream os;
        if (out instanceof HeapDataOutputStream) {
          os = new PdxOutputStream((HeapDataOutputStream) out);
        } else {
          os = new PdxOutputStream();
        }
        PdxWriterImpl writer = new PdxWriterImpl(tr, o, aci, os);

        try {
          if (is662SerializationEnabled()) {
            boolean alreadyInProgress = isPdxSerializationInProgress();
            if (!alreadyInProgress) {
              setPdxSerializationInProgress(true);
              try {
                asm.writeData(writer, o, aci);
              } finally {
                setPdxSerializationInProgress(false);
              }
            } else {
              asm.writeData(writer, o, aci);
            }
          } else {
            asm.writeData(writer, o, aci);
          }
        } catch (ToDataException | CancelException | NonPortableClassException
            | GemFireRethrowable ex) {
          throw ex;
        } catch (VirtualMachineError err) {
          SystemFailure.initiateFailure(err);
          // If this ever returns, rethrow the error. We're poisoned
          // now, so don't let this thread continue.
          throw err;
        } catch (Throwable t) {
          // Whenever you catch Error or Throwable, you must also
          // catch VirtualMachineError (see above). However, there is
          // _still_ a possibility that you are dealing with a cascading
          // error condition, so you also need to check to see if the JVM
          // is still usable:
          SystemFailure.checkFailure();
          throw new ToDataException("PdxSerializer failed when calling toData on " + o.getClass(),
              t);
        }
        int bytesWritten = writer.completeByteStreamGeneration();
        getDMStats(internalCache).incPdxSerialization(bytesWritten);
        if (!(out instanceof HeapDataOutputStream)) {
          writer.sendTo(out);
        }
        return true;
      }
    }
    return false;
  }

  private static void checkPdxCompatible(Object o, boolean ensurePdxCompatibility) {
    if (ensurePdxCompatibility) {
      throw new NonPortableClassException(
          "Instances of " + o.getClass() + " are not compatible with non-java PDX.");
    }
  }

  /**
   * Test to see if the object is in the gemfire package, to see if we should pass it on to a user's
   * custom serializater.
   */
  static boolean isGemfireObject(Object o) {
    return (o instanceof Function // fixes 43691
        || o.getClass().getName().startsWith("org.apache.geode."))
        && !(o instanceof PdxSerializerObject);
  }

  /**
   * Reads an object that was serialized by a customer ("user") {@code DataSerializer} from the
   * given {@code DataInput}.
   *
   * @throws IOException If the serializer that can deserialize the object is not registered.
   */
  private static Object readUserObject(DataInput in, int serializerId)
      throws IOException, ClassNotFoundException {
    DataSerializer serializer = InternalDataSerializer.getSerializer(serializerId);

    if (serializer == null) {
      throw new IOException(LocalizedStrings.DataSerializer_SERIALIZER_0_IS_NOT_REGISTERED
          .toLocalizedString(new Object[] {serializerId}));
    }

    return serializer.fromData(in);
  }

  /**
   * Checks to make sure a {@code DataOutput} is not {@code null}.
   *
   * @throws NullPointerException If {@code out} is {@code null}
   */
  public static void checkOut(DataOutput out) {
    if (out == null) {
      String s = "Null DataOutput";
      throw new NullPointerException(s);
    }
  }

  /**
   * Checks to make sure a {@code DataInput} is not {@code null}.
   *
   * @throws NullPointerException If {@code in} is {@code null}
   */
  public static void checkIn(DataInput in) {
    if (in == null) {
      String s = "Null DataInput";
      throw new NullPointerException(s);
    }
  }

  /**
   * Writes a {@code Set} to a {@code DataOutput}.
   * <P>
   * This method is internal because its semantics (that is, its ability to write any kind of
   * {@code Set}) are different from the {@code write}XXX methods of the external
   * {@code DataSerializer}.
   *
   * @throws IOException A problem occurs while writing to {@code out}
   *
   * @see #readSet
   *
   * @since GemFire 4.0
   */
  public static void writeSet(Collection<?> set, DataOutput out) throws IOException {
    checkOut(out);

    int size;
    if (set == null) {
      size = -1;
    } else {
      size = set.size();
    }
    writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Writing HashSet with {} elements: {}", size, set);
    }
    if (size > 0) {
      for (Object element : set) {
        writeObject(element, out);
      }
    }
  }

  /**
   * Reads a {@code Set} from a {@code DataInput}.
   *
   * @throws IOException A problem occurs while writing to {@code out}
   * @throws ClassNotFoundException The class of one of the {@code HashSet}'s elements cannot be
   *         found.
   *
   * @see #writeSet
   *
   * @since GemFire 4.0
   */
  public static Set readSet(DataInput in) throws IOException, ClassNotFoundException {
    return readHashSet(in);
  }

  /**
   * Reads a {@code Set} from a {@code DataInput} into the given non-null collection. Returns true
   * if collection read is non-null else returns false. TODO: readCollection is unused
   *
   * @throws IOException A problem occurs while reading from {@code in}
   * @throws ClassNotFoundException The class of one of the {@code Set}'s elements cannot be found.
   *
   * @see #writeSet
   */
  public static <E> boolean readCollection(DataInput in, Collection<E> c)
      throws IOException, ClassNotFoundException {

    checkIn(in);

    final int size = readArrayLength(in);
    if (size >= 0) {
      for (int index = 0; index < size; ++index) {
        E element = DataSerializer.readObject(in);
        c.add(element);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER, "Read Collection with {} elements: {}", size, c);
      }
      return true;
    }
    return false;
  }

  /**
   * write a set of Long objects
   *
   * @param set the set of Long objects
   * @param hasLongIDs if false, write only ints, not longs
   * @param out the output stream
   */
  public static void writeSetOfLongs(Set set, boolean hasLongIDs, DataOutput out)
      throws IOException {
    if (set == null) {
      out.writeInt(-1);
    } else {
      out.writeInt(set.size());
      out.writeBoolean(hasLongIDs);
      for (Object aSet : set) {
        Long l = (Long) aSet;
        if (hasLongIDs) {
          out.writeLong(l);
        } else {
          out.writeInt((int) l.longValue());
        }
      }
    }
  }

  /** read a set of Long objects */
  public static Set<Long> readSetOfLongs(DataInput in) throws IOException {
    int size = in.readInt();
    if (size < 0) {
      return null;
    } else {
      Set result = new HashSet(size);
      boolean longIDs = in.readBoolean();
      for (int i = 0; i < size; i++) {
        long l = longIDs ? in.readLong() : in.readInt();
        result.add(l);
      }
      return result;
    }
  }

  /**
   * write a set of Long objects TODO: writeListOfLongs is unused
   *
   * @param list the set of Long objects
   * @param hasLongIDs if false, write only ints, not longs
   * @param out the output stream
   */
  public static void writeListOfLongs(List list, boolean hasLongIDs, DataOutput out)
      throws IOException {
    if (list == null) {
      out.writeInt(-1);
    } else {
      out.writeInt(list.size());
      out.writeBoolean(hasLongIDs);
      for (Object aList : list) {
        Long l = (Long) aList;
        if (hasLongIDs) {
          out.writeLong(l);
        } else {
          out.writeInt((int) l.longValue());
        }
      }
    }
  }

  /** read a set of Long objects */
  public static List<Long> readListOfLongs(DataInput in) throws IOException {
    int size = in.readInt();
    if (size < 0) {
      return null;
    } else {
      List result = new LinkedList();
      boolean longIDs = in.readBoolean();
      for (int i = 0; i < size; i++) {
        long l = longIDs ? in.readLong() : in.readInt();
        result.add(l);
      }
      return result;
    }
  }



  /**
   * Writes the type code for a primitive type Class to {@code DataOutput}.
   */
  public static void writePrimitiveClass(Class c, DataOutput out) throws IOException {
    if (c == Boolean.TYPE) {
      out.writeByte(BOOLEAN_TYPE);
    } else if (c == Character.TYPE) {
      out.writeByte(CHARACTER_TYPE);
    } else if (c == Byte.TYPE) {
      out.writeByte(BYTE_TYPE);
    } else if (c == Short.TYPE) {
      out.writeByte(SHORT_TYPE);
    } else if (c == Integer.TYPE) {
      out.writeByte(INTEGER_TYPE);
    } else if (c == Long.TYPE) {
      out.writeByte(LONG_TYPE);
    } else if (c == Float.TYPE) {
      out.writeByte(FLOAT_TYPE);
    } else if (c == Double.TYPE) {
      out.writeByte(DOUBLE_TYPE);
    } else if (c == Void.TYPE) {
      out.writeByte(VOID_TYPE);
    } else if (c == null) {
      out.writeByte(NULL);
    } else {
      throw new InternalGemFireError(
          LocalizedStrings.InternalDataSerializer_UNKNOWN_PRIMITIVE_TYPE_0
              .toLocalizedString(c.getName()));
    }
  }

  public static Class decodePrimitiveClass(byte typeCode) {
    switch (typeCode) {
      case BOOLEAN_TYPE:
        return Boolean.TYPE;
      case CHARACTER_TYPE:
        return Character.TYPE;
      case BYTE_TYPE:
        return Byte.TYPE;
      case SHORT_TYPE:
        return Short.TYPE;
      case INTEGER_TYPE:
        return Integer.TYPE;
      case LONG_TYPE:
        return Long.TYPE;
      case FLOAT_TYPE:
        return Float.TYPE;
      case DOUBLE_TYPE:
        return Double.TYPE;
      case VOID_TYPE:
        return Void.TYPE;
      case NULL:
        return null;
      default:
        throw new InternalGemFireError(LocalizedStrings.InternalDataSerializer_UNEXPECTED_TYPECODE_0
            .toLocalizedString(typeCode));
    }
  }

  private static final byte TIME_UNIT_NANOSECONDS = -1;
  private static final byte TIME_UNIT_MICROSECONDS = -2;
  private static final byte TIME_UNIT_MILLISECONDS = -3;
  private static final byte TIME_UNIT_SECONDS = -4;

  /**
   * Reads a {@code TimeUnit} from a {@code DataInput}.
   *
   * @throws IOException A problem occurs while writing to {@code out}
   */
  private static TimeUnit readTimeUnit(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    byte type = in.readByte();

    TimeUnit unit;
    switch (type) {
      case TIME_UNIT_NANOSECONDS:
        unit = TimeUnit.NANOSECONDS;
        break;
      case TIME_UNIT_MICROSECONDS:
        unit = TimeUnit.MICROSECONDS;
        break;
      case TIME_UNIT_MILLISECONDS:
        unit = TimeUnit.MILLISECONDS;
        break;
      case TIME_UNIT_SECONDS:
        unit = TimeUnit.SECONDS;
        break;
      default:
        throw new IOException(
            LocalizedStrings.DataSerializer_UNKNOWN_TIMEUNIT_TYPE_0.toLocalizedString(type));
    }

    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Read TimeUnit: {}", unit);
    }

    return unit;
  }

  private static void writeTimestamp(Timestamp o, DataOutput out) throws IOException {
    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Writing Timestamp: {}", o);
    }
    DataSerializer.writePrimitiveLong(o.getTime(), out);
  }

  private static Timestamp readTimestamp(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);
    Timestamp result = new Timestamp(DataSerializer.readPrimitiveLong(in));
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Read Timestamp: {}", result);
    }
    return result;
  }

  private static void writeUUID(java.util.UUID o, DataOutput out) throws IOException {
    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Writing UUID: {}", o);
    }
    DataSerializer.writePrimitiveLong(o.getMostSignificantBits(), out);
    DataSerializer.writePrimitiveLong(o.getLeastSignificantBits(), out);
  }

  private static UUID readUUID(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);
    long mb = DataSerializer.readPrimitiveLong(in);
    long lb = DataSerializer.readPrimitiveLong(in);
    UUID result = new UUID(mb, lb);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Read UUID: {}", result);
    }
    return result;
  }

  private static void writeBigDecimal(BigDecimal o, DataOutput out) throws IOException {
    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Writing BigDecimal: {}", o);
    }
    DataSerializer.writeString(o.toString(), out);
  }

  private static BigDecimal readBigDecimal(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);
    BigDecimal result = new BigDecimal(DataSerializer.readString(in));
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Read BigDecimal: {}", result);
    }
    return result;
  }

  private static void writeBigInteger(BigInteger o, DataOutput out) throws IOException {
    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Writing BigInteger: {}", o);
    }
    DataSerializer.writeByteArray(o.toByteArray(), out);
  }

  private static BigInteger readBigInteger(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);
    BigInteger result = new BigInteger(DataSerializer.readByteArray(in));
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Read BigInteger: {}", result);
    }
    return result;
  }

  private static final ConcurrentMap dsfidToClassMap =
      logger.isTraceEnabled(LogMarker.DEBUG_DSFID) ? new ConcurrentHashMap() : null;

  public static void writeUserDataSerializableHeader(int classId, DataOutput out)
      throws IOException {
    if (classId <= Byte.MAX_VALUE && classId >= Byte.MIN_VALUE) {
      out.writeByte(USER_DATA_SERIALIZABLE);
      out.writeByte(classId);
    } else if (classId <= Short.MAX_VALUE && classId >= Short.MIN_VALUE) {
      out.writeByte(USER_DATA_SERIALIZABLE_2);
      out.writeShort(classId);
    } else {
      out.writeByte(USER_DATA_SERIALIZABLE_4);
      out.writeInt(classId);
    }
  }

  /**
   * Writes given number of characters from array of {@code char}s to a {@code DataOutput}.
   *
   * @throws IOException A problem occurs while writing to {@code out}
   *
   * @see DataSerializer#readCharArray
   * @since GemFire 6.6
   */
  public static void writeCharArray(char[] array, int length, DataOutput out) throws IOException {
    checkOut(out);

    if (array == null) {
      length = -1;
    }
    writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Writing char array of length {}", length);
    }
    if (length > 0) {
      for (int i = 0; i < length; i++) {
        out.writeChar(array[i]);
      }
    }
  }

  /**
   * returns true if the byte array is the serialized form of a null reference
   *
   * @param serializedForm the serialized byte array
   */
  public static boolean isSerializedNull(byte[] serializedForm) {
    return serializedForm.length == 1 && serializedForm[0] == NULL;
  }

  public static void basicWriteObject(Object o, DataOutput out, boolean ensurePdxCompatibility)
      throws IOException {
    checkOut(out);

    final boolean isDebugEnabled_SERIALIZER = logger.isTraceEnabled(LogMarker.SERIALIZER);
    if (isDebugEnabled_SERIALIZER) {
      logger.trace(LogMarker.SERIALIZER, "basicWriteObject: {}", o);
    }

    // Handle special objects first
    if (o == null) {
      out.writeByte(NULL);

    } else if (o instanceof DataSerializableFixedID) {
      checkPdxCompatible(o, ensurePdxCompatibility);
      DataSerializableFixedID dsfid = (DataSerializableFixedID) o;
      writeDSFID(dsfid, out);
    } else if (autoSerialized(o, out)) {
      // all done
    } else if (o instanceof DataSerializable.Replaceable) {
      // do this first to fix bug 31609
      // do this before DataSerializable
      Object replacement = ((DataSerializable.Replaceable) o).replace();
      basicWriteObject(replacement, out, ensurePdxCompatibility);

    } else if (o instanceof PdxSerializable) {
      writePdx(out, GemFireCacheImpl
          .getForPdx("PDX registry is unavailable because the Cache has been closed."), o, null);
    } else if (o instanceof DataSerializable) {
      if (isDebugEnabled_SERIALIZER) {
        logger.trace(LogMarker.SERIALIZER, "Writing DataSerializable: {}", o);
      }
      checkPdxCompatible(o, ensurePdxCompatibility);

      Class c = o.getClass();
      // Is "c" a user class registered with an Instantiator?
      int classId = InternalInstantiator.getClassId(c);
      if (classId != 0) {
        writeUserDataSerializableHeader(classId, out);
      } else {
        out.writeByte(DATA_SERIALIZABLE);
        DataSerializer.writeClass(c, out);
      }
      DataSerializable ds = (DataSerializable) o;
      invokeToData(ds, out);

    } else if (o instanceof Sendable) {
      if (!(o instanceof PdxInstance) || o instanceof PdxInstanceEnum) {
        checkPdxCompatible(o, ensurePdxCompatibility);
      }
      ((Sendable) o).sendTo(out);
    } else if (writeWellKnownObject(o, out, ensurePdxCompatibility)) {
      // Nothing more to do...
    } else {
      checkPdxCompatible(o, ensurePdxCompatibility);
      if (logger.isTraceEnabled(LogMarker.DUMP_SERIALIZED)) {
        logger.trace(LogMarker.DUMP_SERIALIZED, "DataSerializer Serializing an instance of {}",
            o.getClass().getName());
      }

      /*
       * If the (internally known) ThreadLocal named "DataSerializer.DISALLOW_JAVA_SERIALIZATION" is
       * set, then an exception will be thrown if we try to do standard Java Serialization. This is
       * used to catch Java serialization early for the case where the data is being sent to a
       * non-Java client
       */
      if (disallowJavaSerialization() && o instanceof Serializable) {
        throw new NotSerializableException(
            LocalizedStrings.DataSerializer_0_IS_NOT_DATASERIALIZABLE_AND_JAVA_SERIALIZATION_IS_DISALLOWED
                .toLocalizedString(o.getClass().getName()));
      }

      writeSerializableObject(o, out);
    }
  }

  private static boolean disallowJavaSerialization() {
    Boolean v = DISALLOW_JAVA_SERIALIZATION.get();
    return v != null && v;
  }

  /**
   * @since GemFire 6.6.2
   */
  private static void writePdxEnum(Enum<?> e, DataOutput out) throws IOException {
    TypeRegistry tr =
        GemFireCacheImpl.getForPdx("PDX registry is unavailable because the Cache has been closed.")
            .getPdxRegistry();
    int eId = tr.getEnumId(e);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "write PdxEnum id={} enum={}", eId, e);
    }
    writePdxEnumId(eId, out);
  }

  public static void writePdxEnumId(int eId, DataOutput out) throws IOException {
    out.writeByte(PDX_ENUM);
    out.writeByte(eId >> 24);
    writeArrayLength(eId & 0xFFFFFF, out);
  }

  /**
   * @throws IOException since 6.6.2
   */
  private static Object readPdxEnum(DataInput in) throws IOException {
    int dsId = in.readByte();
    int tmp = readArrayLength(in);
    int enumId = dsId << 24 | tmp & 0xFFFFFF;
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "read PdxEnum id={}", enumId);
    }
    InternalCache internalCache = GemFireCacheImpl
        .getForPdx("PDX registry is unavailable because the Cache has been closed.");
    TypeRegistry tr = internalCache.getPdxRegistry();

    Object result = tr.getEnumById(enumId);
    if (result instanceof PdxInstance) {
      getDMStats(internalCache).incPdxInstanceCreations();
    }
    return result;
  }

  private static void writeGemFireEnum(Enum<?> e, DataOutput out) throws IOException {
    boolean isGemFireObject = isGemfireObject(e);
    DataSerializer.writePrimitiveByte(isGemFireObject ? GEMFIRE_ENUM : PDX_INLINE_ENUM, out);
    DataSerializer.writeString(e.getDeclaringClass().getName(), out);
    DataSerializer.writeString(e.name(), out);
    if (!isGemFireObject) {
      InternalDataSerializer.writeArrayLength(e.ordinal(), out);
    }
  }

  @SuppressWarnings("unchecked")
  private static Enum<?> readGemFireEnum(DataInput in) throws IOException, ClassNotFoundException {
    String className = DataSerializer.readString(in);
    String enumName = DataSerializer.readString(in);
    @SuppressWarnings("rawtypes")
    Class c = getCachedClass(className);
    return Enum.valueOf(c, enumName);
  }

  private static Object readPdxInlineEnum(DataInput in) throws IOException, ClassNotFoundException {
    InternalCache internalCache = GemFireCacheImpl.getInstance();
    if (internalCache != null && internalCache.getPdxReadSerializedByAnyGemFireServices()) {
      String className = DataSerializer.readString(in);
      String enumName = DataSerializer.readString(in);
      int enumOrdinal = InternalDataSerializer.readArrayLength(in);
      getDMStats(internalCache).incPdxInstanceCreations();
      return new PdxInstanceEnum(className, enumName, enumOrdinal);
    } else {
      Enum<?> e = readGemFireEnum(in);
      InternalDataSerializer.readArrayLength(in);
      return e;
    }
  }

  /**
   * write an object in java Serializable form with a SERIALIZABLE DSCODE so that it can be
   * deserialized with DataSerializer.readObject()
   *
   * @param o the object to serialize
   * @param out the data output to serialize to
   */
  public static void writeSerializableObject(Object o, DataOutput out) throws IOException {
    out.writeByte(SERIALIZABLE);
    if (out instanceof ObjectOutputStream) {
      ((ObjectOutputStream) out).writeObject(o);
    } else {
      OutputStream stream;
      if (out instanceof OutputStream) {
        stream = (OutputStream) out;

      } else {
        final DataOutput out2 = out;
        stream = new OutputStream() {
          @Override
          public void write(int b) throws IOException {
            out2.write(b);
          }
        };
      }
      boolean wasDoNotCopy = false;
      if (out instanceof HeapDataOutputStream) {
        // To fix bug 52197 disable doNotCopy mode
        // while serialize with an ObjectOutputStream.
        // The problem is that ObjectOutputStream keeps
        // an internal byte array that it reuses while serializing.
        wasDoNotCopy = ((HeapDataOutputStream) out).setDoNotCopy(false);
      }
      try {
        ObjectOutput oos = new ObjectOutputStream(stream);
        if (stream instanceof VersionedDataStream) {
          Version v = ((VersionedDataStream) stream).getVersion();
          if (v != null && v != Version.CURRENT) {
            oos = new VersionedObjectOutput(oos, v);
          }
        }
        oos.writeObject(o);
        // To fix bug 35568 just call flush. We can't call close because
        // it calls close on the wrapped OutputStream.
        oos.flush();
      } finally {
        if (wasDoNotCopy) {
          ((HeapDataOutputStream) out).setDoNotCopy(true);
        }
      }
    }
  }

  /**
   * For backward compatibility this method should be used to invoke toData on a DSFID or
   * DataSerializable. It will invoke the correct toData method based on the class's version
   * information. This method does not write information about the class of the object. When
   * deserializing use the method invokeFromData to read the contents of the object.
   *
   * @param ds the object to write
   * @param out the output stream.
   */
  public static void invokeToData(Object ds, DataOutput out) throws IOException {
    boolean isDSFID = ds instanceof DataSerializableFixedID;
    try {
      boolean invoked = false;
      Version v = InternalDataSerializer.getVersionForDataStreamOrNull(out);

      if (v != null && v != Version.CURRENT) {
        // get versions where DataOutput was upgraded
        Version[] versions = null;
        if (ds instanceof SerializationVersions) {
          SerializationVersions sv = (SerializationVersions) ds;
          versions = sv.getSerializationVersions();
        }
        // check if the version of the peer or diskstore is different and
        // there has been a change in the message
        if (versions != null && versions.length > 0) {
          for (Version version : versions) {
            // if peer version is less than the greatest upgraded version
            if (v.compareTo(version) < 0) {
              ds.getClass().getMethod("toDataPre_" + version.getMethodSuffix(),
                  new Class[] {DataOutput.class}).invoke(ds, out);
              invoked = true;
              break;
            }
          }
        }
      }

      if (!invoked) {
        if (isDSFID) {
          ((DataSerializableFixedID) ds).toData(out);
        } else {
          ((DataSerializable) ds).toData(out);
        }
      }
    } catch (IOException io) {
      // DSFID serialization expects an IOException but otherwise
      // we want to catch it and transform into a ToDataException
      // since it might be in user code and we want to report it
      // as a problem with the plugin code
      if (isDSFID) {
        throw io;
      } else {
        throw new ToDataException("toData failed on DataSerializable " + ds.getClass(), io);
      }
    } catch (CancelException | ToDataException | GemFireRethrowable ex) {
      // Serializing a PDX can result in a cache closed exception. Just rethrow
      throw ex;
    } catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    } catch (Throwable t) {
      // Whenever you catch Error or Throwable, you must also
      // catch VirtualMachineError (see above). However, there is
      // _still_ a possibility that you are dealing with a cascading
      // error condition, so you also need to check to see if the JVM
      // is still usable:
      SystemFailure.checkFailure();
      throw new ToDataException("toData failed on DataSerializable " + ds.getClass(), t);
    }
  }

  /**
   * For backward compatibility this method should be used to invoke fromData on a DSFID or
   * DataSerializable. It will invoke the correct fromData method based on the class's version
   * information. This method does not read information about the class of the object. When
   * serializing use the method invokeToData to write the contents of the object.
   *
   * @param ds the object to write
   * @param in the input stream.
   */
  public static void invokeFromData(Object ds, DataInput in)
      throws IOException, ClassNotFoundException {
    try {
      boolean invoked = false;
      Version v = InternalDataSerializer.getVersionForDataStreamOrNull(in);
      if (v != null && v != Version.CURRENT) {
        // get versions where DataOutput was upgraded
        Version[] versions = null;
        if (ds instanceof SerializationVersions) {
          SerializationVersions vds = (SerializationVersions) ds;
          versions = vds.getSerializationVersions();
        }
        // check if the version of the peer or diskstore is different and
        // there has been a change in the message
        if (versions != null && versions.length > 0) {
          for (Version version : versions) {
            // if peer version is less than the greatest upgraded version
            if (v.compareTo(version) < 0) {
              ds.getClass().getMethod("fromDataPre" + '_' + version.getMethodSuffix(),
                  new Class[] {DataInput.class}).invoke(ds, in);
              invoked = true;
              break;
            }
          }
        }
      }
      if (!invoked) {
        if (ds instanceof DataSerializableFixedID) {
          ((DataSerializableFixedID) ds).fromData(in);
        } else {
          ((DataSerializable) ds).fromData(in);
        }
      }
    } catch (EOFException | ClassNotFoundException | CacheClosedException ex) {
      // client went away - ignore
      throw ex;
    } catch (Exception ex) {
      throw new SerializationException(
          LocalizedStrings.DataSerializer_COULD_NOT_CREATE_AN_INSTANCE_OF_0
              .toLocalizedString(ds.getClass().getName()),
          ex);
    }
  }


  private static Object readDataSerializable(final DataInput in)
      throws IOException, ClassNotFoundException {
    Class c = readClass(in);
    try {
      Constructor init = c.getConstructor(new Class[0]);
      init.setAccessible(true);
      Object o = init.newInstance(new Object[0]);
      Assert.assertTrue(o instanceof DataSerializable);
      invokeFromData(o, in);

      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER, "Read DataSerializable {}", o);
      }

      return o;

    } catch (EOFException ex) {
      // client went away - ignore
      throw ex;
    } catch (Exception ex) {
      throw new SerializationException(
          LocalizedStrings.DataSerializer_COULD_NOT_CREATE_AN_INSTANCE_OF_0
              .toLocalizedString(c.getName()),
          ex);
    }
  }

  private static Object readDataSerializableFixedID(final DataInput in)
      throws IOException, ClassNotFoundException {
    Class c = readClass(in);
    try {
      Constructor init = c.getConstructor(new Class[0]);
      init.setAccessible(true);
      Object o = init.newInstance(new Object[0]);

      invokeFromData(o, in);

      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER, "Read DataSerializableFixedID {}", o);
      }

      return o;

    } catch (Exception ex) {
      throw new SerializationException(
          LocalizedStrings.DataSerializer_COULD_NOT_CREATE_AN_INSTANCE_OF_0
              .toLocalizedString(c.getName()),
          ex);
    }
  }

  /**
   * Get the {@link Version} of the peer or disk store that created this {@link DataInput}.
   */
  public static Version getVersionForDataStream(DataInput in) {
    // check if this is a versioned data input
    if (in instanceof VersionedDataStream) {
      final Version v = ((VersionedDataStream) in).getVersion();
      return v != null ? v : Version.CURRENT;
    } else {
      // assume latest version
      return Version.CURRENT;
    }
  }

  /**
   * Get the {@link Version} of the peer or disk store that created this {@link DataInput}. Returns
   * null if the version is same as this member's.
   */
  public static Version getVersionForDataStreamOrNull(DataInput in) {
    // check if this is a versioned data input
    if (in instanceof VersionedDataStream) {
      return ((VersionedDataStream) in).getVersion();
    } else {
      // assume latest version
      return null;
    }
  }

  /**
   * Get the {@link Version} of the peer or disk store that created this {@link DataOutput}.
   */
  public static Version getVersionForDataStream(DataOutput out) {
    // check if this is a versioned data output
    if (out instanceof VersionedDataStream) {
      final Version v = ((VersionedDataStream) out).getVersion();
      return v != null ? v : Version.CURRENT;
    } else {
      // assume latest version
      return Version.CURRENT;
    }
  }

  /**
   * Get the {@link Version} of the peer or disk store that created this {@link DataOutput}. Returns
   * null if the version is same as this member's.
   */
  public static Version getVersionForDataStreamOrNull(DataOutput out) {
    // check if this is a versioned data output
    if (out instanceof VersionedDataStream) {
      return ((VersionedDataStream) out).getVersion();
    } else {
      // assume latest version
      return null;
    }
  }

  // array is null
  public static final byte NULL_ARRAY = -1;

  /**
   * array len encoded as unsigned short in next 2 bytes
   *
   * @since GemFire 5.7
   */
  private static final byte SHORT_ARRAY_LEN = -2;

  /**
   * array len encoded as int in next 4 bytes
   *
   * @since GemFire 5.7
   */
  public static final byte INT_ARRAY_LEN = -3;

  private static final int MAX_BYTE_ARRAY_LEN = (byte) -4 & 0xFF;

  public static void writeArrayLength(int len, DataOutput out) throws IOException {
    if (len == -1) {
      out.writeByte(NULL_ARRAY);
    } else if (len <= MAX_BYTE_ARRAY_LEN) {
      out.writeByte(len);
    } else if (len <= 0xFFFF) {
      out.writeByte(SHORT_ARRAY_LEN);
      out.writeShort(len);
    } else {
      out.writeByte(INT_ARRAY_LEN);
      out.writeInt(len);
    }
  }

  public static int readArrayLength(DataInput in) throws IOException {
    byte code = in.readByte();
    if (code == NULL_ARRAY) {
      return -1;
    } else {
      int result = ubyteToInt(code);
      if (result > MAX_BYTE_ARRAY_LEN) {
        if (code == SHORT_ARRAY_LEN) {
          result = in.readUnsignedShort();
        } else if (code == INT_ARRAY_LEN) {
          result = in.readInt();
        } else {
          throw new IllegalStateException("unexpected array length code=" + code);
        }
      }
      return result;
    }
  }

  /**
   * Serializes a list of Integers. The argument may be null. Deserialize with readListOfIntegers().
   *
   * TODO: writeListOfIntegers is unused
   */
  public void writeListOfIntegers(List<Integer> list, DataOutput out) throws IOException {
    int size;
    if (list == null) {
      size = -1;
    } else {
      size = list.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (size > 0) {
      for (int i = 0; i < size; i++) {
        out.writeInt(list.get(i));
      }
    }
  }

  public static Object readDSFID(final DataInput in) throws IOException, ClassNotFoundException {
    checkIn(in);
    byte header = in.readByte();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "readDSFID: header={}", header);
    }
    if (header == DS_FIXED_ID_BYTE) {
      return DSFIDFactory.create(in.readByte(), in);
    } else if (header == DS_FIXED_ID_SHORT) {
      return DSFIDFactory.create(in.readShort(), in);
    } else if (header == DS_NO_FIXED_ID) {
      return readDataSerializableFixedID(in);
    } else if (header == DS_FIXED_ID_INT) {
      return DSFIDFactory.create(in.readInt(), in);
    } else {
      throw new IllegalStateException("unexpected byte: " + header + " while reading dsfid");
    }
  }

  public static int readDSFIDHeader(final DataInput in) throws IOException {
    checkIn(in);
    byte header = in.readByte();
    if (header == DS_FIXED_ID_BYTE) {
      return in.readByte();
    } else if (header == DS_FIXED_ID_SHORT) {
      return in.readShort();
    } else if (header == DS_NO_FIXED_ID) {
      // is that correct??
      return Integer.MAX_VALUE;
    } else if (header == DS_FIXED_ID_INT) {
      return in.readInt();
    } else {
      throw new IllegalStateException("unexpected byte: " + header + " while reading dsfid");
    }
  }

  /**
   * Reads an instance of {@code String} from a {@code DataInput} given the header byte already
   * being read. The return value may be {@code null}.
   *
   * @throws IOException A problem occurs while reading from {@code in}
   *
   * @since GemFire 5.7
   */
  public static String readString(DataInput in, byte header) throws IOException {
    if (header == DSCODE.STRING_BYTES) {
      int len = in.readUnsignedShort();
      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER, "Reading STRING_BYTES of len={}", len);
      }
      byte[] buf = new byte[len];
      in.readFully(buf, 0, len);
      return new String(buf, 0); // intentionally using deprecated constructor
    } else if (header == DSCODE.STRING) {
      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER, "Reading utf STRING");
      }
      return in.readUTF();
    } else if (header == DSCODE.NULL_STRING) {
      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER, "Reading NULL_STRING");
      }
      return null;
    } else if (header == DSCODE.HUGE_STRING_BYTES) {
      int len = in.readInt();
      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER, "Reading HUGE_STRING_BYTES of len={}", len);
      }
      byte[] buf = new byte[len];
      in.readFully(buf, 0, len);
      return new String(buf, 0); // intentionally using deprecated constructor
    } else if (header == DSCODE.HUGE_STRING) {
      int len = in.readInt();
      if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
        logger.trace(LogMarker.SERIALIZER, "Reading HUGE_STRING of len={}", len);
      }
      char[] buf = new char[len];
      for (int i = 0; i < len; i++) {
        buf[i] = in.readChar();
      }
      return new String(buf);
    } else {
      String s = "Unknown String header " + header;
      throw new IOException(s);
    }
  }

  private static DataSerializer dvddeserializer;

  // TODO: registerDVDDeserializer is unused
  public static void registerDVDDeserializer(DataSerializer dvddeslzr) {
    dvddeserializer = dvddeslzr;
  }

  /**
   * Just like readObject but make sure and pdx deserialized is not a PdxInstance.
   *
   * @since GemFire 6.6.2
   */
  public static <T> T readNonPdxInstanceObject(final DataInput in)
      throws IOException, ClassNotFoundException {
    boolean wouldReadSerialized = PdxInstanceImpl.getPdxReadSerialized();
    if (!wouldReadSerialized) {
      return DataSerializer.readObject(in);
    } else {
      PdxInstanceImpl.setPdxReadSerialized(false);
      try {
        return DataSerializer.readObject(in);
      } finally {
        PdxInstanceImpl.setPdxReadSerialized(true);
      }
    }
  }

  public static Object basicReadObject(final DataInput in)
      throws IOException, ClassNotFoundException {
    checkIn(in);

    // Read the header byte
    byte header = in.readByte();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "basicReadObject: header={}", header);
    }
    switch (header) {
      case DS_FIXED_ID_BYTE:
        return DSFIDFactory.create(in.readByte(), in);
      case DS_FIXED_ID_SHORT:
        return DSFIDFactory.create(in.readShort(), in);
      case DS_FIXED_ID_INT:
        return DSFIDFactory.create(in.readInt(), in);
      case DS_NO_FIXED_ID:
        return readDataSerializableFixedID(in);
      case NULL:
        return null;
      case NULL_STRING:
      case STRING:
      case HUGE_STRING:
      case STRING_BYTES:
      case HUGE_STRING_BYTES:
        return readString(in, header);
      case CLASS:
        return readClass(in);
      case DATE:
        return readDate(in);
      case FILE:
        return readFile(in);
      case INET_ADDRESS:
        return readInetAddress(in);
      case BOOLEAN:
        return readBoolean(in);
      case CHARACTER:
        return readCharacter(in);
      case BYTE:
        return readByte(in);
      case SHORT:
        return readShort(in);
      case INTEGER:
        return readInteger(in);
      case LONG:
        return readLong(in);
      case FLOAT:
        return readFloat(in);
      case DOUBLE:
        return readDouble(in);
      case BYTE_ARRAY:
        return readByteArray(in);
      case ARRAY_OF_BYTE_ARRAYS:
        return readArrayOfByteArrays(in);
      case SHORT_ARRAY:
        return readShortArray(in);
      case STRING_ARRAY:
        return readStringArray(in);
      case INT_ARRAY:
        return readIntArray(in);
      case LONG_ARRAY:
        return readLongArray(in);
      case FLOAT_ARRAY:
        return readFloatArray(in);
      case DOUBLE_ARRAY:
        return readDoubleArray(in);
      case BOOLEAN_ARRAY:
        return readBooleanArray(in);
      case CHAR_ARRAY:
        return readCharArray(in);
      case OBJECT_ARRAY:
        return readObjectArray(in);
      case ARRAY_LIST:
        return readArrayList(in);
      case LINKED_LIST:
        return readLinkedList(in);
      case HASH_SET:
        return readHashSet(in);
      case LINKED_HASH_SET:
        return readLinkedHashSet(in);
      case HASH_MAP:
        return readHashMap(in);
      case IDENTITY_HASH_MAP:
        return readIdentityHashMap(in);
      case HASH_TABLE:
        return readHashtable(in);
      case CONCURRENT_HASH_MAP:
        return readConcurrentHashMap(in);
      case PROPERTIES:
        return readProperties(in);
      case TIME_UNIT:
        return readTimeUnit(in);
      case USER_CLASS:
        return readUserObject(in, in.readByte());
      case USER_CLASS_2:
        return readUserObject(in, in.readShort());
      case USER_CLASS_4:
        return readUserObject(in, in.readInt());
      case VECTOR:
        return readVector(in);
      case STACK:
        return readStack(in);
      case TREE_MAP:
        return readTreeMap(in);
      case TREE_SET:
        return readTreeSet(in);
      case BOOLEAN_TYPE:
        return Boolean.TYPE;
      case CHARACTER_TYPE:
        return Character.TYPE;
      case BYTE_TYPE:
        return Byte.TYPE;
      case SHORT_TYPE:
        return Short.TYPE;
      case INTEGER_TYPE:
        return Integer.TYPE;
      case LONG_TYPE:
        return Long.TYPE;
      case FLOAT_TYPE:
        return Float.TYPE;
      case DOUBLE_TYPE:
        return Double.TYPE;
      case VOID_TYPE:
        return Void.TYPE;

      case USER_DATA_SERIALIZABLE:
        return readUserDataSerializable(in, in.readByte());
      case USER_DATA_SERIALIZABLE_2:
        return readUserDataSerializable(in, in.readShort());
      case USER_DATA_SERIALIZABLE_4:
        return readUserDataSerializable(in, in.readInt());

      case DATA_SERIALIZABLE:
        return readDataSerializable(in);

      case SERIALIZABLE: {
        final boolean isDebugEnabled_SERIALIZER = logger.isTraceEnabled(LogMarker.SERIALIZER);
        Object serializableResult;
        if (in instanceof DSObjectInputStream) {
          serializableResult = ((DSObjectInputStream) in).readObject();
        } else {
          InputStream stream;
          if (in instanceof InputStream) {
            stream = (InputStream) in;
          } else {
            stream = new InputStream() {
              @Override
              public int read() throws IOException {
                try {
                  return in.readUnsignedByte(); // fix for bug 47249
                } catch (EOFException ignored) {
                  return -1;
                }
              }

            };
          }

          ObjectInput ois = new DSObjectInputStream(stream);
          serializationFilter.setFilterOn((ObjectInputStream) ois);
          if (stream instanceof VersionedDataStream) {
            Version v = ((VersionedDataStream) stream).getVersion();
            if (v != null && v != Version.CURRENT) {
              ois = new VersionedObjectInput(ois, v);
            }
          }

          serializableResult = ois.readObject();

          if (isDebugEnabled_SERIALIZER) {
            logger.trace(LogMarker.SERIALIZER, "Read Serializable object: {}", serializableResult);
          }
        }
        if (isDebugEnabled_SERIALIZER) {
          logger.trace(LogMarker.SERIALIZER, "deserialized instanceof {}",
              serializableResult.getClass());
        }
        return serializableResult;
      }
      case PDX:
        return readPdxSerializable(in);
      case PDX_ENUM:
        return readPdxEnum(in);
      case GEMFIRE_ENUM:
        return readGemFireEnum(in);
      case PDX_INLINE_ENUM:
        return readPdxInlineEnum(in);
      case BIG_INTEGER:
        return readBigInteger(in);
      case BIG_DECIMAL:
        return readBigDecimal(in);
      case UUID:
        return readUUID(in);
      case TIMESTAMP:
        return readTimestamp(in);
      default:
        String s = "Unknown header byte: " + header;
        throw new IOException(s);
    }
  }

  private static Object readUserDataSerializable(final DataInput in, int classId)
      throws IOException {
    Instantiator instantiator = InternalInstantiator.getInstantiator(classId);
    if (instantiator == null) {
      logger.error(LogMarker.SERIALIZER,
          LocalizedMessage.create(
              LocalizedStrings.DataSerializer_NO_INSTANTIATOR_HAS_BEEN_REGISTERED_FOR_CLASS_WITH_ID_0,
              classId));
      throw new IOException(
          LocalizedStrings.DataSerializer_NO_INSTANTIATOR_HAS_BEEN_REGISTERED_FOR_CLASS_WITH_ID_0
              .toLocalizedString(classId));

    } else {
      try {
        DataSerializable ds;
        if (instantiator instanceof CanonicalInstantiator) {
          CanonicalInstantiator ci = (CanonicalInstantiator) instantiator;
          ds = ci.newInstance(in);
        } else {
          ds = instantiator.newInstance();
        }
        ds.fromData(in);
        return ds;

      } catch (Exception ex) {
        throw new SerializationException(
            LocalizedStrings.DataSerializer_COULD_NOT_DESERIALIZE_AN_INSTANCE_OF_0
                .toLocalizedString(instantiator.getInstantiatedClass().getName()),
            ex);
      }
    }
  }

  private static final ThreadLocal<Boolean> pdxSerializationInProgress = new ThreadLocal<>();

  public static boolean isPdxSerializationInProgress() {
    Boolean v = pdxSerializationInProgress.get();
    return v != null && v;
  }

  public static void setPdxSerializationInProgress(boolean v) {
    if (v) {
      pdxSerializationInProgress.set(true);
    } else {
      pdxSerializationInProgress.set(false);
    }
  }

  public static boolean writePdx(DataOutput out, InternalCache internalCache, Object pdx,
      PdxSerializer pdxSerializer) throws IOException {
    TypeRegistry tr = null;
    if (internalCache != null) {
      tr = internalCache.getPdxRegistry();
    }

    PdxOutputStream os;
    if (out instanceof HeapDataOutputStream) {
      os = new PdxOutputStream((HeapDataOutputStream) out);
    } else {
      os = new PdxOutputStream();
    }
    PdxWriterImpl writer = new PdxWriterImpl(tr, pdx, os);

    try {
      if (pdxSerializer != null) {
        // Hack to make sure we don't pass internal objects to the user's
        // serializer
        if (isGemfireObject(pdx)) {
          return false;
        }
        if (is662SerializationEnabled()) {
          boolean alreadyInProgress = isPdxSerializationInProgress();
          if (!alreadyInProgress) {
            setPdxSerializationInProgress(true);
            try {
              if (!pdxSerializer.toData(pdx, writer)) {
                return false;
              }
            } finally {
              setPdxSerializationInProgress(false);
            }
          } else {
            if (!pdxSerializer.toData(pdx, writer)) {
              return false;
            }
          }
        } else {
          if (!pdxSerializer.toData(pdx, writer)) {
            return false;
          }
        }
      } else {
        if (is662SerializationEnabled()) {
          boolean alreadyInProgress = isPdxSerializationInProgress();
          if (!alreadyInProgress) {
            setPdxSerializationInProgress(true);
            try {
              ((PdxSerializable) pdx).toData(writer);
            } finally {
              setPdxSerializationInProgress(false);
            }
          } else {
            ((PdxSerializable) pdx).toData(writer);
          }
        } else {
          ((PdxSerializable) pdx).toData(writer);
        }
      }
    } catch (ToDataException | CancelException | NonPortableClassException
        | GemFireRethrowable ex) {
      throw ex;
    } catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    } catch (Throwable t) {
      // Whenever you catch Error or Throwable, you must also
      // catch VirtualMachineError (see above). However, there is
      // _still_ a possibility that you are dealing with a cascading
      // error condition, so you also need to check to see if the JVM
      // is still usable:
      SystemFailure.checkFailure();
      if (pdxSerializer != null) {
        throw new ToDataException("PdxSerializer failed when calling toData on " + pdx.getClass(),
            t);
      } else {
        throw new ToDataException("toData failed on PdxSerializable " + pdx.getClass(), t);
      }
    }
    int bytesWritten = writer.completeByteStreamGeneration();
    getDMStats(internalCache).incPdxSerialization(bytesWritten);
    if (!(out instanceof HeapDataOutputStream)) {
      writer.sendTo(out);
    }
    return true;
  }

  public static DMStats getDMStats(InternalCache internalCache) {
    if (internalCache != null) {
      return internalCache.getDistributionManager().getStats();
    } else {
      DMStats result = InternalDistributedSystem.getDMStats();
      if (result == null) {
        result = new LonerDistributionManager.DummyDMStats();
      }
      return result;
    }
  }

  private static Object readPdxSerializable(final DataInput in)
      throws IOException, ClassNotFoundException {
    int len = in.readInt();
    int typeId = in.readInt();

    InternalCache internalCache = GemFireCacheImpl
        .getForPdx("PDX registry is unavailable because the Cache has been closed.");
    PdxType pdxType = internalCache.getPdxRegistry().getType(typeId);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "readPdxSerializable pdxType={}", pdxType);
    }
    if (pdxType == null) {
      throw new IllegalStateException("Unknown pdx type=" + typeId);
    }

    DMStats dmStats = getDMStats(internalCache);
    dmStats.incPdxDeserialization(len + 9);

    // check if PdxInstance needs to be returned.
    if (pdxType.getNoDomainClass() || internalCache.getPdxReadSerializedByAnyGemFireServices()) {
      dmStats.incPdxInstanceCreations();
      return new PdxInstanceImpl(pdxType, in, len);
    } else {
      PdxReaderImpl pdxReader = new PdxReaderImpl(pdxType, in, len);
      return pdxReader.getObject();
    }
  }

  /**
   * Reads a PdxInstance from dataBytes and returns it. If the first object read is not pdx encoded
   * returns null.
   */
  public static PdxInstance readPdxInstance(final byte[] dataBytes, InternalCache internalCache) {
    try {
      byte type = dataBytes[0];
      if (type == PDX) {
        PdxInputStream in = new PdxInputStream(dataBytes);
        in.readByte(); // throw away the type byte
        int len = in.readInt();
        int typeId = in.readInt();
        PdxType pdxType = internalCache.getPdxRegistry().getType(typeId);
        if (pdxType == null) {
          throw new IllegalStateException("Unknown pdx type=" + typeId);
        }

        return new PdxInstanceImpl(pdxType, in, len);
      } else if (type == DSCODE.PDX_ENUM) {
        PdxInputStream in = new PdxInputStream(dataBytes);
        in.readByte(); // throw away the type byte
        int dsId = in.readByte();
        int tmp = readArrayLength(in);
        int enumId = dsId << 24 | tmp & 0xFFFFFF;
        TypeRegistry tr = internalCache.getPdxRegistry();
        EnumInfo ei = tr.getEnumInfoById(enumId);
        if (ei == null) {
          throw new IllegalStateException("Unknown pdx enum id=" + enumId);
        }
        return ei.getPdxInstance(enumId);
      } else if (type == DSCODE.PDX_INLINE_ENUM) {
        PdxInputStream in = new PdxInputStream(dataBytes);
        in.readByte(); // throw away the type byte
        String className = DataSerializer.readString(in);
        String enumName = DataSerializer.readString(in);
        int enumOrdinal = InternalDataSerializer.readArrayLength(in);
        return new PdxInstanceEnum(className, enumName, enumOrdinal);
      }
    } catch (IOException ignore) {
    }
    return null;
  }

  public static int getLoadedDataSerializers() {
    return idsToSerializers.size();
  }

  public static Map getDsClassesToHoldersMap() {
    return dsClassesToHolders;
  }

  public static Map getIdsToHoldersMap() {
    return idsToHolders;
  }

  public static Map getSupportedClassesToHoldersMap() {
    return supportedClassesToHolders;
  }

  /**
   * A marker object for {@code DataSerializer}s that have not been registered. Using this marker
   * object allows us to asynchronously send {@code DataSerializer} registration updates. If the
   * serialized bytes arrive at a VM before the registration message does, the deserializer will
   * wait an amount of time for the registration message to arrive.
   */
  abstract static class Marker {
    /** The DataSerializer that is filled in upon registration */
    protected DataSerializer serializer = null;

    /** set to true once setSerializer is called. */
    boolean hasBeenSet = false;

    abstract DataSerializer getSerializer();

    /**
     * Sets the serializer associated with this marker. It will notify any threads that are waiting
     * for the serializer to be registered.
     */
    void setSerializer(DataSerializer serializer) {
      synchronized (this) {
        this.hasBeenSet = true;
        this.serializer = serializer;
        this.notifyAll();
      }
    }
  }
  /**
   * A marker object for {@code DataSerializer}s that have not been registered. Using this marker
   * object allows us to asynchronously send {@code DataSerializer} registration updates. If the
   * serialized bytes arrive at a VM before the registration message does, the deserializer will
   * wait an amount of time for the registration message to arrive. Made public for unit test
   * access.
   *
   * @since GemFire 5.7
   */
  public static class GetMarker extends Marker {
    /**
     * Number of milliseconds to wait. Also used by InternalInstantiator. Note that some tests set
     * this to a small amount to speed up failures. Made public for unit test access.
     */
    public static int WAIT_MS = Integer.getInteger(
        DistributionConfig.GEMFIRE_PREFIX + "InternalDataSerializer.WAIT_MS", 60 * 1000);

    /**
     * Returns the serializer associated with this marker. If the serializer has not been registered
     * yet, then this method will wait until the serializer is registered. If this method has to
     * wait for too long, then {@code null} is returned.
     */
    @Override
    DataSerializer getSerializer() {
      synchronized (this) {
        boolean firstTime = true;
        long endTime = 0;
        while (!this.hasBeenSet) {
          if (firstTime) {
            firstTime = false;
            endTime = System.currentTimeMillis() + WAIT_MS;
          }
          try {
            long remainingMs = endTime - System.currentTimeMillis();
            if (remainingMs > 0) {
              this.wait(remainingMs); // spurious wakeup ok
            } else {
              // timed out call setSerializer just to make sure that anyone else
              // also waiting on this marker times out also
              setSerializer(null);
              break;
            }
          } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
            // Just return null, let it fail
            return null;
          }
        }
        return this.serializer;
      }
    }
  }

  /**
   * A marker object for {@code DataSerializer}s that is in the process of being registered. It is
   * possible for getSerializer to return {@code null}
   *
   * @since GemFire 5.7
   */
  static class InitMarker extends Marker {
    /*
     * Returns the serializer associated with this marker. If the serializer has not been registered
     * yet, then this method will wait until the serializer is registered. If this method has to
     * wait for too long, then {@code null} is returned.
     */
    /**
     * Returns the serializer associated with this marker. Waits forever (unless interrupted) for it
     * to be initialized. Returns null if this Marker failed to initialize.
     */
    @Override
    DataSerializer getSerializer() {
      synchronized (this) {
        while (!this.hasBeenSet) {
          try {
            this.wait(); // spurious wakeup ok
          } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
            // Just return null, let it fail
            return null;
          }
        }
        return this.serializer;
      }
    }
  }
  /**
   * A distribution message that alerts other members of the distributed cache of a new
   * {@code DataSerializer} being registered.
   */
  public static class RegistrationMessage extends SerialDistributionMessage {
    /**
     * The id of the {@code DataSerializer} that was registered since 5.7 an int instead of a byte
     */
    private int id;

    /**
     * The eventId of the {@code DataSerializer} that was registered
     */
    protected EventID eventId;

    /** The name of the {@code DataSerializer} class */
    private String className;

    /** The versions in which this message was modified */
    private static final Version[] dsfidVersions = new Version[] {};

    /**
     * Constructor for {@code DataSerializable}
     */
    public RegistrationMessage() {}

    /**
     * Creates a new {@code RegistrationMessage} that broadcasts that the given
     * {@code DataSerializer} was registered.
     */
    public RegistrationMessage(DataSerializer s) {
      this.className = s.getClass().getName();
      this.id = s.getId();
      this.eventId = (EventID) s.getEventId();
    }

    static String getFullMessage(Throwable t) {
      StringBuffer sb = new StringBuffer();
      getFullMessage(sb, t);
      return sb.toString();
    }

    private static void getFullMessage(StringBuffer sb, Throwable t) {
      if (t.getMessage() != null) {
        sb.append(t.getMessage());
      } else {
        sb.append(t.getClass());
      }
      if (t.getCause() != null) {
        sb.append(" caused by: ");
        getFullMessage(sb, t.getCause());
      }
    }

    @Override
    protected void process(ClusterDistributionManager dm) {
      if (CacheClientNotifier.getInstance() != null) {
        // This is a server so we need to send the dataserializer to clients
        // right away. For that we need to load the class as the constructor of
        // ClientDataSerializerMessage requires list of supported classes.
        Class<?> c;
        try {
          c = getCachedClass(this.className); // fix for bug 41206
        } catch (ClassNotFoundException ex) {
          // fixes bug 44112
          logger.warn(
              "Could not load data serializer class {} so both clients of this server and this server will not have this data serializer. Load failed because: {}",
              this.className, getFullMessage(ex));
          return;
        }
        DataSerializer s;
        try {
          s = newInstance(c);
        } catch (IllegalArgumentException ex) {
          // fixes bug 44112
          logger.warn(
              "Could not create an instance of data serializer for class {} so both clients of this server and this server will not have this data serializer. Create failed because: {}",
              this.className, getFullMessage(ex));
          return;
        }
        s.setEventId(this.eventId);
        try {
          InternalDataSerializer._register(s, false);
        } catch (IllegalArgumentException ex) {
          logger.warn(
              "Could not register data serializer for class {} so both clients of this server and this server will not have this data serializer. Registration failed because: {}",
              this.className, getFullMessage(ex));
        } catch (IllegalStateException ex) {
          logger.warn(
              "Could not register data serializer for class {} so both clients of this server and this server will not have this data serializer. Registration failed because: {}",
              this.className, getFullMessage(ex));
        }
      } else {
        try {
          InternalDataSerializer.register(this.className, false, this.eventId, null, this.id);
        } catch (IllegalArgumentException ex) {
          logger.warn(
              "Could not register data serializer for class {} so it will not be available in this JVM. Registration failed because: {}",
              this.className, getFullMessage(ex));
        } catch (IllegalStateException ex) {
          logger.warn(
              "Could not register data serializer for class {} so it will not be available in this JVM. Registration failed because: {}",
              this.className, getFullMessage(ex));
        }
      }
    }

    @Override
    public int getDSFID() {
      return IDS_REGISTRATION_MESSAGE;
    }

    @Override
    public void toData(DataOutput out) throws IOException {
      super.toData(out);
      DataSerializer.writeNonPrimitiveClassName(this.className, out);
      out.writeInt(this.id);
      DataSerializer.writeObject(this.eventId, out);
    }

    @Override
    public void fromData(DataInput in) throws IOException, ClassNotFoundException {
      super.fromData(in);
      InternalDataSerializer.checkIn(in);
      this.className = DataSerializer.readNonPrimitiveClassName(in);
      this.id = in.readInt();
      this.eventId = (EventID) DataSerializer.readObject(in);
    }

    @Override
    public String toString() {
      return LocalizedStrings.InternalDataSerializer_REGISTER_DATASERIALIZER_0_OF_CLASS_1
          .toLocalizedString(this.id, this.className);
    }

    @Override
    public Version[] getSerializationVersions() {
      return dsfidVersions;
    }
  }

  /**
   * A listener whose listener methods are invoked when {@link DataSerializer}s and
   * {@link Instantiator}s are registered. This is part of the fix for bug 31422.
   *
   * @see InternalDataSerializer#addRegistrationListener
   * @see InternalDataSerializer#removeRegistrationListener
   */
  public interface RegistrationListener {

    /**
     * Invoked when a new {@code Instantiator} is {@linkplain Instantiator#register(Instantiator)
     * registered}.
     */
    void newInstantiator(Instantiator instantiator);

    /**
     * Invoked when a new {@code DataSerializer} is {@linkplain DataSerializer#register(Class)
     * registered}.
     */
    void newDataSerializer(DataSerializer ds);
  }

  /**
   * An {@code ObjectInputStream} whose {@link #resolveClass} method loads classes from the current
   * context class loader.
   */
  private static class DSObjectInputStream extends ObjectInputStream {

    /**
     * Creates a new {@code DSObjectInputStream} that delegates its behavior to a given
     * {@code InputStream}.
     */
    DSObjectInputStream(InputStream stream) throws IOException {
      super(stream);
    }

    @Override
    protected Class resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException {

      String className = desc.getName();

      OldClientSupportService svc = getOldClientSupportService();
      if (svc != null) {
        className = svc.processIncomingClassName(className);
      }
      try {
        return getCachedClass(className);
      } catch (ClassNotFoundException ignored) {
        return super.resolveClass(desc);
      }
    }

    @Override
    protected Class resolveProxyClass(String[] interfaces)
        throws IOException, ClassNotFoundException {

      ClassLoader nonPublicLoader = null;
      boolean hasNonPublicInterface = false;

      // define proxy in class loader of non-public
      // interface(s), if any
      Class[] classObjs = new Class[interfaces.length];
      for (int i = 0; i < interfaces.length; i++) {
        Class cl = getCachedClass(interfaces[i]);
        if ((cl.getModifiers() & Modifier.PUBLIC) == 0) {
          if (hasNonPublicInterface) {
            if (nonPublicLoader != cl.getClassLoader()) {
              String s = "conflicting non-public interface class loaders";
              throw new IllegalAccessError(s);
            }

          } else {
            nonPublicLoader = cl.getClassLoader();
            hasNonPublicInterface = true;
          }
        }
        classObjs[i] = cl;
      }

      try {
        if (hasNonPublicInterface) {
          return Proxy.getProxyClass(nonPublicLoader, classObjs);
        } else {
          return ClassPathLoader.getLatest().getProxyClass(classObjs);
        }
      } catch (IllegalArgumentException e) {
        throw new ClassNotFoundException(null, e);
      }
    }
  }

  /**
   * Used to implement serialization code for the well known classes we support in DataSerializer.
   *
   * @since GemFire 5.7
   */
  protected abstract static class WellKnownDS extends DataSerializer {
    @Override
    public int getId() {
      // illegal for a customer to use but since our WellKnownDS is never registered
      // with this id it gives us one to use
      return 0;
    }

    @Override
    public Class[] getSupportedClasses() {
      // illegal for a customer to return null but we can do it since we never register
      // this serializer.
      return null;
    }

    @Override
    public Object fromData(DataInput in) throws IOException, ClassNotFoundException {
      throw new IllegalStateException(LocalizedStrings.SHOULDNT_INVOKE.toLocalizedString());
    }
    // subclasses need to implement toData
  }

  /**
   * Just like a WellKnownDS but its type is compatible with PDX.
   *
   */
  protected abstract static class WellKnownPdxDS extends WellKnownDS {
    // subclasses need to implement toData
  }

  public static void writeObjectArray(Object[] array, DataOutput out, boolean ensureCompatibility)
      throws IOException {
    InternalDataSerializer.checkOut(out);
    int length;
    if (array == null) {
      length = -1;
    } else {
      length = array.length;
    }
    InternalDataSerializer.writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER)) {
      logger.trace(LogMarker.SERIALIZER, "Writing Object array of length {}", length);
    }
    if (length >= 0) {
      writeClass(array.getClass().getComponentType(), out);
      for (int i = 0; i < length; i++) {
        basicWriteObject(array[i], out, ensureCompatibility);
      }
    }
  }

  // Variable Length long encoded as int in next 4 bytes
  private static final byte INT_VL = 126;

  // Variable Length long encoded as long in next 8 bytes
  private static final byte LONG_VL = 127;

  private static final int MAX_BYTE_VL = 125;

  /**
   * Write a variable length long the old way (pre 7.0). Use this only in contexts where you might
   * need to communicate with pre 7.0 members or files.
   */
  public static void writeVLOld(long data, DataOutput out) throws IOException {
    if (data < 0) {
      Assert.fail("Data expected to be >=0 is " + data);
    }
    if (data <= MAX_BYTE_VL) {
      out.writeByte((byte) data);
    } else if (data <= 0x7FFF) {
      // set the sign bit to indicate a short
      out.write(((int) data >>> 8 | 0x80) & 0xFF);
      out.write((int) data >>> 0 & 0xFF);
    } else if (data <= Integer.MAX_VALUE) {
      out.writeByte(INT_VL);
      out.writeInt((int) data);
    } else {
      out.writeByte(LONG_VL);
      out.writeLong(data);
    }
  }

  /**
   * Write a variable length long the old way (pre 7.0). Use this only in contexts where you might
   * need to communicate with pre 7.0 members or files.
   */
  public static long readVLOld(DataInput in) throws IOException {
    byte code = in.readByte();
    long result;
    if (code < 0) {
      // mask off sign bit
      result = code & 0x7F;
      result <<= 8;
      result |= in.readByte() & 0xFF;
    } else if (code <= MAX_BYTE_VL) {
      result = code;
    } else if (code == INT_VL) {
      result = in.readInt();
    } else if (code == LONG_VL) {
      result = in.readLong();
    } else {
      throw new IllegalStateException("unexpected variable length code=" + code);
    }
    return result;
  }

  /**
   * Encode a long as a variable length array.
   *
   * This method is appropriate for unsigned integers. For signed integers, negative values will
   * always consume 10 bytes, so it is recommended to use writeSignedVL instead.
   *
   * This is taken from the varint encoding in protobufs (BSD licensed). See
   * https://developers.google.com/protocol-buffers/docs/encoding
   */
  public static void writeUnsignedVL(long data, DataOutput out) throws IOException {
    while (true) {
      if ((data & ~0x7FL) == 0) {
        out.writeByte((int) data);
        return;
      } else {
        out.writeByte((int) data & 0x7F | 0x80);
        data >>>= 7;
      }
    }
  }

  /**
   * Decode a long as a variable length array.
   *
   * This is taken from the varint encoding in protobufs (BSD licensed). See
   * https://developers.google.com/protocol-buffers/docs/encoding
   */
  public static long readUnsignedVL(DataInput in) throws IOException {
    int shift = 0;
    long result = 0;
    while (shift < 64) {
      final byte b = in.readByte();
      result |= (long) (b & 0x7F) << shift;
      if ((b & 0x80) == 0) {
        return result;
      }
      shift += 7;
    }
    throw new GemFireIOException("Malformed variable length integer");
  }

  /**
   * Encode a signed long as a variable length array.
   *
   * This method is appropriate for signed integers. It uses zig zag encoding to so that negative
   * numbers will be represented more compactly. For unsigned values, writeUnsignedVL will be more
   * efficient.
   */
  public static void writeSignedVL(long data, DataOutput out) throws IOException {
    writeUnsignedVL(encodeZigZag64(data), out);
  }

  /**
   * Decode a signed long as a variable length array.
   *
   * This method is appropriate for signed integers. It uses zig zag encoding to so that negative
   * numbers will be represented more compactly. For unsigned values, writeUnsignedVL will be more
   * efficient.
   */
  public static long readSignedVL(DataInput in) throws IOException {
    return decodeZigZag64(readUnsignedVL(in));
  }

  /**
   * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be
   * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
   * to be varint encoded, thus always taking 10 bytes on the wire.)
   *
   * @param n An unsigned 64-bit integer, stored in a signed int because Java has no explicit
   *        unsigned support.
   * @return A signed 64-bit integer.
   *
   *         This is taken from the varint encoding in protobufs (BSD licensed). See
   *         https://developers.google.com/protocol-buffers/docs/encoding
   */
  private static long decodeZigZag64(final long n) {
    return n >>> 1 ^ -(n & 1);
  }

  /**
   * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be
   * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
   * to be varint encoded, thus always taking 10 bytes on the wire.)
   *
   * @param n A signed 64-bit integer.
   * @return An unsigned 64-bit integer, stored in a signed int because Java has no explicit
   *         unsigned support.
   *
   *         This is taken from the varint encoding in protobufs (BSD licensed). See
   *         https://developers.google.com/protocol-buffers/docs/encoding
   */
  private static long encodeZigZag64(final long n) {
    // Note: the right-shift must be arithmetic
    return n << 1 ^ n >> 63;
  }

  /* test only method */
  public static int calculateBytesForTSandDSID(int dsid) {
    HeapDataOutputStream out = new HeapDataOutputStream(4 + 8, Version.CURRENT);
    long now = System.currentTimeMillis();
    try {
      writeUnsignedVL(now, out);
      writeUnsignedVL(InternalDataSerializer.encodeZigZag64(dsid), out);
    } catch (IOException ignored) {
      return 0;
    }
    return out.size();
  }

  public static final boolean LOAD_CLASS_EACH_TIME =
      Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "loadClassOnEveryDeserialization");

  private static final CopyOnWriteHashMap<String, WeakReference<Class<?>>> classCache =
      LOAD_CLASS_EACH_TIME ? null : new CopyOnWriteHashMap<>();

  private static final Object cacheAccessLock = new Object();

  public static Class<?> getCachedClass(String p_className) throws ClassNotFoundException {
    String className = processIncomingClassName(p_className);
    if (LOAD_CLASS_EACH_TIME) {
      return ClassPathLoader.getLatest().forName(className);
    } else {
      Class<?> result = getExistingCachedClass(className);
      if (result == null) {
        // Do the forName call outside the sync to fix bug 46172
        result = ClassPathLoader.getLatest().forName(className);
        synchronized (cacheAccessLock) {
          Class<?> cachedClass = getExistingCachedClass(className);
          if (cachedClass == null) {
            classCache.put(className, new WeakReference<>(result));
          } else {
            result = cachedClass;
          }
        }
      }
      return result;
    }
  }

  private static Class<?> getExistingCachedClass(String className) {
    WeakReference<Class<?>> wr = classCache.get(className);
    Class<?> result = null;
    if (wr != null) {
      result = wr.get();
    }
    return result;
  }

  public static void flushClassCache() {
    if (classCache != null) {
      // Not locking classCache during clear as doing so causes a deadlock in the DeployedJar
      classCache.clear();
    }
  }
}
