# RecordChangeEvent.java

    /*
    ** Module   : RecordChangeEvent.java
    ** Abstract : Event sent with DMO state change notifications
    **
    ** Copyright (c) 2004-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------------Description----------------------------------
    ** 001 ECF 20060719   @28120 Created initial version. Defines the event
    **                           sent with DMO state change notifications.
    ** 002 ECF 20060912   @29483 Fixed defect in constructor. Unsafe to make a
    **                           deep copy of snapshot DMO when DMO has been
    **                           deleted. If DMO has collections, Hibernate
    **                           will not be able to initialize them after
    **                           record has been deleted.
    ** 003 ECF 20061207   @31615 Support insert event type. Added isInsert()
    **                           method.
    ** 004 ECF 20070707   @34416 Optimized constructor. It is not necessary to
    **                           make a deep copy of the snapshot, since it is
    **                           already a copy of the original DMO.
    ** 005 ECF 20070926   @35245 Added isUpdate() method. Reports whether
    **                           event signifies a DMO update.
    ** 006 ECF 20071130   @36127 Integrated generics.
    ** 007 ECF 20080814   @39551 Added explicit type field and Type enum.
    **                           Previously, we divined the type of event from
    **                           its state, but this proved unreliable for
    **                           subclasses.
    ** 008 ECF 20090107   @41013 Added support for bulk delete events. Full
    **                           and partial deletes can be differentiated
    **                           from one another.
    ** 009 ECF 20200906          New ORM implementation.
    ** 010 TJD 20240123          Java 17 compatibility updates
    ** 011 CA  20250305          Added state-changed 'refresh buffers' event, to force reload of all buffers in
    **                           current context, for the specified DMO.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.persist.event;

    import java.io.*;
    import java.util.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.Record;

    /**
     * The event sent with DMO state change notifications.  Encapsulates
     * information about the event's source, the original DMO state, the current
     * DMO state, and which properties were modified.  Depending upon the type of
     * event (update, insert, or delete), some of these data may be missing.
     */
    public class RecordChangeEvent
    implements Serializable
    {
       /** Event types */
       public static enum Type
       implements Serializable
       {
          /** Record update event */
          UPDATE,

          /** Record insert event */
          INSERT,

          /** Record delete event */
          DELETE,

          /** Record bulk delete event;  all records deleted */
          FULL_BULK_DELETE,

          /** Record bulk delete event;  selected records deleted */
          RESTRICTED_BULK_DELETE,

          /**
           * Event raised after a direct JDBC access has been made on a table, to force refresh all buffers in
           * current session.
           */
          BUFFER_REFRESH;
       };

       /** Event type */
       private final Type type;

       /** Record buffer which generated this event */
       private final transient RecordBuffer source;

       /** Changed record, reflects current state of DMO */
       private final Record dmo;

       /** Snapshot of the original DMO, before any change was made */
       private final Record snapshot;

       /** Map of property names to extents (for extent fields) */
       private final Map<String, List<Integer>> properties;

       /**
        * Constructor.
        *
        * @param   type
        *          Event type.
        * @param   source
        *          Record buffer which originated the event.
        * @param   dmo
        *          <b>Post-modification</b> state of the affected DMO (in the
        *          case where the modification is the deletion of the DMO from
        *          the database, this will be the object instance which was
        *          deleted).
        * @param   snapshot
        *          A deep copy of the DMO which reflects the state of the DMO as
        *          it was first set into the record buffer.  For a newly created
        *          record, this will contain the primary key assigned to the
        *          record.  All other properties will be set to their default
        *          values.
        * @param   properties
        *          A map whose keys are the names of those properties which have
        *          been modified and whose values are the indexes at which those
        *          values are stored (indexed properties only).  For a simple
        *          property, the matching value will be <code>null</code>.
        */
       public RecordChangeEvent(Type type,
                                RecordBuffer source,
                                Record dmo,
                                Record snapshot,
                                Map<String, List<Integer>> properties)
       {
          this.type = type;
          this.source = source;
          this.dmo = dmo;
          this.snapshot = snapshot;
          this.properties = (properties != null ? Collections.unmodifiableMap(properties) : null);
       }

       /**
        * Get the record buffer which is the source of the change event.
        * <p>
        * Note:  this will be <code>null</code> if this event has been serialized
        * and deserialized;  RecordBuffer objects cannot travel.
        *
        * @return  Source record buffer.
        */
       public RecordBuffer getSource()
       {
          return source;
       }

       /**
        * Get the DMO type associated with this event.  This corresponds with the
        * DMO implementation class' fully qualified name.
        *
        * @return  DMO entity name.
        */
       public String getEntity()
       {
          return source.getEntityName();
       }

       /**
        * Get the record which represents the <b>post-modification</b> state of
        * the affected DMO (in the case where the modification is the deletion of
        * the DMO from the database, this will be the object instance which was
        * deleted).
        *
        * @return  Modified DMO.
        */
       public Record getDMO()
       {
          return dmo;
       }

       /**
        * Get a deep copy snapshot of the DMO which reflects the state of the DMO
        * as it was first set into the record buffer.  For a newly created
        * record, this will contain the primary key assigned to the record.  All
        * other properties will be set to their default values.
        *
        * @return  Snapshot of the unmodified DMO.
        */
       public Record getSnapshot()
       {
          return snapshot;
       }

       /**
        * Get a map whose keys are the names of those properties which have been modified and whose values are
        * lists of the indexes at which those values are stored (indexed properties only).  For a simple property,
        * the matching value will be {@code null}.  If the change represented by the event was a deletion of the
        * record, the map will be empty.
        *
        * @return  An unmodifiable map as described above.
        *
        * @see     #isDelete
        */
       public Map<String, List<Integer>> getProperties()
       {
          if (properties == null)
          {
             return Collections.emptyMap();
          }

          return properties;
       }

       /**
        * Does this event signify a record insert?
        *
        * @return  <code>true</code> if event represents record insert, else
        *          <code>false</code>.
        */
       public boolean isInsert()
       {
          return (type == Type.INSERT);
       }

       /**
        * Does this event signify a record delete?
        *
        * @return  <code>true</code> if event represents record delete, else
        *          <code>false</code>.
        */
       public boolean isDelete()
       {
          return (type == Type.DELETE);
       }

       /**
        * Does this event signify a bulk record delete?
        *
        * @return  <code>true</code> if event represents bulk record delete, else
        *          <code>false</code>.
        */
       public boolean isBulkDelete()
       {
          return (type == Type.FULL_BULK_DELETE || type == Type.RESTRICTED_BULK_DELETE);
       }

       /**
        * Does this event signify a <b>full</b> bulk record delete (all records in
        * a temp table with a given multiplex ID)?
        *
        * @return  <code>true</code> if event represents full bulk record delete,
        *          else <code>false</code>.
        */
       public boolean isFullBulkDelete()
       {
          return (type == Type.FULL_BULK_DELETE);
       }

       /**
        * Does this event signify a modification to one or more of a record's
        * properties?
        *
        * @return  <code>true</code> if event represents record update, else
        *          <code>false</code>.
        */
       public boolean isUpdate()
       {
          return (type == Type.UPDATE);
       }

       /**
        * Does this event signify a force buffer refresh, as JDBC direct access was used on the table?
        *
        * @return  <code>true</code> if event represents buffer refresh, else <code>false</code>.
        */
       public boolean isBufferRefresh()
       {
          return (type == Type.BUFFER_REFRESH);
       }

       /**
        * Report whether this change impacts only extent properties, or simple
        * properties as well.  This may be interesting to listeners who do not
        * care about updates to indexed properties.
        *
        * @return  <code>true</code> if change impacts only extent properties;
        *          <code>false</code> if change impacts any simple property in
        *          the affected DMO.
        */
       public boolean isExtentFieldChangeOnly()
       {
          Iterator<?> iter = getProperties().values().iterator();
          while (iter.hasNext())
          {
             if (iter.next() == null)
             {
                return false;
             }
          }

          return true;
       }
    }