BufferFieldImpl.java
/*
** Module : BufferFieldImpl.java
** Abstract : Implementation of the BufferField interface
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 ECF 20150315 Created initial version by refactoring all support specific to BUFFER-FIELD
** 4GL compatibility from the FieldReference class. See that class for a full
** history. This refactoring was done to eliminate the memory footprint and
** overhead of the BUFFER-FIELD implementation from the many simpler use cases
** for the FieldReference class.
** 002 ECF 20150320 Javadoc fix.
** 003 OM 20150415 Added LITERAL-QUESTION attribute implementation.
** 004 SVL 20150503 Added bufferHandle.
** 005 ECF 20151208 Fixed getDataType, which was reporting the data type of extent fields with
** trailing brackets.
** 006 ECF 20160106 Fixed extent method, which would fail when the buffer was empty.
** 007 ECF 20160227 Reworked extent field implementation to support denormalized extent fields.
** 008 OM 20160613 Fixed LITERAL-QUESTION attribute implementation based on new researches.
** 009 IAS 20160714 Use legacy name instead of DMO alias.
** 010 EVL 20161003 Fix for initial() character return value. If the backing variable is unknown
** the returned character value should be unknown as well.
** 011 SVL 20170126 Added caseSensitive.
** 012 SVL 20171025 Field attributes are now buffer-specific.
** 013 GES 20180418 POSITION is now backed by the original schema value (if available).
** 014 OM 20181114 Added NamedSerializable interface implementation.
** 015 OM 20190405 Added stubs for XML persistence.
** 016 CA 20190514 Added DEFAULT-STRING stub.
** 017 SVL 20190614 Changed access modifiers of getProperty and getDMOProxy.
** 018 OM 20190611 Improved support for before and before temp-tables.
** 019 SBI 20190728 Implemented getHelp.
** SBI 20190814 WIDTH-CHARS should return a value even if the back end record is not available.
** 020 IAS 20200221 Added AVAILABLE attr. support, fixed BUFFER-VALUE() attr. support.
** 021 IAS 20200402 Added DEFAULT-STRING & DECIMALS attrs. support, fixed INITIAL value parsing for date
** CA 20200427 Allow bulk assign support for BUFFER-VALUE and ::, in case of extent fields.
** 022 GES 20200628 Added DEFAULT-VALUE. Cleaned up code formatting and added comments.
** SVL 20200916 getProperty became public. Fixed position() for temp tables.
** CA 20201210 A small fix for WIDTH-CHARS attribute, when the field is extent.
** OM 20201030 Invalid attribute API support for getters/setters. Added DECIMALS attribute.
** Implemented several missing options.
** OM 20210309 Do not use DmoMeta as key in TableMapper because temp-tables share the same DmoMeta.
** OM 20210323 Using delegation to local TableMapper.LFI instead of multiple map lookup in
** RecordBuffer.
** OM 20210312 Saved reference to parent buffer in case of LOBs obtained by dereference.
** VVT 20210428 setFormat(String) fixed: now checks the format before setting it.
** Missing @Override annotations added.
** VVT 20210428 setFormat(String) changed to be more uniform: the LogicalFormatParser
** is now used to check logical formats.
** CA 20210628 Fixed DATETIME and DATETIME-TZ toStringExport() usage.
** VVT 20211018 New implementaton methods added: getWidth(), setWidth(), getHeight() and setHeight() to match
** the changes in the Sizeable interface. See: #5594-45.
** CA 20211204 Fixed BUFFER-FIELD behavior, they need to behave like real resources, with their lifetime
** linked with the parent BUFFER resource.
** CA 20220402 Fixed changeValue, to consider the case when the value is a BDT proxy returned by a
** DYNAMIC-FUNCTION call.
** IAS 20220609 Consider character field format on assignment.
** OM 20220713 Fixed validateBufferFieldIndex() for denormalized fields. value(int, boolean) will locate
** and invoke the getter method of the correct denormalied property.
** OM 20220727 Improve debugging.
** CA 20221006 Refactored TableMapper for temp-tables to keep a cache of LegacyTableInfo and the mutable
** state in MutableFieldInfo, for the actual TempTable instance. Refs #6825
** UNIQUE-ID and ADM-DATA are kept as Java types instead of BDT. Refs #6827
** BUFFER-FIELD's instantiating procedure must be the BUFFER's, and not when it gets lazily
** created.
** CA 20221006 Fixed a NPE related to instantiating-procedure in 'changeValue' (surrogate instances being
** created).
** TJD 20220504 Upgrade do Java 11 minor changes
** OM 20221207 Added createFieldReference() method.
** RFB 20221223 Minor javadoc repair
** CA 20220531 If the BUFFER is in 'unknown mode' (due to an outer join not finding a record), do not
** raise 'no record is available' ERROR condition.
** OM 20220604 Improved dereferenciation support.
** OM 20220706 Implemented or fix runtime for XML-NODE-TYPE, DBNAME, TABLE, KEY, READ-ONLY and VIEW-AS
** attributes. Added conversion methodto P2JField.
** OM 20220914 Added MAX-WIDTH support for CHARACTER fields.
** CA 20221006 Fixed a NPE related to instantiating-procedure in 'changeValue' (surrogate instances being
** created).
** CA 20230110 Cache the mutable info at the temp-table and not in a separate map at TableMapper.
** CA 20230110 UNIQUE-ID is generated only when is read by the application.
** SVL 20230110 P2JIndex.components() provides direct access to the components array in order to improve
** performance.
** HC 20230116 Replaced some handle usages with the actual wrapped resources for
** performance.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 023 OM 20230303 Fixed behaviour of value() for 0-index extent fields.
** 024 CA 20230215 Improved other cases to reduce BDT usage from within FWD runtime.
** 025 OM 20230307 The original extent and property name were lost when denormalizing extent fields.
** 026 OM 20230320 Added getOriginalPropertyName() method. asP2JField() returns the original extent.
** 027 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 028 IAS 20230519 Fixed 'getXmlDataType' method.
** 029 AL2 20230608 Minor refactor to compute type and class only once in changeValue.
** 030 RAA 20230615 Refactored calls of newInstance with a value to use BaseDataTypeFactory.instantiate.
** 031 CA 20230928 Remove the type information for an unknown object field - this is required for dynamic
** invocation, where 4GL allows a type match if the type is not set.
** 032 CA 20231031 BUFFER-FIELD handles associated with permanent database table fields can set their own,
** private, value for certain attributes (like FORMAT, LABEL, XML-DATA-TYPE, etc)
** 033 CA 20231129 Directly set the INSTANTIATING-PROCEDURE referent, from the parent BUFFER's, instead of
** passing it as a handle.
** 034 AI 20240312 Changed empty string to unknown when setting value of field if type is not of text type.
** 035 AL2 20240418 Refactored some legacy calls to use lfi.getExtent() instead of fieldRef.getExtent().
** 036 CA 20240423 Resolve the field's data-type only once.
** 037 OM 20240524 The initial attribute use the session format (for date related datatypes).
** 038 DDF 20240527 Changed recordOrShowError() to recordOrShowDatabaseError() in value() method.
** 039 CA 20241030 Cache the DATA-TYPE attribute.
** 040 ICP 20250123 Used integer and logical constants to leverage caches instances.
*/
/*
** 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 java.lang.ref.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.persist.serial.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.ui.client.format.*;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.ErrorManager;
import com.goldencode.p2j.util.UniqueIdGenerator.*;
import com.goldencode.p2j.util.logging.*;
/**
* Implementation of Progress 4GL BUFFER-FIELD methods and attributes, using a {@link FieldReference} as a
* delegate worker.
*/
public final class BufferFieldImpl
extends HandleChain
implements BufferField,
Deletable
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(BufferField.class.getName());
/** {@link FieldReference} delegate object */
private final FieldReference fieldRef;
/** Corresponds to ADM-DATA attribute. */
private String admData = null;
/** Corresponds to UNIQUE-ID attribute. */
private Long uniqueID = null;
/** The legacy info collected from table creation or modified afterwards. */
private final TableMapper.LegacyFieldInfo lfi;
/** Flag for mutable options. This happens when the field belongs to a temp-table. */
private boolean mutableOptions = false;
/** Flag indicating if the delete must be forced. */
private boolean forceDelete = false;
/** The key flag. Lazily initialized / computed. */
private Boolean key = null;
/** The current value for VIEW-AS attribute. */
private String viewAs = BufferField.FILL_IN;
/** The current value for READ-ONLY attribute. */
private boolean readOnly = false;
/** Explicit value of <code>LITERAL-QUESTION</code> attribute for database table fields. */
private logical literalQuestion = null;
/** Explicit value of <code>COLUMN-LABEL</code> attribute for database table fields. */
private character columnLabel = null;
/** Explicit value of <code>FORMAT</code> attribute for database table fields. */
private character format = null;
/** Explicit value of <code>HELP</code> attribute for database table fields. */
private character help = null;
/** Explicit value of <code>LABEL</code> attribute for database table fields. */
private character label = null;
/** Explicit value of <code>VALIDATE-EXPRESSION</code> attribute for database table fields. */
private character validateExpression = null;
/** Explicit value of <code>VALIDATE-MESSAGE</code> attribute for database table fields. */
private character validateMessage = null;
/** Explicit value of <code>SERIALIZE-HIDDEN</code> attribute for database table fields. */
private logical serializeHidden = null;
/** Explicit value of <code>SERIALIZE-NAME</code> attribute for database table fields. */
private character serializeName = null;
/** Explicit value of <code>XML-DATA-TYPE</code> attribute for database table fields. */
private character xmlDataType = null;
/** Explicit value of <code>XML-NODE-NAME</code> attribute for database table fields. */
private character xmlNodeName = null;
/** Explicit value of <code>XML-NODE-TYPE</code> attribute for database table fields. */
private character xmlNodeType = null;
/** The calculated field's data-type. */
private character dataType = null;
/**
* Create a new field reference given a DMO proxy and property name. A non-indexed getter
* method will be used to resolve the field.
*
* @param instProc
* The parent buffer's instantiating-procedure.
* @param dmo
* DMO proxy.
* @param property
* Target property name.
*/
BufferFieldImpl(Object instProc, DataModelObject dmo, String property)
{
this(instProc, dmo, property, false, null, false);
}
/**
* Create a new field reference given a DMO proxy and property name. A non-indexed getter
* method will be used to resolve the field.
*
* @param instProc
* The parent buffer's instantiating-procedure.
* @param dmo
* DMO proxy.
* @param property
* Target property name.
* @param bulk
* When the reference is an EXTENT field and {@code index} is {@code null} the bulk
* getter/setter will be used.
*/
BufferFieldImpl(Object instProc, DataModelObject dmo, String property, boolean bulk)
{
this(instProc, dmo, property, false, null, bulk);
}
/**
* Create a new field reference given a DMO proxy, a property name, and
* an index for the property. An indexed getter method will be used to
* resolve the field.
*
* @param instProc
* The parent buffer's instantiating-procedure.
* @param dmo
* DMO proxy.
* @param property
* Target property name.
* @param uppercase
* <code>true</code> to force a <code>character</code> value to be
* uppercased/right-trimmed;
* <code>false</code> to leave the result unchanged.
* @param index
* Index to pass to property's getter method at resolve time.
* @param bulk
* When the reference is an EXTENT field and {@code index} is {@code null} the bulk
* getter/setter will be used.
*
* @throws IllegalArgumentException
* if field reference is not recognized.
*/
private BufferFieldImpl(Object instProc,
DataModelObject dmo,
String property,
boolean uppercase,
NumberType index,
boolean bulk)
{
this.fieldRef = new FieldReference(dmo, property, uppercase, index, bulk);
lfi = computeLegacyInfo();
this.instProc = instProc == null ? null : new WeakReference<>(instProc);
}
/**
* Create an instance of this class which accesses a specific element of an extent field, given a field
* reference for the same DMO property, which does not reference a specific element of that field.
*
* @param fieldRef
* A reference to an extent field, which does not have an index into a specific element.
* @param index
* A one-based index into an extent field.
* @param lfi
* The legacy field info (used in case of denormalized fields)
*/
private BufferFieldImpl(FieldReference fieldRef, int index, TableMapper.LegacyFieldInfo lfi)
{
this.fieldRef = new FieldReference(fieldRef, index - 1, lfi.getExtent(), lfi.original);
this.lfi = lfi;
setInstantiatingProcedure(null);
}
/**
* Buffer field resources are always generated lazily, and are being deleted by the parent BUFFER. For
* this reason, they will not be processed at the external program where they are lazily created.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean processResource()
{
return false;
}
/**
* Get the <code>name</code> attribute of a BUFFER-FIELD, which is the legacy name of the
* field referenced by this object.
*
* @return See above.
*/
@Override
public character name()
{
if (name == null)
{
// assign directly to the variable, rather than through the API, to avoid a 4052 error
this.name = lfi.getLegacyName();
}
return super.name();
}
/**
* Get the <code>name</code> attribute of handle as a string.
*
* @return See above.
*/
@Override
public String _name()
{
if (name == null)
{
// assign directly to the variable, rather than through the API, to avoid a 4052 error
this.name = lfi.getLegacyName();
}
return super._name();
}
/**
* Indicate one of the following conditions.
*
* @return <code>true</code> if the query buffer has a record with a field available that
* corresponds to this buffer-field object handle;
* <code>false</code> if the query buffer has a record with the field missing that
* corresponds to this buffer-field object handle;
* Unknown value (?) if the query buffer associated with this buffer-field object
* handle has no record..
*/
@Override
public logical available()
{
if (!getBuffer()._available())
{
return new logical();
}
// TODO: can 'false' result ever happen?
return new logical(true);
}
/**
* Conversion of EXTENT attribute (KW_EXTENT).
*
* Getter of EXTENT attribute.
*
* @return the value of EXTENT attribute.
*
*/
@Override
public integer extent()
{
return integer.of(_extent());
}
/**
* Get the INITIAL attribute for the field.
* <p>
* The value of the INITIAL schema field (which is always CHARACTER), formatted with the
* buffer-field's format. If the INITIAL schema field has the Unknown value (?), the
* value of the INITIAL attribute is the null string ("").
*
* @return Current value of INITIAL attribute.
*/
@Override
public character initial()
{
BaseDataType baseDataType = initialValue();
return baseDataType.isUnknown() ? new character()
: character.valueOf(baseDataType.toString());//getFormat().getValue()));
}
/**
* Conversion of STRING-VALUE attribute (KW_STR_VAL).
*
* Getter of STRING-VALUE attribute.
*
* The STRING-VALUE( ) method uses the format attribute to convert the buffer value to
* a string.
*
* @return current value of STRING-VALUE attribute.
*/
@Override
public character stringValue()
{
int extent = lfi.getExtent();
if (extent == 0)
{
return character.valueOf(value().toString(getFormat().getValue()));
}
else
{
ErrorManager.recordOrShowError(26, "Array subscript 0 is out of range", false);
String msg = "Unable to extract BUFFER-VALUE for field %s";
String property = fieldRef.getProperty();
String err = String.format(msg, property);
ErrorManager.recordOrShowError(7366, err, false);
return new character();
}
}
/**
* Conversion of STRING-VALUE attribute (KW_STR_VAL).
*
* Getter of STRING-VALUE attribute.
*
* The STRING-VALUE( ) method uses the format attribute to convert the buffer value to
* a string.
*
* @param index
* An integer expression representing a subscript, for fields that
* have extents.
*
* @return current value of STRING-VALUE attribute.
*/
@Override
public character stringValue(int index)
{
return stringValue(new integer(index));
}
/**
* Conversion of STRING-VALUE attribute (KW_STR_VAL).
*
* Getter of STRING-VALUE attribute.
*
* The STRING-VALUE( ) method uses the format attribute to convert the buffer value to
* a string.
*
* @param index
* An integer expression representing a subscript, for fields that
* have extents.
*
* @return Current value of STRING-VALUE attribute.
*/
@Override
public character stringValue(NumberType index)
{
return character.valueOf(value(index).toString(getFormat().getValue()));
}
/**
* Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
*
* Getter of current value of a buffer-field object. If you modify the BUFFER-VALUE attribute, Progress
* sets the buffer-field object to the new value.
*
* @param index
* A base-1 INTEGER expression representing a subscript, for fields that have extents.
*
* @return Current value of a buffer-field object.
*/
@Override
public BaseDataType value(int index)
{
return value(index, true);
}
/**
* Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
*
* Getter of current value of a buffer-field object. If you modify the
* BUFFER-VALUE attribute, Progress sets the buffer-field object to the new value.
*
* @param index
* A base-1 INTEGER expression representing a subscript, for fields that have extents.
*
* @return Current value of a buffer-field object if the object is an extent field or
* <code>unknown</code> if it is a scalar one.
*/
@Override
public BaseDataType value(NumberType index)
{
if (!validateBufferFieldIndex(index))
{
return fieldRef.unknownValue();
}
return value(index.intValue(), false);
}
/**
* Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
*
* Setter of current value of a buffer-field object. If you modify the
* BUFFER-VALUE attribute, Progress sets the buffer-field object to the
* new value.
*
* @param index
* An INTEGER expression representing a subscript, for fields
* that have extents.
* @param newValue
* New value of a buffer-field object.
*
*/
@Override
public void changeValue(int index, Object newValue)
{
changeValue(index, newValue, true);
}
/**
* Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
*
* Setter of current value of a buffer-field object. If you modify the
* BUFFER-VALUE attribute, Progress sets the buffer-field object to the
* new value.
*
* @param index
* An INTEGER expression representing a subscript, for fields
* that have extents.
* @param newValue
* New value of a buffer-field object.
*
*/
@Override
public void changeValue(NumberType index, Object newValue)
{
if (!this.validateBufferFieldIndex(index))
{
return;
}
changeValue(index.intValue(), newValue, false);
}
/**
* Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
*
* Getter of current value of a buffer-field object. If you modify the
* BUFFER-VALUE attribute, Progress sets the buffer-field object to the new value.
*
* @return Current value of a buffer-field object. If the field is an extent field,
* <code>unknown</code> value is returned.
*/
@Override
public BaseDataType value()
{
Buffer buf = getBuffer();
if (buf != null && buf.valid())
{
if (buf._available())
{
if (lfi.getExtent() != 0)
{
return fieldRef.unknownValue();
}
BaseDataType retVal = fieldRef.get();
if (retVal instanceof LargeObject)
{
((LargeObject) retVal).setFieldReference(fieldRef);
}
else if (retVal instanceof object && retVal.isUnknown())
{
// remove the type information
retVal = new object<>();
}
return retVal;
}
else if (((BufferImpl) buf).buffer().isUnknownMode())
{
return fieldRef.unknownValue();
}
}
String bn = buf == null || !buf.valid() ? "<UNKNOWN>" : buf.name().toStringMessage();
String message1 = String.format("No %s record is available", bn);
ErrorManager.recordOrShowDatabaseError(91, message1, false, true);
String message2 = String.format("Unable to extract BUFFER-VALUE for field %s",
name().toStringMessage());
ErrorManager.recordOrShowDatabaseError(7366, message2, false, false);
return fieldRef.unknownValue();
}
/**
* Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
* <p>
* Setter of current value of a buffer-field object. If you modify the BUFFER-VALUE attribute,
* Progress sets the buffer-field object to the new value.
* <p>
* Special care is taken when the field is a CHARACTER and the LITERAL-QUESTION is the default
* value {@code false}; in this case the
* <ul>
* <li>"?" is understand as the unknown literal (?) and</li>
* <li>trailing blanks (spaces) for strings with total length less than 9 are removed.
* Note that the {@code BUFFER-VALUE} specification does not mention any thing about the
* string length, and also upholds that enclosing quotes are removed. The current
* implementation reflects the 4GL's behaviour obtained by manual testing.</li>
* </ul>
* If LITERAL-QUESTION is set to {@code yes} the string are not affected.
*
* @param newValue
* New value of a buffer-field object.
*
*/
@Override
public void changeValue(Object newValue)
{
if (fieldRef.getParentBuffer() instanceof TemporaryBuffer &&
fieldRef.getDMO() instanceof BufferImpl)
{
BufferImpl buf = (BufferImpl) fieldRef.getDMO();
if (buf.isBeforeBuffer())
{
ErrorManager.recordOrShowError(12375, buf.doGetName(), "");
// You may not update BEFORE-TABLE <name> record.
ErrorManager.recordOrShowError(142, buf.doGetName(), "");
// ** Unable to update <filename> Field.
ErrorManager.recordOrShowError(3131, "BUFFER-VALUE", "", LegacyResource.BUFFER_FIELD);
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
ErrorManager.recordOrThrowError(142, buf.doGetName(), "");
// ** Unable to update <filename> Field.
return;
}
}
if (fieldRef.getExtent() != null && fieldRef.getIndex() == -1)
{
// we must be in bulk mode
Class<?> clazz = newValue.getClass();
if (!clazz.isArray())
{
BufferImpl buf = (BufferImpl) fieldRef.getDMO();
ErrorManager.recordOrShowError(26, "0", "");
// ** Array subscript 0 is out of range. (26)
ErrorManager.recordOrShowError(142, buf.doGetName(), "");
// ** Unable to update tt1 Field. (142)
ErrorManager.recordOrShowError(3131, "BUFFER-VALUE", "", LegacyResource.BUFFER_FIELD);
// Unable to set attribute ? in widget of type BUFFER-FIELD. (3131)
ErrorManager.recordOrThrowError(142, buf.doGetName(), "");
// ** Unable to update tt1 Field. (142)
return;
}
Class<?> type = fieldRef.getType();
if (!type.isArray())
{
// TODO: error?
return;
}
Class<?> ctype = clazz.getComponentType();
Class<?> ftype = type.getComponentType();
if (ctype != ftype)
{
// if not the same type, convert the value
BaseDataType[] newVal = new BaseDataType[fieldRef.getExtent()];
int psize = Array.getLength(newValue);
for (int i = 0; i < psize; i++)
{
newVal[i] = BaseDataType.generateUnknown(ftype);
newVal[i].assign((BaseDataType) Array.get(newValue, i));
}
newValue = newVal;
}
fieldRef.set((BaseDataType[]) newValue);
return;
}
BaseDataType bdt = null;
String format = null;
if (newValue instanceof Text && !(fieldRef.get() instanceof Text))
{
Text newValueText = (Text) newValue;
if (!newValueText.isUnknown() && newValueText.toJavaType().equals(""))
{
fieldRef.set(fieldRef.unknownValue());
return;
}
}
if (!(newValue instanceof BaseDataType))
{
// try some simple conversions
if (newValue instanceof Integer)
{
bdt = new integer((Integer) newValue);
}
else if (newValue instanceof Long)
{
bdt = new integer((Long) newValue);
}
else if (newValue instanceof Double)
{
bdt = new decimal((Double) newValue);
}
else if (newValue instanceof Boolean)
{
bdt = new logical((Boolean) newValue);
}
else if (newValue instanceof String)
{
bdt = new character((String) newValue);
}
else if (newValue.getClass().isArray())
{
ErrorManager.recordOrShowError(14910);
// Dynamic BUFFER-VALUE array assignment for field f2 requires that extents match. (14910)
ErrorManager.recordOrShowError(3131, "?", "", LegacyResource.BUFFER_FIELD);
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
ErrorManager.recordOrThrowError(142, ((BufferImpl) fieldRef.getDMO()).doGetName(), "");
// ** Unable to update <filename> Field.
return;
}
else
{
throw new ClassCastException(
"Can't convert " + newValue.getClass().getName() + " to BaseDataType!");
}
}
else if (newValue instanceof unknown)
{
bdt = fieldRef.unknownValue();
}
else if (!fieldRef.getType().isAssignableFrom(newValue.getClass()))
{
try
{
// TODO: OM: newValue is not compatible with fieldRef
bdt = (BaseDataType) BaseDataTypeFactory.instantiate(fieldRef.getType(),
(BaseDataType) newValue);
}
catch (InvocationTargetException exc)
{
// the constructor could not manage to convert the passed value into a meaningful
// object of the field's type
Throwable cause = exc.getCause();
if (cause instanceof ConditionException)
{
throw (ConditionException) cause;
}
LOG.log(Level.SEVERE, "Error performing runtime type conversion", exc);
return;
}
catch (ReflectiveOperationException |
IllegalArgumentException exc)
{
ErrorManager.recordOrThrowError(
5729,
"Incompatible datatypes found during runtime conversion");
return;
}
}
else
{
// finally... the simple case
bdt = (BaseDataType) newValue;
// special handling for character fields when LITERAL-QUESTION attribute is false
if (bdt instanceof character && fieldRef.getType() == character.class && !_literalQuestion())
{
character newChar = (character) bdt.val();
String stringMessage = newChar.toStringMessage();
if ("?".equals(stringMessage))
{
bdt = newChar.instantiateUnknown();
}
else if (stringMessage.length() < 9)
{
// remove trailing spaces only if total length is at most 8 characters long
bdt = TextOps.rightTrim(newChar, " ");
}
if (!bdt.isUnknown())
{
format = lfi.getFormat(getTempTable());
if (format != null && !"".equals(format.trim()) )
{
try
{
// TODO: get rid of dependency on UI classes.
bdt = GenericFrame.parseFieldValue(character.class, newChar, format);
}
catch (DisplayFormatCheckException e)
{
// Should never happen.
throw new ErrorConditionException(e.getMessage());
}
}
}
// otherwise use the character as it was passed in as [bdt]
}
}
fieldRef.set(bdt);
}
/**
* Conversion of BUFFER-NAME attribute (KW_BUF_NAME).
*
* Gets the name of the buffer object to which the buffer-field object belongs.
*
* @return name of the buffer object to which the buffer-field object belongs.
*/
@Override
public character bufferName()
{
return new character(getParentBuffer().getLegacyName());
}
/**
* Returns the handle of the buffer object to which the buffer-field belongs.
*
* @return the handle of the buffer object to which the buffer-field belongs.
*/
@Override
public handle bufferHandle()
{
return new handle(getBuffer());
}
/**
* Returns the handle of the buffer object to which the buffer-field belongs.
*
* @return the handle of the buffer object to which the buffer-field belongs.
*/
public Buffer getBuffer()
{
return (Buffer) getParentBuffer().getDMOProxy();
}
/**
* Conversion of LITERAL-QUESTION attribute (KW_LIT_QSTN).
*
* Getter of LITERAL-QUESTION attribute.
*
* @return current value of LITERAL-QUESTION attribute.
*/
@Override
public logical literalQuestion()
{
return logical.of(_literalQuestion());
}
/**
* Conversion of LITERAL-QUESTION attribute (KW_LIT_QSTN).
*
* Setter of LITERAL-QUESTION attribute.
*
* @param literalQuestion
* New value of LITERAL-QUESTION attribute.
*/
@Override
public void changeLiteralQuestion(logical literalQuestion)
{
if (!checkUnknown("LITERAL-QUESTION", literalQuestion))
{
return;
}
if (!mutableOptions)
{
this.literalQuestion = new logical(literalQuestion);
}
else
{
lfi.setLiteralQuestion(literalQuestion.toJavaType(), getTempTable());
}
}
/**
* Conversion of VALIDATE-MESSAGE attribute (KW_VAL_MSG).
*
* Getter of VALIDATE-MESSAGE attribute.
*
* @return current value of VALIDATE-MESSAGE attribute.
*/
@Override
public character validateMessage()
{
if (validateMessage != null)
{
return new character(validateMessage);
}
return new character(lfi.getValidateMessage(getTempTable()));
}
/**
* Conversion of VALIDATE-MESSAGE attribute (KW_VAL_MSG).
*
* Setter of VALIDATE-MESSAGE attribute.
*
* @param validateMessage
* New value of VALIDATE-MESSAGE attribute.
*/
@Override
public void changeValidateMessage(character validateMessage)
{
if (!checkUnknown("VALIDATE-MESSAGE", validateMessage))
{
return;
}
if (!mutableOptions)
{
this.validateMessage = new character(validateMessage);
}
else
{
lfi.setValidateMessage(validateMessage.getValue(), getTempTable());
}
}
/**
* Conversion of MANDATORY attribute (KW_MAND).
*
* Getter of KW_MAND attribute.
*
* @return current value of KW_MAND attribute.
*/
@Override
public logical mandatory()
{
return new logical(lfi.isMandatory());
}
/**
* Gets the read-only POSITION attribute.
*
* @return Current value of POSITION attribute.
*/
@Override
public integer position()
{
int pos = -1;
if (!fieldRef.getParentBuffer().isTemporary()) // ignore POSITION for temp-tables
{
pos = lfi.getPosition();
}
// pos == 0 for a schema where there is no POSITION and no ORDER
// pos == -1 if there is no LegacyFieldInfo for this property
if (pos < 1)
{
// use our internal ordering
pos = lfi.getFieldId() + 1;
}
return new integer(pos);
}
/**
* Conversion of VALIDATE-EXPRESSION attribute (KW_VAL_EXPR).
*
* Getter of VALIDATE-EXPRESSION attribute.
*
* @return current value of VALIDATE-EXPRESSION attribute.
*/
@Override
public character validateExpression()
{
if (validateExpression != null)
{
return new character(validateExpression);
}
return new character(lfi.getValidateExpression(getTempTable()));
}
/**
* Conversion of VALIDATE-EXPRESSION attribute (KW_VAL_EXPR).
*
* Setter of VALIDATE-EXPRESSION attribute.
*
* @param validateExpression
* New value of VALIDATE-EXPRESSION attribute.
*/
@Override
public void changeValidateExpression(character validateExpression)
{
if (!checkUnknown("VALIDATE-EXPRESSION", validateExpression))
{
return;
}
if (!mutableOptions)
{
this.validateExpression = new character(validateExpression);
}
else
{
lfi.setValidateExpression(validateExpression.getValue(), getTempTable());
}
}
/**
* Reports if this object is valid for use.
*
* @return <code>true</code> if we are valid (can be used).
*/
@Override
public boolean valid()
{
return getDMOProxy().valid();
}
/**
* Returns the state of the DBNAME attribute
*
* @return Current value of the DBNAME attribute.
*/
@Override
public character getDbName()
{
Database database = fieldRef.getParentBuffer().getDatabase();
return new character(
SchemaDictionary.TEMP_TABLE_DB.equals(database.getName()) ? "PROGRESST" : database.getName());
}
/**
* Returns the state of the TABLE attribute
*
* @return Current value of the TABLE attribute.
*/
@Override
public character getTable()
{
return ((BufferImpl) fieldRef.getParentBuffer().getDMOProxy()).getTable();
}
/**
* HEIGHT-CHARS attribute is not defined for BUFFER-FIELD handle. Calls {@link #invalidAttribute} to
* display appropriate error.
*
* @return unknown value.
*/
@Override
public decimal getHeightChars()
{
invalidAttribute("HEIGHT-CHARS", false);
return new decimal();
}
/**
* HEIGHT-CHARS attribute is not defined for BUFFER-FIELD handle. Calls
* {@link #readOnlyError} to display appropriate error.
*
* @param h
* Not used.
*/
@Override
public void setHeightChars(NumberType h)
{
readOnlyError("HEIGHT", handle.UNKNOWN_ARGUMENT);
}
/**
* HEIGHT-CHARS attribute is not defined for BUFFER-FIELD handle. Calls
* {@link #readOnlyError} to display appropriate error.
*
* @param h
* Not used.
*/
@Override
public void setHeightChars(double h)
{
readOnlyError("HEIGHT", handle.UNKNOWN_ARGUMENT);
}
/**
* Get the WIDTH-CHARS attribute of this BUFFER-FIELD handle.
*
* For buffer-field objects, the WIDTH-CHARS attribute is the number of characters in the
* STRING-VALUE, which the AVM calculates using the FORMAT attribute.
*
* @return See above.
*/
@Override
public decimal getWidthChars()
{
String strValue;
if (getDMOProxy()._available() && fieldRef.getExtent() == null)
{
strValue = stringValue().toStringMessage();
}
else
{
BaseDataType defValue = BaseDataType.generateDefault(BaseDataType.fromTypeName(
getDataType().getValue()));
strValue = defValue.toString(getFormat().getValue());
}
return new decimal(strValue == null ? 0 : strValue.length());
}
/**
* Get the WIDTH Progress attribute. The value is equal to the current component char
* width.
*
* This attribute is an alias for <tt>WIDTH-CHARS attribute</tt>, this method shall
* never be called directly, its solely purpose is to assert the call
* <tt>CAN-QUERY(widget, "WIDTH")</tt> works properly.
*
* @return The integer value of the WIDTH attribute.
*/
@Override
public decimal getWidth()
{
return getWidthChars();
}
/**
* Set the WIDTH-CHARS attribute of this BUFFER-FIELD handle.
*
* The WIDTH-CHARS attribute of a buffer-field is readable but not writeable.
*
* @param widthChars
* The new value for the WIDTH-CHARS attribute.
*/
@Override
public void setWidthChars(NumberType widthChars)
{
readOnlyError("WIDTH", handle.UNKNOWN_ARGUMENT);
}
/**
* Set the WIDTH-CHARS attribute of this BUFFER-FIELD handle.
*
* The WIDTH-CHARS attribute of a buffer-field is readable but not writeable.
*
* @param widthChars
* The new value for the WIDTH-CHARS attribute.
*/
@Override
public void setWidthChars(double widthChars)
{
readOnlyError("WIDTH", handle.UNKNOWN_ARGUMENT);
}
/**
* Set the WIDTH Progress attribute.
*
* This attribute is an alias for <tt>WIDTH-CHARS attribute</tt>, this method shall
* never be called directly, its solely purpose is to assert the call
* <tt>CAN-SET(widget, "WIDTH")</tt> works properly.
*
* @param widthChars
* The integer value for the new WIDTH value.
*/
@Override
public void setWidth(NumberType widthChars)
{
setWidthChars(widthChars);
}
/**
* Get the HEIGHT Progress attribute. The value is equal to the current component char
* height.
*
* This attribute is an alias for <tt>HEIGHT-CHARS attribute</tt>, this method shall
* never be called directly, its solely purpose is to assert the call
* <tt>CAN-QUERY(widget, "HEIGHT")</tt> works properly.
*
* @return The integer value of the HEIGHT attribute.
*/
@Override
public decimal getHeight()
{
return getHeightChars();
}
/**
* Set the HEIGHT Progress attribute.
*
* This attribute is an alias for <tt>HEIGHT-CHARS attribute</tt>, this method shall
* never be called directly, its solely purpose is to assert the call
* <tt>CAN-SET(widget, "HEIGHT")</tt> works properly.
*
* @param heightChars
* The integer value for the new HEIGHT value.
*/
@Override
public void setHeight(NumberType heightChars)
{
setHeightChars(heightChars);
}
/**
* WIDTH-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
* {@link #invalidAttribute} to display appropriate error.
*
* @return unknown value.
*/
@Override
public integer getWidthPixels()
{
invalidAttribute("WIDTH-PIXELS", false);
return new integer();
}
/**
* WIDTH-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
* {@link #readOnlyError} to display appropriate error.
*
* @param widthPixels
* Not used.
*/
@Override
public void setWidthPixels(NumberType widthPixels)
{
readOnlyError("WIDTH-PIXELS", handle.UNKNOWN_ARGUMENT);
}
/**
* WIDTH-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
* {@link #readOnlyError} to display appropriate error.
*
* @param widthPixels
* Not used.
*/
@Override
public void setWidthPixels(int widthPixels)
{
readOnlyError("WIDTH-PIXELS", handle.UNKNOWN_ARGUMENT);
}
/**
* HEIGHT-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
* {@link #invalidAttribute} to display appropriate error.
*
* @return unknown value.
*/
@Override
public integer getHeightPixels()
{
invalidAttribute("HEIGHT-PIXELS", false);
return new integer();
}
/**
* HEIGHT-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
* {@link #readOnlyError} to display appropriate error.
*
* @param heightPixels
* Not used.
*/
@Override
public void setHeightPixels(NumberType heightPixels)
{
readOnlyError("HEIGHT-PIXELS", handle.UNKNOWN_ARGUMENT);
}
/**
* HEIGHT-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
* {@link #readOnlyError} to display appropriate error.
*
* @param heightPixels
* Not used.
*/
@Override
public void setHeightPixels(int heightPixels)
{
readOnlyError("HEIGHT-PIXELS", handle.UNKNOWN_ARGUMENT);
}
/**
* Getter of FORMAT attribute.
*
* @return current value of FORMAT attribute.
*/
@Override
public character getFormat()
{
if (format != null)
{
return new character(format);
}
return new character(lfi.getFormat(getTempTable()));
}
/**
* Setter of FORMAT attribute.
*
* @param format
* The new value.
*/
@Override
public void setFormat(final String format)
{
// Check if the format is valid for the type
final Class<?> fieldDataClass = fieldRef.getType();
if (datetime.class.isAssignableFrom(fieldDataClass))
{
new DatetimeFormatParser().parse(format);
}
else if (date.class.isAssignableFrom(fieldDataClass))
{
new DateFormatParser().parse(format);
}
else if (Text.class.isAssignableFrom(fieldDataClass))
{
new CharacterFormatParser().parse(format);
}
else if (logical.class.isAssignableFrom(fieldDataClass))
{
new LogicalFormatParser().parse(format);
}
else if (NumberType.class.isAssignableFrom(fieldDataClass))
{
new NumberFormatParser().parse(format);
}
else if (rowid.class.isAssignableFrom(fieldDataClass))
{
// FIXME: is this branch ever visited?
// no-op: formats are ignored when rowid is formatted.
}
if (!mutableOptions)
{
this.format = new character(format);
}
else
{
lfi.setFormat(format, getTempTable());
}
}
/**
* Setter of FORMAt attribute.
*
* @param format
* The new value.
*/
@Override
public void setFormat(character format)
{
if (!checkUnknown("FORM", format)) // "FORM" is correct
{
return;
}
setFormat(format.getValue());
}
/**
* Getter of COLUMN-LABEL attribute.
*
* @return current value of COLUMN-LABEL attribute.
*/
@Override
public character getColumnLabel()
{
if (columnLabel != null)
{
return new character(columnLabel);
}
return new character(lfi.getColumnLabel(getTempTable()));
}
/**
* Setter of COLUMN-LABEL attribute.
*
* @param columnLabel
* New value of a attribute.
*/
@Override
public void setColumnLabel(character columnLabel)
{
if (!checkUnknown("COLUMN-LABEL", columnLabel))
{
return;
}
setColumnLabel(columnLabel.getValue());
}
/**
* Setter of COLUMN-LABEL attribute.
*
* @param columnLabel
* New value of a attribute.
*/
@Override
public void setColumnLabel(String columnLabel)
{
if (!mutableOptions)
{
this.columnLabel = new character(columnLabel);
}
else
{
lfi.setColumnLabel(columnLabel, getTempTable());
}
}
/**
* Returns the value of the DATA-TYPE attribute
*
* @return Current value of the DATA-TYPE attribute.
*/
@Override
public character getDataType()
{
if (dataType == null)
{
Class<?> type = fieldRef.getType();
if (type.isArray())
{
type = type.getComponentType();
}
if (type == datetimetz.class)
{
// the only wrapper type currently whose simple Java name doesn't match 4GL name
// TODO: this will change with 4GL OO support
dataType = character.of("datetime-tz");
}
else
{
dataType = character.of(type.getSimpleName());
}
}
return dataType;
}
/**
* Set the <code>DATA-TYPE</code> attribute.
*
* @param dataType
* The DATA-TYPE to be set.
*/
@Override
public void setDataType(String dataType)
{
readOnlyError("DATA-TYPE", handle.UNKNOWN_ARGUMENT);
}
/**
* Set the <code>DATA-TYPE</code> attribute..
*
* @param dataType
* The DATA-TYPE to be set.
*/
@Override
public void setDataType(character dataType)
{
readOnlyError("DATA-TYPE", handle.UNKNOWN_ARGUMENT);
}
/**
* Returns the value of the LABEL attribute
*
* @return Current value of the LABEL attribute.
*/
@Override
public character getLabel()
{
if (label != null)
{
return new character(label);
}
return new character(lfi.getLabel(getTempTable()));
}
/**
* Set the <code>LABEL</code> attribute of handle.
*
* @param label
* The LABEL to be set.
*/
@Override
public void setLabel(String label)
{
if (!mutableOptions)
{
this.label = new character(label);
}
else
{
lfi.setLabel(label, getTempTable());
}
}
/**
* Set the <code>LABEL</code> attribute of handle.
*
* @param label
* The LABEL to be set.
*/
@Override
public void setLabel(character label)
{
// no unknown validation
setLabel(label.toJavaType());
}
/**
* Get the value of the ADM-DATA attribute.
*
* @return See above.
*/
@Override
public character getADMData()
{
return new character(this.admData);
}
/**
* Set the value of the ADM-DATA attribute.
*
* @param value
* The new value.
*/
@Override
public void setADMData(String value)
{
this.admData = value;
}
/**
* Set the value of the ADM-DATA attribute.
*
* @param value
* The new value.
*/
@Override
public void setADMData(character value)
{
this.admData = value.toJavaType();
}
/**
* Gets the the unique ID number associated to this object by the underlying system. Not the
* same as the handle value itself.
*
* @return The integer number of the children.
*/
@Override
public integer getUniqueID()
{
if (uniqueID == null)
{
uniqueID = UniqueIdGenerator.getUniqueId(IdKind.BUFFER_FIELD);
}
return new integer(this.uniqueID);
}
/**
* Getter of CASE-SENSITIVE attribute. Indicates whether the buffer-field is case-sensitive.
*
* @return current value of CASE-SENSITIVE attribute.
*/
@Override
public logical caseSensitive()
{
return new logical(lfi.isCaseSensitive());
}
/**
* Implementation of the read access of the {@code SERIALIZE-NAME} attribute.
*
* @return the current value of the {@code SERIALIZE-NAME} attribute.
*/
@Override
public character getSerializeName()
{
if (serializeName != null)
{
return new character(serializeName);
}
String sName = lfi.getSerializeOptions(getTempTable()).getSerializeName();
return new character(sName == null || sName.trim().isEmpty() ? lfi.getLegacyName() : sName);
}
/**
* Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
*
* @param sName
* the new value for the {@code SERIALIZE-NAME} attribute.
*/
@Override
public void setSerializeName(Text sName)
{
setSerializeName(sName == null || sName.isUnknown() ? null : sName.getValue());
}
/**
* Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
*
* @param sName
* the new value for the {@code SERIALIZE-NAME} attribute.
*/
@Override
public void setSerializeName(String sName)
{
if (!mutableOptions)
{
this.serializeName = new character(sName);
}
else
{
lfi.getSerializeOptions(getTempTable()).setSerializeName(sName);
}
}
/**
* Get the property name of this field.
*
* @return property name of this field.
*/
public String getProperty()
{
return fieldRef.getProperty();
}
/**
* Get the original property name of this field. If the original property is not defined (not a
* denormalized extent field), the normal property name is returned.
*
* @return property name of this field.
*/
public String getOriginalPropertyName()
{
return lfi.original.isEmpty() ? fieldRef.getProperty() : lfi.original;
}
/**
* Creates and returns a copy of the {@code FieldReference} held by this instance.
*
* @param extIndex
* The extent index. If none, {@code null} is accepted.
*
* @return Copy of the {@code FieldReference} held by this instance.
*/
public FieldReference createFieldReference(Integer extIndex)
{
return new FieldReference(fieldRef, extIndex);
}
/**
* Delete the resource.
*
* BUFFER-FIELD will be deleted only when the dynamic buffer is deleted. No resource-specific
* delete is required.
*
* @return <code>true</code> if the resource was deleted.
*/
@Override
protected boolean resourceDelete()
{
// this is always called after the buffer has been deleted, so allow it to delete
return forceDelete || !valid();
}
/**
* Check if this resource supports the PREV-SIBLING attribute.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean hasPrevSibling()
{
return false;
}
/**
* Check if this resource supports the NEXT-SIBLING attribute.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean hasNextSibling()
{
return false;
}
/**
* Get the DMO proxy which references parent buffer.
*
* @return DMO proxy object.
*/
Buffer getDMOProxy()
{
return (Buffer) fieldRef.getParentBuffer().getDMOProxy();
}
/**
* Force the delete of this BUFFER-FIELD.
*/
void forceDelete()
{
forceDelete = true;
delete();
}
/**
* Getter of EXTENT attribute.
*
* @return the value of EXTENT attribute.
*
*/
int _extent()
{
Integer extent = lfi.getExtent();
return extent == null ? 0 : extent;
}
/**
* Retrieve the value of a specific element of an extent field, optionally validating the
* supplied subscript.
*
* @param index
* One-based subscript of the extent field to be changed.
* @param validate
* {@code true} to validate {@code index}, else {@code false}.
*/
private BaseDataType value(int index, boolean validate)
{
if (validate && !validateBufferFieldIndex(index))
{
return fieldRef.unknownValue();
}
BaseDataType ret = null;
if (index == 0)
{
if (lfi.getExtent() == 0) // scalar case
{
ret = fieldRef.get();
}
else
{
// silently return unknown value for extent[0]
ret = lfi.getInitialValue().instantiateUnknown();
}
}
else
{
// normalized extents:
if (!lfi.original.isEmpty())
{
// denormalized extents: the [fieldRef] contains a reference to one of the properties the original
// field was denormalized into, not mandatory the [index]-th one. We need to
// look-up the [index]-th property and invoke its [get()] property.
String legacyName = lfi.getLegacyName();
DmoMeta dmoInfo = ((BufferImpl) fieldRef.getDMO()).buffer().getDmoInfo();
Iterator<Property> fields = dmoInfo.getFields(false);
while (fields.hasNext())
{
Property property = fields.next();
if (property.legacy.equalsIgnoreCase(legacyName))
{
index--;
if (index == 0)
{
ret = new FieldReference((DataModelObject) fieldRef.getDMO(),
property.name, fieldRef.isUpperCase(), null, null, false).get();
break;
}
}
}
// if there is no match, default to [fieldRef]
}
}
if (ret == null)
{
ret = new FieldReference(fieldRef, index - 1).get();
}
if (ret instanceof object && ret.isUnknown())
{
// strip the type information
ret = new object<>();
}
return ret;
}
/**
* Change the value of a specific element of an extent field, optionally validating the
* supplied subscript.
*
* @param index
* One-based subscript of the extent field to be changed.
* @param newValue
* New value to set into the extent field.
* @param validate
* <code>true</code> to validate <code>index</code>, else <code>false</code>.
*/
private void changeValue(int index, Object newValue, boolean validate)
{
if (validate && !validateBufferFieldIndex(index))
{
return;
}
if (index == 0)
{
changeValue(newValue);
}
else
{
BufferFieldImpl field = new BufferFieldImpl(fieldRef, index, lfi);
try
{
field.changeValue(newValue);
}
finally
{
// force the delete, this instance is required for an indexed assign in an extent field, and this
// instance is not a real BUFFER-FIELD resource.
field.forceDelete();
}
}
}
/**
* Validate the index passed to a BUFFER-VALUE method call. For scalar fields, this value must
* be zero. For extent fields, this value must be a positive number less than or equal to the
* size of the extent field. Unknown value is invalid in either case.
*
* @param index
* Index value (unadjusted) provided by converted business logic.
*
* @return <code>true</code> if <code>index</code> is valid, else <code>false</code>.
*
* @throws ErrorConditionException
* if <code>index</code> is invalid and session is not in silent error mode.
*/
private boolean validateBufferFieldIndex(NumberType index)
{
return validateBufferFieldIndex(index.isUnknown() ? -1 : index.intValue());
}
/**
* Validate the index passed to a BUFFER-VALUE method call. For scalar fields, this value must
* be zero. For extent fields, this value must be a positive number less than or equal to the
* size of the extent field. Unknown value is invalid in either case.
*
* @param index
* Index value (unadjusted) provided by converted business logic, or <code>-1</code>
* if that value was unknown.
*
* @return <code>true</code> if <code>index</code> is valid, else <code>false</code>.
*
* @throws ErrorConditionException
* if <code>index</code> is invalid and session is not in silent error mode.
*/
private boolean validateBufferFieldIndex(int index)
{
int max = 0;
if (index > 0)
{
int extent = lfi.getExtent();
if (extent > 0)
{
max = extent;
}
}
if (index < 0 || index > max || (max == 0 && index != 0))
{
String fieldName = lfi.getLegacyName();
String msg = String.format("%s BUFFER-VALUE argument must be valid array index, less " +
"than or equal to field extent %d",
fieldName,
max);
LOG.severe(msg, new Throwable());
ErrorManager.recordOrThrowError(7365, msg, false);
return false;
}
return true;
}
/**
* Check if the provided value of the attribute is unknown and raise error in this case.
*
* @param attributeName
* Name of the attribute which value is checked.
* @param value
* Value of the attribute to be checked.
*
* @return {@code false} if the value is unknown.
*/
private boolean checkUnknown(String attributeName, BaseDataType value)
{
if (value == null || value.isUnknown())
{
ErrorManager.recordOrShowError(4083, attributeName, type() + " widget");
// **Unable to assign UNKNOWN value to attribute <attribute> on <widget id>.
return false;
}
return true;
}
/**
* Get the parent record buffer.
*
* @return parent record buffer.
*/
private RecordBuffer getParentBuffer()
{
return fieldRef.getParentBuffer();
}
/**
* Obtain the XML Schema data type for the buffer-field object.
*
* @return the XML Schema data type for the buffer-field object.
*/
@Override
public character getXmlDataType()
{
if (xmlDataType != null)
{
return new character(xmlDataType);
}
String xmlDataType = lfi.getSerializeOptions(getTempTable()).getXmlDataType();
if (xmlDataType == null || xmlDataType.isEmpty())
{
xmlDataType = Util.getXmlType(BaseDataType.fromTypeName(getDataType().toJavaType()));
}
return new character(xmlDataType);
}
/**
* Configures the XML Schema data type for the buffer-field object.
*
* @param newType the new XML Schema data type for the buffer-field object.
*/
@Override
public void setXmlDataType(String newType)
{
if (mutableOptions)
{
lfi.getSerializeOptions(getTempTable()).setXmlDataType(newType);
}
else
{
xmlDataType = new character(newType);
}
}
/**
* Configures the XML Schema data type for the buffer-field object.
*
* @param newType the new XML Schema data type for the buffer-field object.
*/
@Override
public void setXmlDataType(Text newType)
{
if (checkUnknown("XML-DATA-TYPE", newType))
{
setXmlDataType(newType.toJavaType());
}
}
/**
* Obtain the XML node type of this object.
* This represents the getter for {@code XML-NODE-TYPE} ABL attribute.
*
* @return the XML node type of this object.
*/
@Override
public character getXmlNodeType()
{
if (xmlNodeType != null)
{
return new character(xmlNodeType);
}
return new character(lfi.getSerializeOptions(getTempTable()).getXmlNodeType().name());
}
/**
* Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
* node types are "ATTRIBUTE", "ELEMENT", "HIDDEN", and "TEXT".
* This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
*
* @param newType
* The new type.
*/
@Override
public void setXmlNodeType(Text newType)
{
if (checkUnknown("XML-NODE-TYPE", newType))
{
setXmlNodeType(newType.toJavaType());
}
}
/**
* Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
* node types are "ATTRIBUTE", "ELEMENT", "HIDDEN", and "TEXT".
* This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
*
* @param newType
* The new type.
*/
@Override
public void setXmlNodeType(String newType)
{
if (XmlNode.TEXT.equalsIgnoreCase(newType))
{
// In order to have a Temp-Table field with an XML-NODE-TYPE of 'TEXT', all other fields in the
// Temp-Table must have an XML-NODE-TYPE of 'ATTRIBUTE' or 'HIDDEN'.
BufferImpl buf = (BufferImpl) getParentBuffer().getDMOProxy();
int n = buf._numFields();
boolean allowed = true;
for (int k = 0; k < n; k++)
{
BufferFieldImpl bf = (BufferFieldImpl) buf.bufferFieldNative(k + 1);
if (bf != this)
{
String nodeType = bf.getXmlNodeType().toJavaType();
if (!XmlNode.ATTRIBUTE.equals(nodeType) && !XmlNode.HIDDEN.equals(nodeType))
{
allowed = false;
break;
}
}
}
if (!allowed)
{
ErrorManager.recordOrShowError(13233, _name(), "");
// Cannot set XML-NODE-TYPE of field '<field>' to 'TEXT'. (13233)
ErrorManager.recordOrThrowError(3131, "XML-NODE-TYPE", "", LegacyResource.BUFFER_FIELD);
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>. (3131)
}
}
newType = newType.toUpperCase();
if (mutableOptions)
{
lfi.getSerializeOptions(getTempTable()).setXmlNodeType(SerializeOptions.XmlNodeType.valueOf(newType));
}
else
{
xmlNodeType = new character(SerializeOptions.XmlNodeType.valueOf(newType).name());
}
}
/**
* Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
* node types are "ATTRIBUTE", "ELEMENT", "HIDDEN", and "TEXT".
* This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
*
* @param newType
* The new type.
*/
public void setXmlNodeType(SerializeOptions.XmlNodeType newType)
{
if (mutableOptions)
{
lfi.getSerializeOptions(getTempTable()).setXmlNodeType(newType);
}
else
{
xmlNodeType = new character(newType.name());
}
}
/**
* Checks whether fields are written when the temp-table is serialized, for example into
* JSON or XML.
* This represents the getter for {@code SERIALIZE-HIDDEN} ABL attribute.
*
* @return {@code true} when the fields are hidden.
*/
@Override
public logical getSerializeHidden()
{
if (serializeHidden != null)
{
return new logical(serializeHidden);
}
return new logical(lfi.getSerializeOptions(getTempTable()).isSerializeHidden());
}
/**
* Allow to configure whether fields are written when the temp-table is serialized, for example
* into JSON or XML.
* This represents the setter for {@code SERIALIZE-HIDDEN} ABL attribute.
*
* @param val
* {@code true} when the fields are hidden.
*/
@Override
public void setSerializeHidden(logical val)
{
if (checkUnknown("SERIALIZE-HIDDEN", val))
{
setSerializeHidden(val.booleanValue());
}
}
/**
* Allow to configure whether fields are written when the temp-table is serialized, for example
* into JSON or XML.
* This represents the setter for {@code SERIALIZE-HIDDEN} ABL attribute.
*
* @param val
* {@code true} when the fields are hidden.
*/
@Override
public void setSerializeHidden(boolean val)
{
if (mutableOptions)
{
lfi.getSerializeOptions(getTempTable()).setSerializeHidden(val);
}
else
{
serializeHidden = new logical(val);
}
}
/**
* Obtain the number of decimal places, after the decimal point, that are stored in this
* buffer-field object that corresponds to a DECIMAL field.
*
* @return the number of decimals after the decimal point for this buffer-field.
*/
@Override
public integer getDecimals()
{
return new integer(lfi.getDecimals(getTempTable()));
}
/**
* Sets the number of decimal places, after the decimal point, that are stored in this buffer-field
* object that corresponds to a DECIMAL field.
* <p>
* This attribute is writable for temp-tables but only read-only for permanent tables.
*
* @param decimals
* The new number of decimals after the decimal point for this buffer-field.
*/
@Override
public void setDecimals(NumberType decimals)
{
if (!checkUnknown("DECIMALS", decimals))
{
return;
}
setDecimals(decimals.longValue());
}
/**
* Sets the number of decimal places, after the decimal point, that are stored in this buffer-field
* object that corresponds to a DECIMAL field.
* <p>
* This attribute is writable for temp-tables but only read-only for permanent tables.
*
* @param decimals
* The new number of decimals after the decimal point for this buffer-field.
*/
@Override
public void setDecimals(long decimals)
{
if (!fieldRef.getParentBuffer().isTemporary())
{
ErrorManager.recordOrShowError(17092);
// Cannot set the DECIMALS attribute for a database field.
ErrorManager.recordOrShowError(4078, "DECIMALS", "BUFFER-FIELD widget");
// **Unable to set attribute <attribute> for <widget id>
return;
}
if (lfi.getDataType() != decimal.class)
{
ErrorManager.recordOrShowError(17093);
// DECIMALS attribute only allowed on DECIMAL datatypes.
ErrorManager.recordOrShowError(4078, "DECIMALS", "BUFFER-FIELD widget");
// **Unable to set attribute <attribute> for <widget id>
return;
}
lfi.setDecimals((int) decimals, getTempTable());
}
/**
* Checks whether this field participates in an index.
*
* @return {@code true} if this field is part of an index.
*/
@Override
public logical isKey()
{
if (key == null)
{
key = false;
Iterator<P2JIndex> allIndexes = getParentBuffer().getDmoInfo().getDatabaseIndexes();
while (!key && allIndexes.hasNext())
{
P2JIndex idx = allIndexes.next();
ArrayList<P2JIndexComponent> comps = idx.components();
for (int i = 0; !key && i < comps.size(); i++)
{
P2JIndexComponent comp = comps.get(i);
if (comp.getPropertyName().equals(fieldRef.getProperty()))
{
key = true;
break;
}
}
}
}
return new logical(key);
}
/**
* Obtain the name of the XML element or attribute representing the target object (either a
* {@code DataSet}, a {@code TempTable}, a {@code TemporaryBuffer}, or a temp-table
* {@code BufferField}) name in an XML document.
*
* @return the name of the XML element for this object.
*/
@Override
public character getXmlNodeName()
{
if (xmlNodeName != null)
{
return new character(xmlNodeName);
}
return new character(lfi.getSerializeOptions(getTempTable()).getXmlNodeName());
}
/**
* Sets the name of the XML element or attribute representing the target object (either a
* {@code DataSet}, a {@code TempTable}, a {@code TemporaryBuffer}, or a temp-table
* {@code BufferField}) name in an XML document.
*
* @param name The new name of the XML element for this object.
*/
@Override
public void setXmlNodeName(String name)
{
if (mutableOptions)
{
lfi.getSerializeOptions(getTempTable()).setXmlNodeName(name);
}
else
{
xmlNodeName = new character(name);
}
}
/**
* Sets the name of the XML element or attribute representing the target object (either a
* {@code DataSet}, a {@code TempTable}, a {@code TemporaryBuffer}, or a temp-table
* {@code BufferField}) name in an XML document.
*
* @param name The new name of the XML element for this object.
*/
@Override
public void setXmlNodeName(Text name)
{
if (checkUnknown("XML-NODE-NAME", name))
{
setXmlNodeName(name.toJavaType());
}
}
/**
* Get the DEFAULT-VALUE attribute. This is the initial value of the field in its proper data type.
* This is a polymorphic return type which must be specified as a BDT value even though it will have
* the actual data type of the field on return.
*
* @return The default value of the field.
*/
@Override
public BaseDataType getDefaultValue()
{
return initialValue();
}
/**
* Get the DEFAULT-STRING attribute.
*
* @return See above.
*/
@Override
public character getDefaultString()
{
String init = lfi.getInitial();
Class<?> cls = fieldRef.getType();
if (init == null ||
BinaryData.class.isAssignableFrom(cls) ||
handle.class.isAssignableFrom(cls) ||
rowid.class.isAssignableFrom(cls) ||
recid.class.isAssignableFrom(cls))
{
return new character();
}
if (character.class.isAssignableFrom(cls))
{
return new character(init);
}
else if (datetime.class.isAssignableFrom(cls) && init.trim().equalsIgnoreCase("NOW"))
{
// TODO: this value MUST NOT be cached
return new character("now");
}
else if (date.class.isAssignableFrom(cls) && init.trim().equalsIgnoreCase("TODAY"))
{
// TODO: this value MUST NOT be cached
return new character("today");
}
BaseDataType val = initialValue();
if (val.isUnknown())
{
return new character();
}
else if (val instanceof date)
{
// also will handle datetime and datetime-tz because they are subclasses of date
return new character(val.toStringMessage());
}
else if (val instanceof logical)
{
return new character(val.toString());
}
else if (val instanceof int64)
{
// also will handle integer because it is a subclass of int64
return new character(init.trim().toLowerCase());
}
else if (val instanceof decimal)
{
char decSep = NumberType.getNumericDecimalPoint().getValue().charAt(0);
return new character(init.trim().replace('.', decSep));
}
return new character();
}
/**
* Set the DEFAULT-STRING attribute.
*
* @param val
* The default-string value.
*/
@Override
public void setDefaultString(String val)
{
setDefaultString(new character(val));
}
/**
* Set the DEFAULT-STRING attribute.
*
* @param val
* The default-string value.
*/
@Override
public void setDefaultString(character val)
{
handle.invalidAttribute("DEFAULT-STRING", true, LegacyResource.BUFFER_FIELD);
}
/**
* Returns the value of the HELP attribute
*
* @return Current value of the HELP attribute.
*/
@Override
public character getHelp()
{
if (help != null)
{
return new character(help);
}
return new character(lfi.getHelp(getTempTable()));
}
/**
* Set the <code>HELP</code> attribute of handle.
*
* @param help
* The HELP to be set.
*/
@Override
public void setHelp(String help)
{
if (!mutableOptions)
{
this.help = new character(help);
}
else
{
lfi.setHelp(help, getTempTable());
}
}
/**
* Set the {@code HELP} attribute of handle.
*
* @param help
* The HELP to be set.
*/
@Override
public void setHelp(character help)
{
// TODO: unknown validation ?
setHelp(help.toJavaType());
}
/**
* Provides a short description of this object used for debugging.
*
* @return a short description of this object used for debugging.
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("BufferFieldImpl{");
sb.append(fieldRef.getParentBuffer().getLegacyName()).append(".").append(lfi.getLegacyName());
sb.append(":").append(fieldRef.getType().getSimpleName());
if (lfi.getExtent() != 0)
{
sb.append("[").append(lfi.getExtent());
if (fieldRef.getExtent() == null)
{
sb.append("*"); // denormalized
}
sb.append("]");
}
sb.append("}");
return sb.toString();
}
/**
* Checks whether the widget is write-protected.
*
* @return {@code true} if the widget is write-protected.
*/
@Override
public logical isReadOnly()
{
UnimplementedFeature.experimental("READ-ONLY attribute: BufferFieldImpl.isReadOnly()");
return new logical(readOnly);
}
/**
* Sets the READ-ONLY attribute of the widget.
*
* @param ro
* {@code true} if the widget should be write-protected.
*/
@Override
public void setReadOnly(boolean ro)
{
UnimplementedFeature.experimental("READ-ONLY attribute: BufferFieldImpl.setReadOnly()");
this.readOnly = ro;
}
/**
* Sets the READ-ONLY attribute of the widget.
*
* @param ro
* {@code true} if the widget should be write-protected.
*/
@Override
public void setReadOnly(logical ro)
{
if (ro == null || ro.isUnknown())
{
ErrorManager.recordOrShowError(4058, "READ-ONLY", LegacyResource.BUFFER_FIELD);
// **Unable to assign UNKNOWN value to attribute VIEW-AS on BUFFER-FIELD widget. (4083)
return;
}
setReadOnly(ro.booleanValue());
}
/**
* Get the BUFFER:VIEW-AS attribute.
*
* @return the value of BUFFER:VIEW-AS attribute.
*/
@Override
public character viewAs()
{
return new character(viewAs);
}
/**
* Set the BUFFER:VIEW-AS attribute.
*
* @param viewAs
* The flag state.
*/
@Override
public void viewAs(String viewAs)
{
UnimplementedFeature.missing("BUFFER:VIEW-AS setter");
if (viewAs == null)
{
ErrorManager.recordOrShowError(4058, "VIEW-AS", LegacyResource.BUFFER_FIELD);
//**Unable to assign UNKNOWN value to attribute VIEW-AS on BUFFER-FIELD widget. (4083)
return;
}
switch (viewAs.toUpperCase())
{
case COMBO_BOX:
case DROP_DOWN:
case DROP_DOWN_LIST:
case FILL_IN:
case TOGGLE_BOX:
this.viewAs = viewAs;
break;
default:
ErrorManager.recordOrShowError(4058, "VIEW-AS", LegacyResource.BUFFER_FIELD, viewAs);
// **Attribute VIEW-AS for the BUFFER-FIELD widget has an invalid value of #VIEW-AS. (4058)
return;
}
this.viewAs = viewAs;
}
/**
* Set the BUFFER:VIEW-AS attribute.
*
* @param viewAs
* The flag state.
*/
@Override
public void viewAs(character viewAs)
{
viewAs(viewAs.toJavaType());
}
/**
* Convert the information about this field at the current moment into a {@code P2JField} object and
* return it. Because some parts of the is not immutable, each time this method is invoked, a fresh
* object is created to reflect the current status.
*
* @return The attributes of the field encapsulated in a {@code P2JField} object.
*/
P2JField asP2JField()
{
SerializeOptions serOpt = lfi.getSerializeOptions(getTempTable());
return new P2JField(_name(), ParmType.fromClass(lfi.getDataType()), lfi.getExtent(),
lfi.getFormat(getTempTable()), lfi.getInitialValue(), lfi.getLabel(getTempTable()), lfi.getColumnLabel(getTempTable()),
lfi.isCaseSensitive(), lfi.getCodePage(), lfi.getHelp(getTempTable()),
serOpt.getXmlNodeType() == SerializeOptions.XmlNodeType.HIDDEN,
serOpt.getSerializeName(), serOpt.getXmlDataType(), serOpt.getXmlNodeName(),
serOpt.getXmlNodeType().toString(), lfi.mandatory, lfi.getDecimals(getTempTable()), 0/* MAX-WIDTH*/,
readOnly, lfi.getValidateExpression(getTempTable()), lfi.getValidateMessage(getTempTable()));
}
/**
* Conversion of LITERAL-QUESTION attribute (KW_LIT_QSTN).
*
* Getter of LITERAL-QUESTION attribute.
*
* @return current value of LITERAL-QUESTION attribute.
*/
boolean _literalQuestion()
{
if (literalQuestion != null)
{
return literalQuestion.booleanValue();
}
return lfi.isLiteralQuestion(getTempTable());
}
/**
* Get the lowercased legacy name.
*
* @return The lowercased legacy name.
*/
String getLcLegacyName()
{
return lfi.getLcLegacyName();
}
/**
* Get the value of INITIAL attribute as a BaseDataType.
*
* @return Current value of INITIAL attribute.
*/
private BaseDataType initialValue()
{
return lfi.getInitialValue().duplicate();
// String init = lfi.getInitial();
// character initial = character.valueOf(init);
// Class<?> cls = fieldRef.getType();
//
// character dateFmt = null;
//
// if (date.class.isAssignableFrom(cls))
// {
// // save off the old date format
// dateFmt = SessionUtils.getDateFormat();
//
// // override with MDY
// SessionUtils.setDateFormat("mdy");
// }
//
// try
// {
// return (BaseDataType) (BaseDataTypeFactory.instantiate(character.class, initial));
// }
// catch (Exception exc)
// {
// throw new RuntimeException(exc);
// }
// finally
// {
// if (dateFmt != null)
// {
// // restore date format
// SessionUtils.setDateFormat(dateFmt);
// }
// }
}
/**
* Extracts and returns the {@code LegacyFieldInfo} associated with this field. This method also updates
* the {@code mutableSerializationOptions} if the field belongs to a temp-table.
*
* @return the {@code LegacyFieldInfo} associated with this field.
*/
private TableMapper.LegacyFieldInfo computeLegacyInfo()
{
RecordBuffer parentBuffer = fieldRef.getParentBuffer();
DmoMeta dmoInfo = parentBuffer.getDmoInfo();
if (dmoInfo.isTempTable())
{
// temp tables case
mutableOptions = true;
return TableMapper.getLegacyFieldInfo(parentBuffer.getParentTable(), fieldRef.getProperty());
}
else
{
// permanent tables: P4GL allows the attribute to be queried for permanent tables and
// returns the legacy name.
return TableMapper.getLegacyFieldInfo(dmoInfo, fieldRef.getProperty());
}
}
/**
* Get the parent temp-table from the {@link #fieldRef}.
*
* @return The parent {@link TempTable} instance, or <code>null</code> if this is not a temporary buffer.
*/
private TempTable getTempTable()
{
RecordBuffer parentBuffer = fieldRef.getParentBuffer();
return parentBuffer.getParentTable();
}
}