# ResourceIdHelper.java

    /*
    ** Module   : ResourceIdHelper.java
    ** Abstract : Facility to encapsulate semantics for Progress resource identifiers.
    **
    ** Copyright (c) 2013-2022, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------------Description---------------------------------------
    ** 001 HC  20131030 Created initial version, see issue #2183.
    ** 002 CA  20131219 Negative resource IDs must not be generated, as they are not valid resource
    **                  IDs in 4GL (thus a idFromString call may fail if resource ID is negative, even
    **                  if it is a real ID).
    ** 003 GES 20140115 Documentation changes, added comments and added a small feature to
    **                  idFromString().
    ** 004 CA  20190729 Changed nextId() to generate a 32-bit value - this is because of real-life
    **                  application code expecting these values to be 32-bit.
    ** 005 ECF 20200924 Performance improvement: attempt simple, fast parse of resource ID string before using
    **                  slower method.
    ** 006 GES 20200927 Added the definition for an invalid resource ID (0).
    **     CA  20210306 nextId() now computes always-incremental values, given a certain value.  Random IDs are
    **                  left only for com-handle resources.
    **     TJD 20220504 Java 11 compatibility minor changes
    */

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

    import java.math.BigInteger;
    import java.security.SecureRandom;

    /**
     * The class encapsulates semantics related to Progress resource identifiers,
     * see {@link handle#resourceId(WrappedResource)}.
     * <p>
     * The main areas of functionality implemented by this class include allocations
     * of new resource identifiers, string formatting and parsing.
     * <p>
     * A resource identifier takes two forms, numeric and textual. The numeric form is
     * represented by {@link Long} data type.
     * <p>
     * Internally, P2J can avoid the string representation of the resource ID and use it as a long
     * value. For this reason, the {@link #getNext()} API will never generate negative resource IDs,
     * as these are not valid legacy resource IDs (as calling {@link #idFromString} with a negative
     * resource ID will fail).
     */
    public class ResourceIdHelper
    {
       /** Constant denoting the unknown resource id */
       public static final Long UNKNOWN = null;

       /** Constant denoting the unknown resource id string */
       public static final String UNKNOWN_STRING = "?";

       /** Constant denoting the zero resource id */
       public static final Long ZERO = 0L;

       /** Invalid resource id. */
       public static final long INVALID_RESOURCE = 0L;

       /** The default format string for a resource identifier (WARNING: not compatible with Progress). */
       public static final String DEFAULT_FORMAT = ">>>>>>>>>>>>>>>>>>>9";

       /**
        * We are using Secure random number generator so the generated values
        * are uniformly distributed in the whole 64-bit range.
        */
       private static final SecureRandom rnd = new SecureRandom();

       /** Max long constant */
       private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);

       /**
        * Generates a valid resource identifier.
        * <p>
        * The returned value is a randomly generated positive 32bit number with no guarantee of
        * uniqueness. It is the responsibility of the caller to check uniqueness of the returned
        * value in the caller's context if required.
        * <p>
        * There is real-life production code which relies on the handle resource ID to be a 32-bit
        * value; this is because the IDs start from small values, and they are generated incrementally.
        * Because of this dependency, and until we need real 64-bit IDs, this method will always
        * return a positive 32-bit value.
        */
       public static Long getNext()
       {
          int num = 0;

          // zero doesn't denote a valid id
          while (num == 0)
          {
             num = rnd.nextInt();
          }

          // do not generate negative resource IDs.
          if (num < 0)
          {
             num = num + Integer.MAX_VALUE;
          }

          return Long.valueOf(num);
       }

       /**
        * Generates a valid resource identifier.
        * <p>
        * The returned value is a always incremental, and avoids negative or zero values.
        *
        * @param    start
        *           The current resource ID index.
        *
        * @return   See above.
        */
       public static Long getNext(long start)
       {
          long num = start + 1;

          if (num < 0)
          {
             num = num + Long.MAX_VALUE;
          }

          if (num == 0)
          {
             num = num + 1;
          }

          return num;
       }
       /**
        * Returns <code>true</code> if the supplied identifier represents
        * a zero resource identifier, <code>false</code> otherwise.
        *
        * @param   resourceId
        *          Resource identifier.
        */
       public static boolean isZero(Long resourceId)
       {
          return ZERO.equals(resourceId);
       }

       /**
        * Returns <code>true</code> if the supplied identifier represents
        * an unknown resource identifier, <code>false</code> otherwise.
        *
        * @param   resourceId
        *          Resource identifier.
        *
        */
       public static boolean isUnknown(Long resourceId)
       {
          return UNKNOWN == resourceId;
       }

       /**
        * Parses a resource id from its textual form into its numeric form. The number is parsed into
        * a 64-bit integer using Progress conventions, see {@link NumberType#parseDouble}.
        * <p>
        * The method returns {@code UNKNOWN} if the input is {@code null} or equal to "?" or
        * the method returns {@code ZERO} if the input contains a trimmed empty string or
        * the method returns {@code ZERO} if the parsed input is zero or less than zero or
        * the method throws {@link ErrorConditionException} if the input cannot be parsed to
        * an integer.
        *
        * @param    resourceId
        *           A string value representing a resource identifier.
        *
        * @return   A numeric resource id.
        *
        * @throws   ErrorConditionException
        *           If the input cannot be parsed into an integer, see method description
        *           for more details.
        */
       public static Long idFromString(String resourceId)
       throws ErrorConditionException
       {
          if (resourceId == null || resourceId.equals(UNKNOWN_STRING))
          {
             return UNKNOWN;
          }

          // we don't know why length > 255 is a special case in the 4GL, but it is
          int len = resourceId.length();
          if (len == 0 || len > 255)
          {
             return ZERO;
          }

          // try fast parse first; simplifying assumption vs Long.parseLong() is that string contains only
          // digits, nothing else
          long val = 0L;
          int x = -1;
          for (int i = 0, e = len - 1; i < len; i++, e--)
          {
             char c = resourceId.charAt(i);
             if (c >= '0' && c <='9')
             {
                x = c - '0';
             }
             else
             {
                x = -1;
                break;
             }

             val = val * 10 + x;
             if (val < 0L)
             {
                // overflow, bail
                ErrorManager.recordOrThrowError(78, "Value too large for integer");
                return null;
             }
          }

          if (x >= 0)
          {
             // if x is a non-negative integer, parsing finished successfully
             return val;
          }

          // if we've gotten here, the fast parse did not work because we encountered a non-digit character,
          // so now we have to do it the slower, more complete way

          // TODO: report two errors: "** Decimal number is too large. (536)" and
          //                          "** Value too large for integer. (78)"
          //    when the string exceeds 50 significant digits (i.e. not including numsign,
          //    white spaces, leading zeros, etc.).
          //    UPDATE: this would probably be obtained "for free" by utilizing a code path that
          //            runs through int64.setValue(String) which already has this same 2 error
          //            (536 then 78) behavior; parseDecimal() already does throw 536 and the
          //            int64.setValue() uses parseDecimal() and then if it gets a 536 it raises
          //            the 78 error; converted code tests should be run to confirm

          // normalize the string with Progress conventions
          String javaResourceId = NumberType.parseDecimal(resourceId, true);
          BigInteger num = new BigInteger(javaResourceId);

          // we only consider positive integers valid resource ids. this is safe because P2J will
          // never generate a negative resource ID
          if (num.signum() <= 0)
          {
             return ZERO;
          }

          // out of range of positive long?
          if (num.compareTo(MAX_LONG) > 0)
          {
             ErrorManager.recordOrThrowError(78, "Value too large for integer");
             return null;
          }

          return num.longValue();
       }

       /**
        * Formats the numeric resource identifier with the default format string,
        * {@link ResourceIdHelper#DEFAULT_FORMAT}.
        * See {@link #idToString(String, Long)} for more details.
        *
        * @param   resourceId
        *          Resource identifier.
        *
        * @return  the formatted resource identifier.
        */
       public static String idToString(Long resourceId)
       {
          return idToString(null, resourceId);
       }

       /**
        * Formats the numeric resource identifier with the supplied format string.
        * If this method receives a negative resource ID, an exception is thrown. Internally, P2J will
        * never use negative IDs.
        *
        * @param   format
        *          Format string.
        * @param   resourceId
        *          Resource identifier.
        *
        * @return  the formatted resource identifier.
        *
        * @throws  IllegalArgumentException
        *          If the resource ID is negative.
        */
       public static String idToString(String format, Long resourceId)
       {
          if (isUnknown(resourceId))
          {
             return UNKNOWN_STRING;
          }

          BigInteger num = BigInteger.valueOf(resourceId);

          if (num.signum() < 0)
          {
             throw new IllegalArgumentException("The resource ID can not be negative!");
          }

          return NumberType.toString(num.toString(), "", false, format == null ? DEFAULT_FORMAT : format, false);
       }
    }