IndexDefinition.java

/*
** Module   : IndexDefinition.java
** Abstract : A container for a table index metadata.
**
** Copyright (c) 2022-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 IAS 20221104 Created initial version.
** 002 OM  20230517 Fixed deserialization. Improved javadocs and local optimization.
*/

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

import static com.goldencode.util.NativeTypeSerializer.*; 
import java.io.*;
import java.util.*;
import java.util.stream.*;
import com.goldencode.p2j.persist.TableMapper.*;
import com.goldencode.p2j.util.*;

/**
 * Container for a table index metadata. Used to send the metadata of a
 * DMO to a remote side, when remote appserver calls are involved.
 */
public class IndexDefinition
implements Externalizable
{
   /** Flag for default (none) index attribute. */
   private static final int IA_NONE = 0;
   
   /** Flag for 'primary' / 'effective primary' index attribute. */
   private static final int IA_PRIMARY = 1;
   
   /** Flag for 'unique' index attribute. */
   private static final int IA_UNIQUE = 2;
   
   /** Flag for 'word' index attribute. */
   private static final int IA_WORD = 4;
   
   /** The legacy name of this index. */
   private String legacyName;
   
   /**
    * The collection of index attribute. It is a OR-ed combination of the following flags:
    * <ul>
    *    <li>{@code IA_PRIMARY} - determines if the index is a primary index: the one with the "primary"
    *          annotation or, if there is no such one, the first in the definition order;</li>
    *    <li>{@code IA_UNIQUE} - determines if the index is a unique index;</li>
    *    <li>{@code IA_WORD} - determines if the index is a word-index index.</li>
    * </ul>
    * The collection is serialized as a single byte because there are only 3 attributes. When/if we include
    * additional attributes and the 8 bits of the byte are not enough, we will use larger data type.
    */
   private int indexAttribute = IA_NONE; 
   
   /** Index components (fields with sorting directions). */
   private List<IndexComponentDefinition> components;
   
   /**
    * Default c'tor
    */
   public IndexDefinition()
   {
   }
   
   /**
    * Constructor. Creates an {@code IndexDefinition} staring from the {@code TableMapper} legacy data.
    * 
    * @param   lii
    *          ORM index info
    */
   public IndexDefinition(LegacyIndexInfo lii)
   {
      this.legacyName = lii.getLegacyName();
      indexAttribute = (lii.isEffectivePrimary() ? IA_PRIMARY : IA_NONE) |
                       (lii.isUnique()           ? IA_UNIQUE  : IA_NONE) |
                       (lii.isWord()             ? IA_WORD    : IA_NONE);
      this.components = lii.indexComponents().map(IndexComponentDefinition::new).collect(Collectors.toList());
   }
   
   /**
    * Add current index to the {@code TableBuilder}.
    *
    * @param   tb
    *          {@code TableBuilder} instance.
    */
   public void addTo(TempTableBuilder tb)
   {
      tb.addNewIndex(new character(legacyName),
                     new logical((indexAttribute & IA_UNIQUE) != 0),
                     new logical((indexAttribute & IA_PRIMARY) != 0),
                     new logical((indexAttribute & IA_WORD) != 0));
      for (int i = 0; i < components.size(); i++)
      {
         IndexComponentDefinition c = components.get(i);
         tb.addFieldToIndex(legacyName, c.fieldLegacyName, c.effectiveDescending ? "desc" : "asc");
      }
   }
   
   /**
    * Read the index definition from the specified input source.
    * 
    * @param   in
    *          The input source from which the index definition will be read.
    *
    * @throws  IOException
    *          In case of I/O errors.
    * @throws  ClassNotFoundException
    *          If the class of property could not be found/loaded.
    */
   @Override
   public void readExternal(ObjectInput in) 
   throws IOException, 
          ClassNotFoundException
   {
      legacyName = readString(in);
      indexAttribute = in.readByte(); // reading a single byte because there are only 3 attributes
      components = readList(in, () -> new IndexComponentDefinition());
   }
   
   /**
    * Send the index definition to the specified output destination.
    * 
    * @param    out
    *           The output destination to which the index definition will be sent.
    *
    * @throws   IOException
    *           In case of I/O errors.
    */
   @Override
   public void writeExternal(ObjectOutput out) 
   throws IOException
   {
      writeString(out, legacyName);
      out.writeByte(indexAttribute); // writing a single byte because there are only 3 attributes
      writeList(out, components);
   }
   
   /** A container with an index component information. */
   public static class IndexComponentDefinition
   implements Externalizable
   {
      /** The legacy name of the field participating in the index. */
      private String fieldLegacyName;
      
      /** Determines if this component has descending sorting. */
      private boolean effectiveDescending;
      
      /**
       * Default c'tor
       */
      public IndexComponentDefinition()
      {
      }
      
      /**
       * Constructor.
       *
       * @param lici
       *        DMO ORM index component info.
       */
      public IndexComponentDefinition(LegacyIndexComponentInfo lici)
      {
         this.fieldLegacyName = lici.fieldLegacyName;
         this.effectiveDescending = lici.effectiveDescending;
      }
      
      /**
       * Read the index definition from the specified input source.
       * 
       * @param   in
       *          The input source from which the index component definition will be read.
       *
       * @throws  IOException
       *          In case of I/O errors.
       * @throws  ClassNotFoundException
       *          If the class of property could not be found/loaded.
       */
      @Override
      public void readExternal(ObjectInput in) 
      throws IOException, 
             ClassNotFoundException
      {
         fieldLegacyName = readString(in);
         effectiveDescending = in.readByte() != 0;
      }
      
      /**
       * Send the index definition to the specified output destination.
       * 
       * @param    out
       *           The output destination to which the index component definition will be sent.
       *
       * @throws   IOException
       *           In case of I/O errors.
       */
      @Override
      public void writeExternal(ObjectOutput out) 
      throws IOException
      {
         writeString(out, fieldLegacyName);
         out.writeByte(effectiveDescending ? 1 : 0);
      }
   }
}