# FieldInfo.java

    /*
    ** Module   : FieldInfo.java
    ** Abstract : Helper class for record buffer copy/compare operations
    **
    ** Copyright (c) 2014-2017, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ------------------------------Description------------------------------------
    ** 001 ECF 20140305 Created initial version.
    ** 002 ECF 20150909 Added '+' to valid characters; fixed parsing error; added getFullSpec method.
    */
    /*
    ** 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 com.goldencode.p2j.util.*;

    /**
     * A data structure which stores information about a field, parsed from a field specifier string
     * during construction. See the {@link #FieldInfo(String) constructor} for valid field specifier
     * forms. This is a helper class for buffer copy and compare operations performed by {@link
     * RecordBuffer}.
     */
    class FieldInfo
    {
       /** Valid, non-alphanumeric characters for a field specifier (beside '.', '[', and ']') */
       private static String VALID_CHARS = "+#$%&-_";

       /** Full spec */
       private final String fullSpec;

       /** Buffer name (optional) */
       private String bufferName = null;

       /** Field name (required) */
       private String fieldName = null;

       /** Zero-based extent field index (-1 if not an extent field) */
       private int extentIndex = -1;

       /**
        * Constructor which breaks a legacy field specification into its constituent parts. These
        * consist of an optional buffer name, a required field name, and an optional extent value.
        *
        * @param   fieldSpec
        *          A legacy field specifier. This can take the following forms:
        *          <ul>
        *          <li><code>buffer.field[extent]</code></li>
        *          <li><code>buffer.field</code></li>
        *          <li><code>field[extent]</code></li>
        *          <li><code>field</code></li>
        *          </ul>
        *
        * @throws  ErrorConditionException
        *          if <code>fieldSpec</code> includes an extent index specifier that is invalid (and
        *          we are not in silent error mode).
        * @throws  IllegalArgumentException
        *          if <code>fieldSpec</code> is otherwise malformed, or we have the extent index
        *          error in silent error mode (which, btw, should not be a valid state).
        */
       FieldInfo(String fieldSpec)
       {
          fullSpec = fieldSpec.toLowerCase();
          StringBuilder bld = new StringBuilder();
          int len = fullSpec.length();
          int openBracketPos = -1;
          for (int i = 0; i < len; i++)
          {
             char c = fullSpec.charAt(i);
             if (c == '.')
             {
                if (bufferName == null)
                {
                   if (bld.length() == 0)
                   {
                      throw new IllegalArgumentException("Invalid leading dot in field spec");
                   }

                   bufferName = bld.toString();
                   bld.setLength(0);
                }
                else
                {
                   // quite silly, but evidently the dot is not only a buffer/field delimiter, but
                   // also a valid character within a field name
                   bld.append(c);
                }

                continue;
             }

             if (c == '[')
             {
                if (fieldName == null && bld.length() > 0)
                {
                   fieldName = bld.toString();
                   bld.setLength(0);
                   openBracketPos = i;
                }
                else
                {
                   // this will create an invalid extent index, which we will handle later, to match
                   // Progress' behavior
                   bld.append(c);
                }

                continue;
             }

             if (c == ']')
             {
                if (extentIndex < 0 && bld.length() > 0)
                {
                   boolean error = false;
                   String extStr = bld.toString();
                   bld.setLength(0);
                   try
                   {
                      int val = Integer.parseInt(extStr);
                      extentIndex = val - 1; // convert from 1-based to 0-based index

                      if (extentIndex < 0)
                      {
                         error = true;
                      }
                   }
                   catch (NumberFormatException exc)
                   {
                      error = true;
                   }
                   if (error)
                   {
                      String spec = (openBracketPos < 0
                                     ? fieldSpec
                                     : fieldSpec.substring(0, openBracketPos));
                      String msg =
                         String.format("Array index %s for %s must be a constant positive integer",
                                       extStr,
                                       spec);
                      ErrorManager.recordOrThrowError(11864, msg, false);

                      throw new IllegalArgumentException(msg);
                   }
                }

                // Progress silently tolerates and ignores any number of closing brackets after the
                // first closing bracket
                continue;
             }

             if (!Character.isLetterOrDigit(c) && VALID_CHARS.indexOf(c) < 0)
             {
                throw new IllegalArgumentException(fieldSpec);
             }

             bld.append(c);
          }

          if (fieldName == null)
          {
             fieldName = bld.toString();
             bld.setLength(0);
          }

          if (fieldName.length() == 0)
          {
             // we should have a field name by now
             throw new IllegalArgumentException(fieldSpec);
          }

          if (bld.length() > 0)
          {
             // we had junk characters after we thought we were done parsing all useful information;
             // Progress appears to stuff everything after the buffer into the field name in this case
             // and to let downstream processing handle the errors
             extentIndex = -1;
             if (bufferName == null)
             {
                fieldName = fieldSpec;
             }
             else
             {
                fieldName = fieldSpec.substring(fieldSpec.lastIndexOf('.') + 1);
             }
          }
       }

       /**
        * Get the lower case full spec.
        *
        * @return  Full field spec passed into the constructor, lowercased.
        */
       String getFullSpec()
       {
          return fullSpec;
       }

       /**
        * Get lower case buffer name (if any).
        *
        * @return  Buffer name (may be <code>null</code>).
        */
       String getBufferName()
       {
          return bufferName;
       }

       /**
        * Get lower case field name.
        *
        * @return  Field name.
        */
       String getFieldName()
       {
          return fieldName;
       }

       /**
        * Get extent index, if any.
        *
        * @return  Zero-based extent index, or -1 if no extent spec was provided.
        */
       int getExtentIndex()
       {
          return extentIndex;
       }
    }