/*
 * 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.pdx;

import java.util.List;

import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.client.ClientCacheFactory;

/**
 * PdxInstance provides run time access to the fields of a PDX without deserializing the PDX.
 * Preventing deserialization saves time and memory and does not require the domain class. This
 * interface is implemented by GemFire. The PdxInstance implementation is a light weight wrapper
 * that simply refers to the raw bytes of the PDX that are kept in the cache. Applications can
 * choose to access PdxInstances instead of Java objects by configuring the Cache to prefer PDX
 * instances during deserialization. This can be done in <code>cache.xml</code> by setting the
 * attribute <code>read-serialized</code> to true on the <code>pdx</code> element. Or it can be done
 * programmatically using either the {@link CacheFactory#setPdxReadSerialized(boolean)
 * setPdxReadSerialized} or {@link ClientCacheFactory#setPdxReadSerialized(boolean) client
 * setPdxReadSerialized} method. Once this preference is configured, then any time deserialization
 * of a PDX is done it will deserialize into a PdxInstance.
 * <P>
 * PdxInstances are immutable. If you want to change one call {@link #createWriter}.
 * <p>
 * A PdxInstance's fields will always be those of the version it represents. So if you add a field
 * to your domain class you can end up with a PdxInstance for version 1 (that does not have the
 * field) and a PdxInstance for version 2. The PdxInstance for version 1 will not have the added
 * field and the PdxInstance for version 2 will have the field. This differs from deserialization of
 * a pdx back to a domain class. In that case if version 2 is deserializing version 1 PdxReader will
 * return a default value for the added field even though version 1 has no knowledge of it.
 *
 * @since GemFire 6.6
 */
public interface PdxInstance extends java.io.Serializable {

  /**
   * Return the full name of the class that this pdx instance represents.
   * An empty string will be returned if no class is associated with this instance.
   *
   * @return the name of the class that this pdx instance represents.
   * @since GemFire 6.6.2
   */
  String getClassName();

  /**
   * Returns true if this instance represents an enum. Enum's have a String field named "name" and
   * an int field named "ordinal". It is ok to cast a PdxInstance that represents an enum to
   * {@link java.lang.Comparable}. PdxInstances representing enums are not writable.
   *
   * @return true if this instance represents an enum.
   */
  boolean isEnum();

  /**
   * Deserializes and returns the domain object that this instance represents.
   * If this instance is one that never deserializes then getObject returns "this".
   *
   * @return the deserialized domain object or "this" if this instance never deserializes
   * @throws PdxSerializationException if the instance could not be deserialized
   */
  Object getObject();

  /**
   * Checks if the named field exists and returns the result.
   * <p>
   * This can be useful when writing code that handles more than one version of a PDX class.
   *
   * @param fieldName the name of the field to check
   * @return <code>true</code> if the named field exists; otherwise <code>false</code>
   */
  boolean hasField(String fieldName);

  /**
   * Return an unmodifiable list of the field names on this PdxInstance.
   *
   * @return an unmodifiable list of the field names on this PdxInstance
   */
  List<String> getFieldNames();

  /**
   * Checks if the named field was {@link PdxWriter#markIdentityField(String) marked} as an identity
   * field.
   * <p>
   * Note that if no fields have been marked then all the fields are used as identity fields even
   * though this method will return <code>false</code> since none of them have been <em>marked</em>.
   *
   * @param fieldName the name of the field to check
   * @return <code>true</code> if the named field exists and was marked as an identify field;
   *         otherwise <code>false</code>
   */
  boolean isIdentityField(String fieldName);

  /**
   * Reads the named field and returns its value. If the field does not exist <code>null</code> is
   * returned.
   * <p>
   * A <code>null</code> result indicates that the field does not exist or that it exists and its
   * value is currently <code>null</code>. The {@link #hasField(String) hasField} method can be used
   * to figure out which if these two cases is true.
   * <p>
   * If an Object[] is deserialized by this call then that array's component type will be
   * <code>Object.class</code> instead of the original class that the array had when it was
   * serialized. This is done so that PdxInstance objects can be added to the array.
   *
   * @param fieldName name of the field to read
   *
   * @return If this instance has the named field then the field's value is returned, otherwise
   *         <code>null</code> is returned.
   * @throws PdxSerializationException if the field could not be deserialized
   */
  Object getField(String fieldName);

  /**
   * Returns true if the given object is equals to this instance.
   * <p>
   * If <code>other</code> is not a PdxInstance then it is not equal to this instance. NOTE: Even if
   * <code>other</code> is the result of calling {@link #getObject()} it will not be equal to this
   * instance.
   * <p>
   * Otherwise equality of two PdxInstances is determined as follows:
   * <ol>
   * <li>The domain class name must be equal for both PdxInstances
   * <li>Each identity field must be equal.
   * </ol>
   * If the domain class is not the empty string then
   * if one of the instances does not have a field that the other one does
   * then equals will assume it has the field with a default value.
   * If a PdxInstance has marked identity fields using
   * {@link PdxWriter#markIdentityField(String) markIdentityField} then only the marked identity
   * fields are its identity fields. Otherwise all its fields are identity fields.
   * <P>
   * An identity field is equal if all the following are true:
   * <ol>
   * <li>The field name is equal.
   * <li>The field type is equal.
   * <li>The field value is equal.
   * </ol>
   * <P>
   * If a field's type is <code>OBJECT</code> then its value must be deserialized to determine if it
   * is equals. If the deserialized object is an array then
   * {@link java.util.Arrays#deepEquals(Object[], Object[]) deepEquals} is used to determine
   * equality. Otherwise {@link Object#equals(Object) equals} is used.
   * <P>
   * If a field's type is <code>OBJECT[]</code> then its value must be deserialized and
   * {@link java.util.Arrays#deepEquals(Object[], Object[]) deepEquals} is used to determine
   * equality.
   * <P>
   * For all other field types then the value does not need to be deserialized. Instead the
   * serialized raw bytes are compared and used to determine equality.
   * <P>
   * Note that any fields that have objects that do not override {@link Object#equals(Object)
   * equals} will cause equals to return false when you might have expected it to return true. The
   * only exceptions to this are those that call
   * {@link java.util.Arrays#deepEquals(Object[], Object[]) deepEquals} as noted above. You should
   * either override equals and hashCode in these cases or mark other fields as your identity
   * fields.
   *
   * @param other the other instance to compare to this.
   * @return <code>true</code> if this instance is equal to <code>other</code>.
   */
  boolean equals(Object other);

  /**
   * Generates a hashCode based on the identity fields of this PdxInstance.
   * <p>
   * If a PdxInstance has marked identity fields using {@link PdxWriter#markIdentityField(String)
   * markIdentityField} then only the marked identity fields are its identity fields. Otherwise all
   * its fields are identity fields.
   * <p>
   * If an identity field is of type <code>OBJECT</code> then it is deserialized. If the
   * deserialized object is an array then {@link java.util.Arrays#deepHashCode(Object[])
   * deepHashCode} is used. Otherwise {@link Object#hashCode() hashCode} is used.
   * <p>
   * If an identity field is of type <code>OBJECT[]</code> this it is deserialized and
   * {@link java.util.Arrays#deepHashCode(Object[]) deepHashCode} is used.
   * <p>
   * Otherwise the field is not deserialized and the raw bytes of its value are used to compute the
   * hash code.
   * <p>
   * The algorithm used to compute the hashCode is: hashCode = 1; foreach (field:
   * sortedIdentityFields()) { if (field.isDefaultValue()) continue; if (field.isArray()) { hashCode
   * = hashCode*31 + Arrays.deepHashCode(field); } else { hashCode = hashCode*31 + field.hashCode();
   * } } if (hashCode == 0) { hashCode = 1; }
   */
  int hashCode();

  /**
   * Prints out all of the identity fields of this PdxInstance.
   * <p>
   * If a PdxInstance has marked identity fields using {@link PdxWriter#markIdentityField(String)
   * markIdentityField} then only the marked identity fields are its identity fields. Otherwise all
   * its fields are identity fields.
   */
  String toString();

  /**
   * Creates and returns a {@link WritablePdxInstance} whose initial values are those of this
   * PdxInstance. This call returns a copy of the current field values so modifications made to the
   * returned value will not modify this PdxInstance.
   *
   * @return a {@link WritablePdxInstance}
   * @throws IllegalStateException if the PdxInstance is an enum.
   */
  WritablePdxInstance createWriter();


  /**
   * Returns false if this instance will never be deserialized to a domain class.
   * Instances that never deserialize can be created using
   * {@link PdxInstanceFactory#neverDeserialize} or by creating a factory with
   * an empty string as the class name.
   *
   * @return false if this instance will never be deserialized to a domain class.
   *
   * @since Geode 1.9
   */
  default boolean isDeserializable() {
    return false;
  }

}
