ArrayAssigner.java
/*
** Module : ArrayAssigner.java
** Abstract : automates the assignment of each element of a BDT array
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 GES 20051104 @23229 Provides a simple helper to automate the assignment of each element
** of a BaseDataType array based on an array input.
** 002 GES 20060410 @25444 All elements can be set to the same scalar value (new signature).
** 003 GES 20060412 @25486 Added a setUnknown() method.
** 004 GES 20060425 @25707 Added a new assign() signature.
** 005 ECF 20060428 @25851 Added safety code. If target array element is null, duplicate
** corresponding source to initialize it.
** 006 GES 20080911 @39793 Added signatures to allow primitives to be assigned from.
** 007 GES 20090625 @42956 Fix and helpers to avoid ArrayIndexOutOfBounds. This implements a
** new delegation model to hide all array variable dereferencing (for
** assignment or simple access). The helpers can thus translate or
** avoid Java level AIOOB exceptions and behave the way that the 4GL
** requires.
** 008 CA 20130209 Added support for the EXTENT statement and output EXTENT parameters.
** 009 CA 20130315 Added missing assignSingle and assignMulti APIs, for to the cases
** when the rvalue is a resource, as in some cases the :HANDLE attribute
** conversion emits the WrappedResource instance instead of handle.
** 010 OM 20130418 Switched to long/int64 to support 64-bit wide indexes and values.
** 011 CA 20131013 Added no-op scopeDeleted() method, required by the changes in
** Scopeable interface.
** 012 GES 20140116 Made isDynamicArray() public.
** 013 HC 20140324 Fixed extent-to-extent assignment.
** 014 HC 20140413 Fixed extent parameter initialization.
** 015 HC 20140430 Fixed decimal precision for extent variables.
** 016 HC 20140613 Improved extent parameter support - removed some of
** the code being emitted and introduced multiple wrapper classes
** depending on the parameter type and the l-value type (variable vs.
** field).
** 017 CA 20151214 When resizing a shared extent variable, update the reference in the
** SharedVariableManager registries.
** 018 OM 20160706 Small fix in subscriptWorker() method.
** 019 CA 20160728 Fixed undoable support for dynamic-extent arrays: in this case, when
** the array has its extent set, all the elements must register for undo
** depending on the scope where the array was defined.
** 020 CA 20160812 Performance improvements for H019: use IdentityHashMap when using
** ext prog instances or BDT[] as keys, pass the tx level when
** registering dynamic extent arrays.
** 021 GES 20171207 Removed explicit bypass on pending error to match new silent error
** mode.
** 022 CA 20190122 Added runtime for object extent variables.
** CA 20190306 Do not delete the scope of a persistent procedure.
** CA 20190326 Implemented ProcedureHelper and TransactionHelper instead of direct
** usage of the static API. This allows the elimination of context
** local usage in ProcedureManager and TransactionManager.
** 023 CA 20190508 Added support for CASE-SENSITIVE extent var option.
** 024 CA 20190812 Fixed 'isObject' - check if the class is object or a subclass.
** 025 CA 20201003 Replaced Guava identity HashSet with Collections.newSetFromMap(IdentityHashMap).
** ME 20201009 Add new 'assignMulti' method with Object input to work with Class.invoke.
** 20201019 Save the object type reference on resize back to indeterminate array.
** 20210128 Register as dynamic when resize back to indeterminate array.
** CA 20210203 Fixed registration of extent variables (defined in a procedure/function) and
** extent parameters - they need to be registered as 'pending' and registered when
** the top-level scope is processed.
** CA 20210223 If a dynamic array parameter is resized by the top-level block code, then the
** reference in the parameter instance must be updated.
** CA 20210322 Fixed dynamic arrays used in persistent programs or OO instances.
** CA 20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
** initialized at the method's execution, and not at the caller's arguments.
** CA 20220901 Refactored scope notification support: ScopeableFactory was removed, and the
** registration is now specific to each type of scopeable. For each case, the block
** will be registered for scope support (for that particular scopeable) only when
** the scopeable is 'active' (i.e. unnamed streams or accumulators are used). This
** allows a lazy registration of scopeables, to avoid the unnecessary overhead of
** processing all the scopeables for each and every block.
** CA 20221010 Performance improvements - avoid the ProcedureData lookup, by keeping a parallel
** stack of this data for THIS-PROCEDURE. Refs #6826
** CA 20220524 Fixed issues with dynamic array registration.
** CA 20220601 Added 'assignMultiPoly', emitted when r-value is '::', DYNAMIC-INVOKE or
** DYNAMIC-FUNCTION.
** OM 20220604 Improved dereferenciation support.
** CA 20220609 When resizing a dynamic array back to zero, release or legacy object instances.
** reRegister must process the persistent procedure's dynamic arrays, too.
** CA 20220630 A small performance improvement.
** CA 20221010 Performance improvements - avoid the ProcedureData lookup, by keeping a parallel
** stack of this data for THIS-PROCEDURE. Refs #6826
** CA 20230110 Cache the helper for ProcedureManager, ObjectOps and others (as needed), to reduce
** context-local lookup.
** 026 EVL 20230301 Fix for invalid array index exception in assignSingleWorker(). Protection added.
** 027 CA 20230712 Added support to resize a 'jobject' array.
** 028 CA 20231026 A persistent procedure with a pending delete is no longer persistent, so
** scopeDeleted needs to be executed.
** CA 20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
** 029 CA 20231129 Improvements related to the management of various dequeues and collections (use
** empty set/map instead of creating a new instance, where possible).
** 030 CA 20240226 'copyOf' must create an array of ObjectVar if component type is 'object'.
** 031 DDF 20241001 Fix OO instance leak when a call is made with an input, extent, object argument.
** 032 CA 20240911 Added method to resize a dynamic extent field via deserialization of a legacy
** object.
** 033 ICP 20250123 Used integer, int64 and logical constants to leverage caches instances.
** 034 PBB 20250522 Added note explaining a weird case that seems to be a bug in 4gl.
*/
/*
** 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 com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.security.*;
import java.lang.reflect.*;
import java.util.*;
/**
* Provides a simple helper to automate the assignment of each element of a
* {@link BaseDataType} array based on 2 array inputs.
*/
public class ArrayAssigner
{
/** Context-local data container. */
private static ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
{
protected WorkArea initialValue()
{
WorkArea wa = new WorkArea();
return wa;
};
};
/**
* Assigns (mutates the content of) each matching element of a target
* array based on the corresponding element of the source array. The
* source and target arrays must be assignable to each other (must be
* assignment-compatible). The copy operation will stop as soon as the end
* of either array is encountered. If either array is longer than the other,
* then the copy will only occur for the overlapping portion. The copy
* always starts at element 0 and finishes at the lesser of source length or
* target length.
* <p>
* In case the assignment target represents an indeterminate extent variable
* and the source is a determinate extent or a fixed indeterminate extent
* the target is reallocated to the size of the source extent. The allocated
* reference is returned as the result from the function.
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The array from which to assign. Must not be <code>null</code>.
*
* @return The reference to the target array or a reference to a newly
* allocated array representing the assignment target.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static BaseDataType[] assignMultiPoly(BaseDataType[] target, Object source)
{
if (source instanceof BaseDataType)
{
assignMulti(target, (BaseDataType) source);
return target;
}
else if (source != null && source.getClass().isArray())
{
return assignMulti(target, ((BaseDataType[]) source));
}
else
{
throw new IllegalArgumentException("Source type " + (source == null ? "null" : source.getClass()) +
" is not supported!");
}
}
/**
* Assigns (mutates the content of) each matching element of a target
* array based on the corresponding element of the source array. The
* source and target arrays must be assignable to each other (must be
* assignment-compatible). The copy operation will stop as soon as the end
* of either array is encountered. If either array is longer than the other,
* then the copy will only occur for the overlapping portion. The copy
* always starts at element 0 and finishes at the lesser of source length or
* target length.
* <p>
* In case the assignment target represents an indeterminate extent variable
* and the source is a determinate extent or a fixed indeterminate extent
* the target is reallocated to the size of the source extent. The allocated
* reference is returned as the result from the function.
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The array from which to assign. Must not be <code>null</code>.
*
* @return The reference to the target array or a reference to a newly
* allocated array representing the assignment target.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static BaseDataType[] assignMulti(BaseDataType[] target, BaseDataType[] source)
throws ErrorConditionException,
NullPointerException
{
// This seems to be a 4GL error but strangely, when trying to assign to a variable
// declared as extent 1, when DYNAMIC-PROPERTY is supposed to return an extent of 1,
// progress throws this error. This behavior is currently intentionally omitted.
boolean dynamicTarget = isDynamicArray(target);
if (dynamicTarget &&
target.length > 0 &&
(source.length == 0 ||
source.length != target.length))
{
final String err = "Indeterminate extent is already fixed to a dimension of %d";
String errMsg = String.format(err, target.length);
ErrorManager.recordOrThrowError(13738, errMsg, false);
return target;
}
if (!dynamicTarget)
{
if (source.length == 0)
{
final String errMsg = "Uninitialized array used as source of assignment";
ErrorManager.recordOrThrowError(14906, errMsg, false);
return target;
}
else if (source.length != target.length)
{
final String errMsg =
"Whole-array assignment target and source must have the same extent unless " +
"the target is indeterminate";
ErrorManager.recordOrThrowError(14905, errMsg, false);
return target;
}
}
if (target.length == 0 && source.length > 0)
{
target = resize(target, source.length);
}
for (int i = 0; (i < target.length && i < source.length); i++)
{
if (target[i] == null)
{
target[i] = source[i].duplicate();
}
else
{
(target[i]).assign(source[i]);
}
}
return target;
}
/**
* Assigns (mutates the content of) each matching element of a target
* array based on the corresponding element of the source array.
*
* This is used for <code>invoke</code> method of Class or Call interface.
*
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The source array from which to assign. Must be an array of <code>BaseDataType</code>.
*
* @return The reference to the target array or a reference to a newly
* allocated array representing the assignment target.
*
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
* ErrorConditionException
* If the source is not an array of <code>BaseDataType</code>.
*/
public static BaseDataType[] assignMulti(BaseDataType[] target, Object source)
throws ErrorConditionException,
NullPointerException
{
if (source.getClass().isArray() && BaseDataType.class.isAssignableFrom(source.getClass().getComponentType())) {
return assignMulti(target, (BaseDataType[]) source);
} else {
throw new ErrorConditionException("Parameter is a non-array parameter");
}
}
/**
* Assigns (mutates the content of) all elements of a target array from
* the same source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The value from which to assign.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, long source)
throws ErrorConditionException,
NullPointerException
{
assignMulti(target, int64.of(source));
}
/**
* Assigns (mutates the content of) all elements of a target array from
* the same source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The value from which to assign.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, double source)
throws ErrorConditionException,
NullPointerException
{
assignMulti(target, new decimal(source));
}
/**
* Assigns (mutates the content of) all elements of a target array from
* the same source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The value from which to assign.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, String source)
throws ErrorConditionException,
NullPointerException
{
assignMulti(target, new character(source));
}
/**
* Assigns (mutates the content of) all elements of a target array from
* the same source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The value from which to assign.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, boolean source)
throws ErrorConditionException,
NullPointerException
{
assignMulti(target, logical.of(source));
}
/**
* Assigns (mutates the content of) all elements of a target array from
* the same source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, BaseDataType source)
throws ErrorConditionException,
NullPointerException
{
if (target.length == 0 && TransactionManager.blockInitialized())
{
// TODO: for some reason, this error message doesn't end in a dot in 4GL
String msg = "Invalid assignment to an unfixed indeterminate extent";
ErrorManager.recordOrThrowError(11389, msg, false);
return;
}
for (int i = 0; i < target.length; i++)
{
if (target[i] == null)
{
target[i] = source.duplicate();
}
else
{
(target[i]).assign(source);
}
}
}
/**
* Assigns (mutates the content of) all elements of a target array from
* the same source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, WrappedResource source)
throws ErrorConditionException,
NullPointerException
{
if (target.length == 0 && TransactionManager.blockInitialized())
{
// TODO: for some reason, this error message doesn't end in a dot in 4GL
String msg = "Invalid assignment to an unfixed indeterminate extent";
ErrorManager.recordOrThrowError(11389, msg, false);
return;
}
for (int i = 0; i < target.length; i++)
{
if (target[i] == null)
{
target[i] = new handle(source);
}
else
{
(target[i]).assign(new handle(source));
}
}
}
/**
* Assigns (mutates the content of) all elements of a target array starting
* at the given index through the end of the array, using the same source
* value. The source and target types must be assignable to each other
* (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param idx
* Starting index (0-based) for the assignments.
* @param source
* The value from which to assign.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, int idx, long source)
throws ErrorConditionException,
NullPointerException
{
assignMulti(target, idx, int64.of(source));
}
/**
* Assigns (mutates the content of) all elements of a target array starting
* at the given index through the end of the array, using the same source
* value. The source and target types must be assignable to each other
* (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param idx
* Starting index (0-based) for the assignments.
* @param source
* The value from which to assign.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, int idx, double source)
throws ErrorConditionException,
NullPointerException
{
assignMulti(target, idx, new decimal(source));
}
/**
* Assigns (mutates the content of) all elements of a target array starting
* at the given index through the end of the array, using the same source
* value. The source and target types must be assignable to each other
* (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param idx
* Starting index (0-based) for the assignments.
* @param source
* The value from which to assign.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, int idx, String source)
throws ErrorConditionException,
NullPointerException
{
assignMulti(target, idx, new character(source));
}
/**
* Assigns (mutates the content of) all elements of a target array starting
* at the given index through the end of the array, using the same source
* value. The source and target types must be assignable to each other
* (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param idx
* Starting index (0-based) for the assignments.
* @param source
* The value from which to assign.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, int idx, boolean source)
throws ErrorConditionException,
NullPointerException
{
assignMulti(target, idx, logical.of(source));
}
/**
* Assigns (mutates the content of) all elements of a target array starting
* at the given index through the end of the array, using the same source
* value. The source and target types must be assignable to each other
* (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param idx
* Starting index (0-based) for the assignments.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, int idx, BaseDataType source)
throws ErrorConditionException,
NullPointerException
{
for (int i = idx; i < target.length; i++)
{
if (target[i] == null)
{
target[i] = source.duplicate();
}
else
{
(target[i]).assign(source);
}
}
}
/**
* Assigns (mutates the content of) all elements of a target array starting
* at the given index through the end of the array, using the same source
* value. The source and target types must be assignable to each other
* (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param idx
* Starting index (0-based) for the assignments.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure of an individual assignment.
* @throws NullPointerException
* If either the source or the target arrays are
* <code>null</code>.
*/
public static void assignMulti(BaseDataType[] target, int idx, WrappedResource source)
throws ErrorConditionException,
NullPointerException
{
for (int i = idx; i < target.length; i++)
{
if (target[i] == null)
{
target[i] = new handle(source);
}
else
{
(target[i]).assign(new handle(source));
}
}
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, long index, long source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, int64.of(index), int64.of(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, long index, double source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, int64.of(index), new decimal(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, long index, boolean source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, int64.of(index), new logical(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, long index, String source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, int64.of(index), new character(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, long index, BaseDataType source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, int64.of(index), source);
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, long index, WrappedResource source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, int64.of(index), new handle(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array to the given source value.
* The source and target types must be assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by 1 and then used if it is in
* bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, long index, Object source)
throws ErrorConditionException,
NullPointerException
{
if (source instanceof BaseDataType)
{
assignSingleWorker(target, int64.of(index), (BaseDataType) source);
}
else if (source != null && source.getClass().isArray())
{
ErrorManager.recordOrThrowError(14905);
// Whole-array assignment target and source must have the same extent unless the target is indeterminate. (14905)
}
else
{
throw new IllegalArgumentException(
"Source type " + (source == null ? "null" : source.getClass()) + " is not supported!");
}
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, NumberType index, long source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, index, int64.of(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, NumberType index, double source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, index, new decimal(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, NumberType index, boolean source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, index, logical.of(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, NumberType index, String source)
throws ErrorConditionException,
NullPointerException
{
assignSingle(target, index, new character(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, NumberType index, BaseDataType source)
throws ErrorConditionException,
NullPointerException
{
assignSingleWorker(target, index, source);
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, NumberType index, WrappedResource source)
throws ErrorConditionException,
NullPointerException
{
assignSingleWorker(target, index, new handle(source));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingle(BaseDataType[] target, NumberType index, Object source)
throws ErrorConditionException,
NullPointerException
{
if (source instanceof BaseDataType)
{
assignSingleWorker(target, index, (BaseDataType) source);
}
else if (source != null && source.getClass().isArray())
{
ErrorManager.recordOrThrowError(14905);
// Whole-array assignment target and source must have the same extent unless the target is indeterminate. (14905)
}
else
{
throw new IllegalArgumentException(
"Source type " + (source == null ? "null" : source.getClass()) + " is not supported!");
}
}
/**
* Dereference the given array using the given 1-based index.
*
* @param array
* The array to dereference.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
*/
public static <B extends BaseDataType> B subscript(B[] array, long index)
{
int idx = subscriptHelper(array, index);
return subscriptWorker(array, idx);
}
/**
* Dereference the given array using the given 1-based index.
*
* @param array
* The array to dereference.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
*/
public static <B extends BaseDataType> B subscript(B[] array, NumberType index)
{
int idx = subscriptHelper(array, index, false);
return subscriptWorker(array, idx);
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the unknown value.
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingleUnknown(BaseDataType[] target, long index)
throws ErrorConditionException,
NullPointerException
{
assignSingleUnknown(target, int64.of(index));
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the unknown value.
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
public static void assignSingleUnknown(BaseDataType[] target, NumberType index)
throws ErrorConditionException,
NullPointerException
{
Class<? extends BaseDataType> cls =
(Class<? extends BaseDataType>) target.getClass().getComponentType();
assignSingleWorker(target, index, BaseDataType.generateUnknown(cls));
}
/**
* Sets all elements of a target array to the <code>unknown value</code>.
*
* @param target
* The array to be modified. Must not be <code>null</code>.
*/
public static void assignUnknown(BaseDataType[] target)
{
if (target.length == 0 && TransactionManager.blockInitialized())
{
// TODO: for some reason, this error message doesn't end in a dot in 4GL
String msg = "Invalid assignment to an unfixed indeterminate extent";
ErrorManager.recordOrThrowError(11389, msg, false);
return;
}
for (int i = 0; i < target.length; i++)
{
target[i].setUnknown();
}
}
/**
* Resize this Java-style array. In case the new size is greater than the array's size, the new elements
* will be initialized to a {@link jobject} with the {@link Object} type.
* <p>
* WARNING: {@link jobject} arrays are not compatible with EXTENT - they are not tracked by FWD runtime.
*
* @param <T>
* The jobject element type.
* @param array
* The array to be resized.
* @param newSize
* The new size.
*
* @return The new array reference.
*/
public static <T> jobject<T>[] resize(jobject<T>[] array, int newSize)
{
jobject<T>[] res = new jobject[newSize];
System.arraycopy(array, 0, res, 0, array.length);
for (int i = array.length; i < newSize; i++)
{
res[i] = new jobject<T>(Object.class);
}
return res;
}
/**
* Implement the EXTENT statement. This resizes the given array to the new size and returns
* the new instance.
* <p>
* If the given array is not of size zero, then an error is raised. Else, all the elements
* of the array will be set to default value.
*
* @param array
* The dynamic-extent array to be resized.
* @param newSize
* The array's new size.
*
* @return The resized array.
*/
public static <T extends BaseDataType> T[] resize(T[] array, int newSize)
{
return resize(array, integer.of(newSize), false);
}
/**
* Implement the EXTENT statement. This resizes the given array to the new size and returns
* the new instance.
* <p>
* If the given array is not of size zero, then an error is raised. Else, all the elements
* of the array will be set to default value.
*
* @param array
* The dynamic-extent array to be resized.
* @param newSize
* The array's new size.
*
* @return The resized array.
*/
public static <T extends BaseDataType> T[] resize(T[] array, int64 newSize)
{
return resize(array, newSize, false);
}
/**
* Implement the EXTENT statement. This resizes the given array to the new size and returns
* the new instance.
* <p>
* If the given array is not of size zero, then an error is raised. Else, all the elements
* of the array will be set to unknown or default value depending on the <code>initToUnknown</code>
* parameter.
*
* @param array
* The dynamic-extent array to be resized.
* @param newSize
* The array's new size.
* @param initToUnknown
* Initialization flag, see above for more info.
*
* @return The resized array.
*/
public static <T extends BaseDataType> T[] resize(T[] array, int newSize, boolean initToUnknown)
{
return resize(array, integer.of(newSize), initToUnknown);
}
/**
* Implement the EXTENT statement. This resizes the given array to the new size and returns
* the new instance.
* <p>
* If the given array is not of size zero (and the newSize is not unknown), then an error is
* raised. Else, the array will be resized and all the elements will be set to unknown or
* the default value depending on the <code>initToUnknown</code> parameter.
* <p>
* The new extent value must be from the [1..28000] interval.
*
* @param array
* The dynamic-extent array to be resized. Must not be null. If the array was not
* resized yet, must be 0-length.
* @param newSize
* The array's new size.
* @param initToUnknown
* Initialization flag, see above for more info.
*
* @return The resized array.
*/
@SuppressWarnings("unchecked")
public static <T extends BaseDataType> T[] resize(T[] array, int64 newSize, boolean initToUnknown)
{
return resize(array, newSize, initToUnknown, true);
}
/**
* Implement the EXTENT statement. This resizes the given array to the new size and returns
* the new instance.
* <p>
* If the given array is not of size zero (and the newSize is not unknown), then an error is
* raised. Else, the array will be resized and all the elements will be set to unknown or
* the default value depending on the <code>initToUnknown</code> parameter.
* <p>
* The new extent value must be from the [1..28000] interval.
*
* @param array
* The dynamic-extent array to be resized. Must not be null. If the array was not
* resized yet, must be 0-length.
* @param newSize
* The array's new size.
* @param initToUnknown
* Initialization flag, see above for more info.
* @param isVar
* Flag indicating to create {@link ObjectVar} elements instead of {@link object}.
*
* @return The resized array.
*/
@SuppressWarnings("unchecked")
static <T extends BaseDataType> T[] resize(T[] array, int64 newSize, boolean initToUnknown, boolean isVar)
{
long size = newSize.isUnknown() ? 0 : newSize.longValue();
if (!newSize.isUnknown())
{
if (array.length != 0)
{
final String msg = "Indeterminate extent is already fixed to a dimension of %d";
ErrorManager.recordOrThrowError(13738, String.format(msg, array.length), false);
return array;
}
if (size <= 0 || size > 28000)
{
//size < 1: Invalid value specified for the EXTENT statement. (13737)
//size > 28000: Array extent may not be greater than 28000 (3350)
final String msg = "Indeterminate extent dimension %d is out of range 1 to 28000";
ErrorManager.recordOrThrowError(11385, String.format(msg, size), false);
return array;
}
}
WorkArea wa = local.get();
Class<? extends BaseDataType> cls =
(Class<? extends BaseDataType>) array.getClass().getComponentType();
T[] copy = (T[]) Array.newInstance(cls, (int) size);
boolean isObject = object.class.isAssignableFrom(array.getClass().getComponentType());
// if this object array was resized, is no longer needed to keep the type
Class<? extends _BaseObject_> objType = isObject ? wa.dynamicArrayType.remove(array) : null;
if (isObject && size == 0) {
// reset back to indeterminate size, save the type, and release all references
if (objType == null && array.length > 0)
{
objType = (Class<? extends _BaseObject_>) ((object) array[0]).type();
}
if (objType != null)
wa.dynamicArrayType.put((object<? extends _BaseObject_>[]) copy, objType);
for (object<? extends _BaseObject_> ref : (object[]) array)
{
ref.decrement();
}
}
for (int i = 0; i < copy.length; i++)
{
if (isObject)
{
copy[i] = isVar ? (T) new ObjectVar<>(objType) : (T) BaseDataType.generateUnknown(cls);
}
else
{
if (initToUnknown)
{
copy[i] = (T) BaseDataType.generateUnknown(cls);
}
else
{
copy[i] = (T) BaseDataType.generateDefault(cls);
}
}
}
// re-register it - replace the old array with the new one
wa.reRegister(array, copy, wa);
if (isObject)
{
ObjectOps.reRegister(array, copy);
}
// preserve precision of the source array
copyDecimalPrecision(array, copy);
copyCaseSensitive(array, copy);
// if this array belongs to an extent parameter, update it
AbstractExtentParameter<BaseDataType> extentParam = wa.getExtentParameter(array);
if (extentParam != null)
{
extentParam.setParameter(copy);
}
// if shared, update the shared reference (global or local)
SharedVariableManager.updateExtentVar(array, copy);
// if this is an undoable extent var, we need to register all instances as undoable!
Object referent = wa.pm._thisProcedure();
Deque<Map<BaseDataType[], Integer>> undoableExts = wa.dynamicUndoableArrays.get(referent);
Iterator<Map<BaseDataType[], Integer>> iter = undoableExts.iterator();
while (iter.hasNext())
{
Map<BaseDataType[], Integer> undoables = iter.next();
Integer transLevel = undoables.remove(array);
if (transLevel != null)
{
// if this is the ext program scope and the proc is ran persistent
boolean persistent = !iter.hasNext() && wa.pm.isThisProcedurePersistent();
// the entry must be registered at the scope where the extent was defined
wa.tm.registerAt(transLevel, persistent, copy);
break;
}
}
return copy;
}
/**
* Get a copy of the given array.
*
* @param array
* The array for which a copy is needed.
*
* @return The copy.
*/
public static <T extends BaseDataType> T[] copyOf(T[] array)
{
return copyOf(array, true);
}
/**
* Get a copy of the given array.
*
* @param array
* The array for which a copy is needed.
* @param pending
* Flag indicating if the copy is pending (as the call is made before the top-level block starts).
*
* @return The copy.
*/
public static <T extends BaseDataType> T[] copyOf(T[] array, boolean pending)
{
Class<? extends BaseDataType> cls =
(Class<? extends BaseDataType>) array.getClass().getComponentType();
boolean isObject = (cls == object.class);
T[] copy = (T[]) Array.newInstance(cls, array.length);
for (int i = 0; i < copy.length; i++)
{
copy[i] = isObject ? (T) new ObjectVar() : (T) BaseDataType.generateUnknown(cls);
copy[i].assign(array[i]);
}
WorkArea wa = local.get();
if (wa.isRegistered(array))
{
// if the original array is registered as a dynamic array
// register the copy as well.
// for example, when assigning the output parameter back to its
// original reference, a copy is made
wa.register(copy, pending);
}
if (isObject && pending)
{
ObjectOps.registerPending((Object) copy);
}
// preserve precision of the source array
copyDecimalPrecision(array, copy);
copyCaseSensitive(array, copy);
return copy;
}
/**
* Get the length of the given dynamic-extent variable.
*
* @param array
* The dynamic-extent variable.
*
* @return If the array's length is zero, return unknown. Else, the actual size.
*/
public static integer lengthOf(BaseDataType[] array)
{
return array.length == 0 ? integer.UNKNOWN : integer.of(array.length);
}
/**
* Register the given array as a dynamic-array.
* <p>
* As this is called always before the top-level block is actually executed (is invoked during parameter
* processing at the caller), the dynamic array is registered as 'pending', and saved to be processed by
* the next top-level block.
*
* @param array
* The dynamic-array to register.
*/
public static void registerDynamicArray(BaseDataType[] array)
{
registerDynamicArray(array, true);
}
/**
* Register the given array as a dynamic-array.
*
* @param array
* The dynamic-array to register.
* @param pending
* Flag indicating if the copy is pending (as the call is made before the top-level block starts).
*/
public static void registerDynamicArray(BaseDataType[] array, boolean pending)
{
local.get().register(array, pending);
}
/**
* Register the set of given arrays as dynamic extent.
*
* @param arrays
* The set of dynamic arrays to register.
* @param transLevel
* The transaction nesting level where this instance was created.
*/
public static void registerDynamicUndoableArray(Set<BaseDataType[]> arrays, int transLevel)
{
local.get().registerDynamicUndoable(arrays, transLevel);
}
/**
* This method returns true for any real dynamic-extent array (regardless if it had its
* size set).
*
* @param array
* The array to be checked.
*
* @return See above.
*/
public static boolean isDynamicArray(BaseDataType[] array)
{
return local.get().isRegistered(array);
}
/**
* Sets decimal precision for the array argument.
*
* @param array
* The array to be set with the supplied precision.
* @param precision
* Decimal precision to be set in the supplied array.
*/
public static void setDecimalPrecision(decimal[] array, int precision)
{
if (array.length == 0)
{
// for dynamic extents, precision is stored in the context
WorkArea wa = local.get();
wa.setDecimalPrecision(array, precision);
}
else
{
// precision is stored in the components for non-dynamic extents
for (decimal d : array)
{
d.setPrecision(precision);
}
}
}
/**
* Sets case sensitivity for the array argument.
*
* @param array
* The array to be set with the supplied case sensitivity.
* @param caseSens
* Case sensitivity to be set in the supplied array.
*/
public static void setCaseSensitive(Text[] array, boolean caseSens)
{
if (array.length == 0)
{
// for dynamic extents, precision is stored in the context
WorkArea wa = local.get();
wa.setCaseSensitive(array, caseSens);
}
else
{
// precision is stored in the components for non-dynamic extents
for (Text txt : array)
{
txt.setCaseSensitive(caseSens);
}
}
}
/**
* Returns decimal precision of the supplied array. If the array represents a
* dynamic-extent, the precision must have been previously set, otherwise <code>null</code> is
* returned.
*
* @param array
* Array representing an extent variable.
*
* @return The decimal precision value or <code>null</code>, see above.
*/
public static Integer getDecimalPrecision(decimal[] array)
{
if (array.length == 0)
{
// for dynamic extents, precision is stored in the context
WorkArea wa = local.get();
return wa.getDecimalPrecision(array);
}
else
{
// precision is stored in the components for non-dynamic extents
return array[0].getPrecision();
}
}
/**
* Returns case sensitivity of the supplied array. If the array represents a
* dynamic-extent, the case sensitivity must have been previously set, otherwise
* <code>null</code> is returned.
*
* @param array
* Array representing an extent variable.
*
* @return The case sensitivity value or <code>null</code>, see above.
*/
public static Boolean getCaseSensitive(Text[] array)
{
if (array.length == 0)
{
// for dynamic extents, precision is stored in the context
WorkArea wa = local.get();
return wa.getCaseSensitive(array);
}
else
{
// precision is stored in the components for non-dynamic extents
return array[0].isCaseSensitive();
}
}
/**
* Save the legacy 4GL class for the specified dynamic extent array.
*
* @param ref
* The dynamic array reference.
* @param cls
* The legacy 4GL class.
*/
static <T extends _BaseObject_> void setObjectType(object<T>[] ref, Class<T> cls)
{
local.get().dynamicArrayType.put(ref, cls);
}
/**
* Delete the saved the legacy 4GL class for the specified dynamic extent array.
*
* @param ref
* The dynamic array reference.
*/
static <T extends _BaseObject_> void removeObjectType(object<T>[] ref)
{
local.get().dynamicArrayType.remove(ref);
}
/**
* Get the legacy 4GL class for the specified dynamic extent array.
*
* @param ref
* The dynamic array reference.
*
* @return The legacy 4GL class for this array's objects.
*/
static Class<? extends _BaseObject_> getObjectType(object<? extends _BaseObject_>[] ref)
{
return local.get().dynamicArrayType.get(ref);
}
/**
* Registers an unfixed indeterminate extent parameter instance.
* The registration is required to keep the extent parameter instance
* up-to-date in case its parameter reference changes (for example
* when the parameter value is resized by the EXTENT statement).
*
* @param extentParameter
* An instance representing an input or input-output extent parameter.
*
* @throws IllegalArgumentException
* When the passed in instance does not represent an unfixed
* indeterminate extent.
*/
static <T extends BaseDataType> void registerExtentParameter(
AbstractExtentParameter<T> extentParameter)
{
T[] param = extentParameter.getParameter();
if (param.length > 0)
{
throw new IllegalArgumentException(
"Unfixed indeterminate extent for extentParameter argument is expected.");
}
WorkArea wa = local.get();
wa.registerExtentParameter(extentParameter);
}
/**
* Register the {@link WorkArea} instance as pending scopeable, to be added to the next block.
*/
static void registerPendingScopeable()
{
WorkArea wa = local.get();
wa.tm.registerPendingScopeable(wa);
}
/**
* Method use to resize a dynamic extent field on object deserialization. The size must never be zero.
*
* @param array
* The dynamic-extent array to be resized. Must not be null, and its size zero.
* @param newSize
* The array's new size, must not be zero.
* @param ref
* The instance to which the array belongs.
*
* @return The resized array.
*/
@SuppressWarnings("unchecked")
static BaseDataType[] resizeField(BaseDataType[] array, int newSize, _BaseObject_ ref)
{
WorkArea wa = local.get();
Class<? extends BaseDataType> cls = (Class<? extends BaseDataType>) array.getClass().getComponentType();
BaseDataType[] copy = (BaseDataType[]) Array.newInstance(cls, newSize);
boolean isObject = object.class.isAssignableFrom(array.getClass().getComponentType());
// if this object array was resized, is no longer needed to keep the type
Class<? extends _BaseObject_> objType = isObject ? wa.dynamicArrayType.remove(array) : null;
for (int i = 0; i < copy.length; i++)
{
if (isObject)
{
copy[i] = new ObjectVar<>(objType);
}
else
{
copy[i] = BaseDataType.generateUnknown(cls);
}
}
// re-register it - replace the old array with the new one
wa.reRegister(array, copy, wa);
if (isObject)
{
ObjectOps.reRegister(array, copy);
}
// preserve precision of the source array
copyDecimalPrecision(array, copy);
copyCaseSensitive(array, copy);
// if this is an undoable extent var, we need to register all instances as undoable!
Deque<Map<BaseDataType[], Integer>> undoableExts = wa.dynamicUndoableArrays.get(ref);
Iterator<Map<BaseDataType[], Integer>> iter = undoableExts.iterator();
while (iter.hasNext())
{
Map<BaseDataType[], Integer> undoables = iter.next();
Integer transLevel = undoables.remove(array);
if (transLevel != null)
{
// the entry must be registered at the scope where the extent was defined
wa.tm.registerAt(transLevel, true, ref, copy);
break;
}
}
return copy;
}
/**
* Dereference the given array using the given 0-based index.
*
* @param array
* The array to dereference.
* @param idx
* The 0-based index into the array. No bounds checking is done.
*/
private static <B extends BaseDataType> B subscriptWorker(B[] array, int idx)
{
if (idx == -2)
{
// this case means the index is unknown, so we need to return unknown
return (B) BaseDataType.generateUnknown(array.getClass().getComponentType());
}
if (idx == -1)
{
// if an invalid index was passed and [subscriptHelper()] returned -1 then the
// [ErrorManager.isPendingError()] is already set to true anyway.
// However, if [ErrorManager.isPendingError()] was set because ANY OTHER reason and [idx]
// is valid, then we need to return the correct dereference, array[idx].
Class<?> cls = array.getClass().getComponentType();
// silently return unknown value (safest thing)
return (B) BaseDataType.generateUnknown(cls);
}
return array[idx];
}
/**
* Generate a valid 0-based integral subscript from the 1-based numeric
* value given. Bounds checking is done based on the target array length.
*
* @param array
* The array to dereference.
* @param _idx
* The 1-based index to be checked and modified.
* @param assign
* <code>true</code> if the extent var in on the left-side of the assign.
*
* @return The 0-based integral subscript, -2 if the index is the
* <code>unknown value</code> or -1 if the index is out of bounds
* in silent error mode.
*
* @throws ErrorConditionException
* If the index is out of bounds.
*/
private static int subscriptHelper(BaseDataType[] array, NumberType _idx, boolean assign)
throws ErrorConditionException
{
if (_idx.isUnknown())
{
// no error is generated if the index is unknown, but unknown needs to be returned
return -2;
}
// 4GL lets the the 33 MSB overflow, computing the array index using last 31 bits
int idx = (int)(_idx.longValue() & 0x7FFFFFFFL);
if (idx < 0)
{
String spec = "Array subscript %d is out of range";
String errmsg = String.format(spec, idx);
ErrorManager.recordOrThrowError(26, errmsg);
return idx - 1;
}
int len = array.length;
if (len == 0)
{
// dynamic-extent, not initialized
String msg = "Invalid runtime use of indeterminate extent. " +
"It must be set to a fixed dimension";
// TODO: if we are in message mode, it seems like the error is shown, but not thrown
ErrorManager.recordOrThrowError(11387, msg, false);
// we need to return -1
return -1;
}
else if (idx <= 0 || idx - 1 >= len)
{
// error out!
if (isDynamicArray(array) && (!assign || idx - 1 >= len))
{
String spec =
"Array subscript %d is out of range. The indeterminate extent is fixed to a " +
"dimension of %d";
String errmsg = String.format(spec, idx, len);
ErrorManager.recordOrThrowError(11388, errmsg, false);
}
else
{
String spec = "Array subscript %d is out of range";
String errmsg = String.format(spec, idx);
ErrorManager.recordOrThrowError(26, errmsg);
}
// we need to return -1
return -1;
}
return idx - 1;
}
/**
* Generate a valid 0-based integral subscript from the 1-based numeric
* value given. Bounds checking is done based on the target array length.
*
* @param array
* The array to dereference.
* @param idx
* The 1-based index to be checked and modified.
*
* @return The 0-based integral subscript or -1 if the index is out
* of bounds in silent error mode.
*
* @throws ErrorConditionException
* If the index is out of bounds.
*/
private static int subscriptHelper(BaseDataType[] array, long idx)
throws ErrorConditionException
{
return subscriptHelper(array, int64.of(idx), false);
}
/**
* Assigns (or mutates the content of) the indexed element of a target array
* to the given source value. The source and target types must be
* assignable to each other (must be assignment-compatible).
*
* @param target
* The array to be assigned. Must not be <code>null</code>.
* @param index
* The 1-based index into the array. This will be decremented by
* 1 and then used if it is in bounds.
* @param source
* The value from which to assign. Must not be <code>null</code>.
*
* @throws ErrorConditionException
* On any failure in the assignment or array index out of bounds.
* @throws NullPointerException
* If either the source or the target arrays are <code>null</code>.
*/
private static void assignSingleWorker(BaseDataType[] target,
NumberType index,
BaseDataType source)
{
boolean unknown = index.isUnknown();
boolean dynamic = isDynamicArray(target);
if (!unknown && target.length == 0)
{
// dynamic-extent, not initialized, and tried to assign an element
if (index.intValue() <= 0)
{
String spec = "Array subscript %d is out of range";
String errmsg = String.format(spec, index.intValue());
ErrorManager.recordOrThrowError(26, errmsg);
}
else
{
// TODO: for some reason, this error message doesn't end in a dot in 4GL
String msg = "Invalid assignment to an unfixed indeterminate extent";
ErrorManager.recordOrThrowError(11389, msg, false);
}
return;
}
int idx = -1;
try
{
idx = subscriptHelper(target, index, true);
if (idx == -2)
{
// in case the index is unknown, show only this message
ErrorManager.recordOrThrowError(26, "Array subscript 0 is out of range");
return;
}
}
catch (ErrorConditionException ece)
{
// re-throw the error if in dynamic mode or if the index is less than zero
if (dynamic || (!index.isUnknown() && index.intValue() < 0))
throw ece;
// eat the first error, we will rethrow shortly
}
if (idx < 0)
{
if (!dynamic)
{
// only non-dynamic arrays show this error (double space before Field is intented)
ErrorManager.recordOrThrowError(142, "Unable to update Field");
}
// silently return
return;
}
if (target[idx] == null)
{
target[idx] = source.duplicate();
}
else
{
(target[idx]).assign(source);
}
}
/**
* Copies decimal precision from <code>source</code> to <code>destination</code> if
* decimal array is passed in. For non-decimal arrays, this method is effectively a no-op.
*
* @param source
* An array instance, the source of decimal precision.
* @param destination
* An array instance, the destination for decimal precision.
*
* @throws IllegalArgumentException
* When component types of the source and destination array are not equal.
*/
private static void copyDecimalPrecision(BaseDataType[] source, BaseDataType[] destination)
{
Class<?> sourceComponentClass = source.getClass().getComponentType();
Class<?> destinationComponentClass = destination.getClass().getComponentType();
if (!sourceComponentClass.equals(destinationComponentClass))
{
throw new IllegalArgumentException("Source and destination component types must be equal.");
}
// only for decimal arrays
if (decimal.class.isAssignableFrom(source.getClass().getComponentType()))
{
// copy the precision
Integer precision = getDecimalPrecision((decimal[]) source);
if (precision != null)
{
setDecimalPrecision((decimal[]) destination, precision);
}
}
}
/**
* Copies case sensitivity from <code>source</code> to <code>destination</code> if
* text array is passed in. For non-text arrays, this method is effectively a no-op.
*
* @param source
* An array instance, the source of text case-sensitivity.
* @param destination
* An array instance, the destination for case-sensitivity.
*
* @throws IllegalArgumentException
* When component types of the source and destination array are not equal.
*/
private static void copyCaseSensitive(BaseDataType[] source, BaseDataType[] destination)
{
Class<?> sourceComponentClass = source.getClass().getComponentType();
Class<?> destinationComponentClass = destination.getClass().getComponentType();
if (!sourceComponentClass.equals(destinationComponentClass))
{
throw new IllegalArgumentException("Source and destination component types must be equal.");
}
// only for Text arrays
if (Text.class.isAssignableFrom(source.getClass().getComponentType()))
{
// copy the case sensitivity
Boolean caseSens = getCaseSensitive((Text[]) source);
if (caseSens != null)
{
setCaseSensitive((Text[]) destination, caseSens);
}
}
}
/**
* Stores global data relating to the state of the current context.
*/
private static class WorkArea
implements Scopeable
{
/** Helper to use the ProcedureManager without any context local lookups. */
private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
/** Helper to use the TM without any context local lookups. */
private TransactionManager.TransactionHelper tm = TransactionManager.getTransactionHelper();
/** Stack of dynamic-extent arrays, per each scope.*/
private Deque<Set<BaseDataType[]>> dynamicArrays = new ArrayDeque<Set<BaseDataType[]>>();
/** The dynamic arrays declared before the next top-level block will start, to be registered with it. */
private Set<BaseDataType[]> pendingDynamicArrays = Collections.newSetFromMap(new IdentityHashMap<>());
/** Stack of undoable dynamic-extent arrays, per each scope and each external program.*/
private Map<Object, Deque<Map<BaseDataType[], Integer>>> dynamicUndoableArrays = new IdentityHashMap<>();
/** Map of decimal dynamic array precisions. */
private Map<BaseDataType[], Integer> dynamicArrayPrecisions = new IdentityHashMap<>();
/** Map of text dynamic array case-sensitive. */
private Map<BaseDataType[], Boolean> dynamicArrayCaseSens = new IdentityHashMap<>();
/** The extent arguments to be passed to the next top-level block, to be registered with it. */
private Map<BaseDataType[], AbstractExtentParameter<?>> pendingExtentParameters = new IdentityHashMap<>();
/** Map of extent parameter instances */
private Deque<Map<BaseDataType[], AbstractExtentParameter<?>>> extentParameters = new ArrayDeque<>();
/**
* Mapping of the dynamic extent underlying 4GL class type - required when setting the
* size of this array.
*/
private Map<object<? extends _BaseObject_>[], Class<? extends _BaseObject_>>
dynamicArrayType = new IdentityHashMap<>();
/** Map of dynamic arrays created in a persistent program. */
private Map<Object, Set<BaseDataType[]>> persistentProcDynArrays = new IdentityHashMap<>();
public WorkArea()
{
// add a scope for the 'global' block where all the persistent procedure arrays are kept
dynamicArrays.push(Collections.newSetFromMap(new IdentityHashMap<>()));
}
@Override
public void scopeStart(BlockDefinition block)
{
if (!pendingDynamicArrays.isEmpty())
{
Set<BaseDataType[]> arrs = Collections.newSetFromMap(new IdentityHashMap<>());
dynamicArrays.push(arrs);
arrs.addAll(pendingDynamicArrays);
pendingDynamicArrays.clear();
}
else
{
dynamicArrays.push(Collections.emptySet());
}
if (!pendingExtentParameters.isEmpty())
{
Map<BaseDataType[], AbstractExtentParameter<?>> exts = new IdentityHashMap<>();
extentParameters.push(exts);
exts.putAll(pendingExtentParameters);
pendingExtentParameters.clear();
}
else
{
extentParameters.push(Collections.emptyMap());
}
Object referent = pm._thisProcedure();
Deque<Map<BaseDataType[], Integer>> undoableExtStack = dynamicUndoableArrays.get(referent);
if (undoableExtStack == null)
{
dynamicUndoableArrays.put(referent, undoableExtStack = new ArrayDeque<>());
}
undoableExtStack.push(Collections.emptyMap());
}
@Override
public void scopeDeleted()
{
Object referent = pm.getProcessedProcedure();
if (referent == null ||
pm._isPersistentAndNotPendingDelete(referent) ||
(referent == pm._thisProcedure() && !tm.isExternalBlock()))
{
// if no referent or procedure is still persistent, do not clean up yet
return;
}
dynamicUndoableArrays.remove(referent);
Set<BaseDataType[]> refs = persistentProcDynArrays.remove(referent);
if (refs != null && !refs.isEmpty())
{
// cleanup after this persistent program - remove them from the global block and other collections
dynamicArrays.getLast().removeAll(refs);
dynamicArrayPrecisions.keySet().removeAll(refs);
dynamicArrayCaseSens.keySet().removeAll(refs);
dynamicArrayType.keySet().removeAll(refs);
}
}
@Override
public void scopeFinished()
{
Object referent = pm._thisProcedure();
if (!dynamicArrays.isEmpty())
{
// remove scope-registered arrays
Set<BaseDataType[]> arrays = dynamicArrays.pop();
if (!arrays.isEmpty())
{
if (pm._isPersistentAndNotPendingDelete(referent) && tm.isExternalBlock())
{
// all dynamic arrays in a persistent program must be saved in the global block
dynamicArrays.getLast().addAll(arrays);
persistentProcDynArrays.put(referent, arrays);
}
else
{
// remove precisions and output extent param registrations for the arrays being removed
for (BaseDataType[] array : arrays)
{
dynamicArrayPrecisions.remove(array);
dynamicArrayCaseSens.remove(array);
dynamicArrayType.remove(array);
}
}
}
}
if (!extentParameters.isEmpty())
{
extentParameters.pop();
}
Deque<Map<BaseDataType[], Integer>> undoableExtStack = dynamicUndoableArrays.get(referent);
if (undoableExtStack != null && undoableExtStack.size() > 1)
{
// the top-most scope (for the ext program) remains saved until the external program
// gets deleted
undoableExtStack.pop();
}
}
/**
* Get the {@link ScopeId} for the instance.
*
* @return {@link ScopeId#ARRAY_ASSIGNER}.
*/
@Override
public ScopeId getScopeId()
{
return ScopeId.ARRAY_ASSIGNER;
}
/**
* Returns <code>true</code> if the supplied array argument has been
* previously registered, <code>false</code> otherwise.
*
* @param array
* A valid array instance.
*
* @return See above.
*/
public boolean isRegistered(BaseDataType[] array)
{
for (Set<BaseDataType[]> scopeArrays : dynamicArrays)
{
if (scopeArrays.contains(array))
{
return true;
}
}
return false;
}
/**
* Register the given <code>array</code> as a dynamic-extent array. If the array is already
* registered the method doesn't register it again and simply returns.
* <p>
* The registration of this array will be done in the current block - the API is executed only by
* legacy APIs which are executed within the top-level block.
*
* @param array
* The dynamic-extent array reference, must not be <code>null</code>.
*/
public void register(BaseDataType[] array)
{
register(array, false);
}
/**
* Register the given <code>array</code> as a dynamic-extent array. If the array is already
* registered the method doesn't register it again and simply returns.
*
* @param array
* The dynamic-extent array reference, must not be <code>null</code>.
* @param pending
* Flag indicating if the copy is pending (as the call is made before the top-level block starts).
*/
public void register(BaseDataType[] array, boolean pending)
{
// register only to one scope
if (!isRegistered(array))
{
if (pending)
{
tm.registerPendingScopeable(this);
pendingDynamicArrays.add(array);
}
else
{
Set<BaseDataType[]> arrs = dynamicArrays.peek();
if (arrs == Collections.EMPTY_SET)
{
dynamicArrays.pop();
dynamicArrays.push(arrs = Collections.newSetFromMap(new IdentityHashMap<>()));
}
arrs.add(array);
}
}
}
/**
* Register the set of given arrays as dynamic extent.
*
* @param arrays
* The set of dynamic arrays to register.
* @param transLevel
* The transaction nesting level where this instance was created.
*/
public void registerDynamicUndoable(Set<BaseDataType[]> arrays, int transLevel)
{
tm.registerBlockScopeable(this);
Object referent = pm._thisProcedure();
Deque<Map<BaseDataType[], Integer>> undoableExts = dynamicUndoableArrays.get(referent);
Map<BaseDataType[], Integer> refs = undoableExts.peek();
if (refs == (Map) Collections.emptyMap())
{
refs = new IdentityHashMap<>();
undoableExts.pop();
undoableExts.push(refs);
}
for (BaseDataType[] array : arrays)
{
refs.put(array, transLevel);
}
}
/**
* Deregisters <code>oldArray</code> and registers <code>newArray</code> into the scope
* previously occupied by <code>oldArray</code>.
*
* @param oldArray
* A valid array instance.
* @param newArray
* A valid array instance.
* @param wa
* The {@link WorkArea} instance.
*/
public void reRegister(BaseDataType[] oldArray, BaseDataType[] newArray, WorkArea wa)
{
// go through all scopes
for (Set<BaseDataType[]> scopeArrays : dynamicArrays)
{
if (scopeArrays.remove(oldArray))
{
// oldArray exists in the scope, replace with newArray
scopeArrays.add(newArray);
// arrays are registered only in the first-level scope
break;
}
}
if (AbstractParameter.isExtentParameter(oldArray))
{
AbstractParameter.replaceParameter(oldArray, newArray);
}
for (Set<BaseDataType[]> thisRefs : wa.persistentProcDynArrays.values())
{
if (thisRefs != null && thisRefs.remove(oldArray))
{
thisRefs.add(newArray);
}
}
}
/**
* Retrieve the decimal precision of the supplied array or <code>null</code> if precision
* was not previously set for the array argument.
*
* @param array
* Array representing an extent variable.
*
* @return The decimal precision value or <code>null</code>, see above.
*/
public Integer getDecimalPrecision(decimal[] array)
{
return dynamicArrayPrecisions.get(array);
}
/**
* Retrieve the case sensitivity of the supplied array or <code>null</code> if case sens
* was not previously set for the array argument.
*
* @param array
* Array representing an extent variable.
*
* @return The case sensitivity value or <code>null</code>, see above.
*/
public Boolean getCaseSensitive(Text[] array)
{
return dynamicArrayCaseSens.get(array);
}
/**
* Sets decimal precision for the array argument.
*
* @param array
* The array to be set with the supplied precision.
* @param precision
* Decimal precision to be set for the supplied array.
*/
public void setDecimalPrecision(decimal[] array, int precision)
{
// make sure the array is registered, so it's properly cleaned up
// on scope unrolling
// it's actually a valid case precision being set before registration
register(array);
dynamicArrayPrecisions.put(array, precision);
}
/**
* Sets case sensitivity for the array argument.
*
* @param array
* The array to be set with the supplied case sensitivity.
* @param caseSens
* Case sensitivity to be set for the supplied array.
*/
public void setCaseSensitive(Text[] array, boolean caseSens)
{
// make sure the array is registered, so it's properly cleaned up
// on scope unrolling
// it's actually a valid case precision being set before registration
register(array);
dynamicArrayCaseSens.put(array, caseSens);
}
/**
* Registers an unfixed indeterminate extent parameter instance into
* the current scope. The registered instance will be automatically unregistered
* when the scope is finished. The method will use the return value of
* {@link AbstractExtentParameter#getParameter()} as the registration key.
* To get the registered instance back, use that reference in the call to
* {@link #getExtentParameter(BaseDataType[])}.
*
* @param extentParameter
* An extent parameter instance.
*/
public <T extends BaseDataType> void registerExtentParameter(AbstractExtentParameter<T> extentParameter)
{
T[] parameter = extentParameter.getParameter();
pendingExtentParameters.put(parameter, extentParameter);
}
/**
* Returns a previously registered extent parameter instance,
* see {@link #registerExtentParameter(AbstractExtentParameter)} for more info.
*
* @param array
* A valid dynamic array instance.
*
* @return An extent parameter instance.
*/
public <T extends BaseDataType> AbstractExtentParameter<T> getExtentParameter(BaseDataType[] array)
{
// go through all scopes
for (Map<BaseDataType[], AbstractExtentParameter<?>> scopeParams : extentParameters)
{
@SuppressWarnings("unchecked")
AbstractExtentParameter<T> extentParam = (AbstractExtentParameter<T>) scopeParams.get(array);
if (extentParam != null)
{
return extentParam;
}
}
return null;
}
}
}