AbstractTempTable.java
/*
** Module : AbstractTempTable.java
** Abstract : Abstract base class for static and dynamic temp-table 4GL objects.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 SVL 20131115 Created initial version.
** 002 CA 20131209 The dynamic flag must be passed up the c'tor call chain.
** 003 HC 20131215 Implemented ADM-DATA and UNIQUE-ID attribute.
** 004 SVL 20140106 ERROR-STRING attribute and some error handling functions are now handled by
** this class.
** 005 SVL 20140124 Some functionality was moved here from the subclasses. Implemented
** hasRecords().
** 006 SVL 20140205 Got some functionality from subclasses, including COPY-TEMP-TABLE.
** 007 SVL 20140709 Reflect changes in copyAllRows signature.
** 008 OM 20151023 Extracted _hasRecords() worker method. Implemented UNDO attribute management.
** 009 CA 20151219 defaultBuffer() returns a TemporaryBuffer. Added deleteAllBuffers().
** 010 ECF 20160501 Replaced direct SQL use in _hasRecords with a more efficient implementation.
** 011 ECF 20160609 SQL performance improvement.
** 012 OM 20190405 Renamed createTableLike() to createLike().
** 013 OM 20190513 Implemented deleteAll() from interface EmptyTempTable.
** 014 OM 20190530 Extends XmlData interface. Added support for original/changed tables.
** 015 HC 20190719 Fixed an abend during temp-table resource cleanup.
** CA 20190826 Fixed READ-XML - delegate to default buffer.
** 016 OM 20190910 Reversed READ-XML delegatation to allow unprepared temp-tables to be read
** (with schema) from XML file.
** 017 ECF 20200906 New ORM implementation.
** 018 AIL 20200911 Implemented Dereferenceable interface.
** AIL 20200914 Fixed checking prepared state when dereferencing.
** AIL 20200915 Moved prepared method from subclasses here.
** OM 20201002 Use DmoMeta cached information instead of map lookups.
** OM 20201120 Added SCHEMA-MARSHAL implementation. Improved XML serialization support.
** OM 20201208 Added REJECTED attribute.
** OM 20210115 For dynamic temp-tables the before-table is automatically created when needed.
** OM 20210328 Added validation for all buffers.
** ECF 20210506 Use RecordBuffer.getDmoInfo() getter instead of direct field access to prevent
** NPE in proxy case.
** ECF 20210612 Added buffer deregistration to fix a memory leak.
** OM 20210825 Added implementation of READ-JSON.
** OM 20210921 Added implementations of NAMESPACE-PREFIX and XML-NODE-NAME buffer attribute.
** OM 20210922 Fixed default values for XML-NODE-NAME and SERIALIZE-NAME.
** CA 20210928 TRACKING-CHANGES can be disabled regardless if the temp-table is attached to a dataset or
** not.
** OM 20220120 Small optimization.
** CA 20220214 The slave buffers are cleaned up only when the associated temp-table gets deleted.
** OM 20220422 Allow Xml/JsonImport to load records into buffer without exposing package private methods.
** CA 20221006 UNIQUE-ID is kept as a Java type instead of BDT. Refs #6827
** 019 BS 20221129 direct calls to ErrorManager.displayError replaced with ErrorManager.recordOrShowError
** CA 20220426 Added PRIMARY attribute stubs.
** CA 20220524 deleteAll() must allow explicit bulk delete of the before-table.
** CA 20220601 Added 'derefPoly', emitted when '::' is used as r-value in a dynamic extent assignment.
** OM 20220603 TRACKING-CHANGES can be set to a temp-table if at least one of its buffers is a dataset
** component.
** OM 20220604 Improved dereferenciation support.
** OM 20220609 Fixed/added implemenentation of several attributes.
** CA 20220920 Buffer implicit deletion performed on temp-table delete must use a ProcedureManager to
** delete it instead of handle.delete.
** IAS 20221003 Fixed error message for error 3131
** IAS 20221111 Added setter for the 'schemaMarshalLevel', fixed one for the 'namespaceURI'.
** RAA 20230109 Changed inline statement(s) to prepared statement(s).
** 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.
** Removed 'changesTables' as is never used.
** Cache the helper for ProcedureManager, ObjectOps and others (as needed), to reduce
** context-local lookup.
** 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.
** 020 CA 20230302 When deleting a temp-table, the delete can be vetoed if any buffer for the temp-table is
** associated with an OUTPUT dataset. Refactored to move the resource-specific code in a
** 'allowDelete' method.
** 021 IAS 20230325 Fixed source validation. Implemented support for dynamic TEMP-TABLE right
** after CREATE TEMP-TABLE.
** 022 IAS 20230524 Added 'getFields()'
** IAS 20230529 Added 'readTable' method.
** 023 CA 20230724 Further reduce context-local usage.
** 024 AL2 20230928 Made _hasRecords make use of fastHasRecords API with direct-access.
** 025 CA 20231129 Moved the LegacyTableInfo instance from TableMapper to AbstractTempTable (only for temp-
** tables.
** 026 DDF 20240122 Integrate BufferManager.activeBuffers() calls into _hasRecords() and reduce the number
** of buffers iterated by only going over the buffers of the current table.
** DDF 20240130 Refactor _hasRecords() by using isActiveBuffer().
** DDF 20240130 Inlined condition in _hasRecords().
** 027 OM 20241009 The content from all buffers of a temp-table are flushed before using any buffer for
** serializing as JSON/XML.
** 028 CA 20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** 029 CA 20240404 Error 9047 does not set ERROR-STATUS:ERROR flag.
** Static temp-table resolution by name must be done using the currently executing type and
** the static temp-table's defining type.
** 030 CA 20240502 When a BEFORE-TABLE is being built too, for a dynamic temp-table, the associated DMOs for
** both the before and after table must have the after/beforeTable annotations set.
** 031 CA 20240512 'flushBuffers' must flush the peer-table's buffers, too.
** 032 CA 20240525 Bulk delete can't be executed if TRACKING-CHANGES is on, FWD runtime needs to emulate an
** actual FOR EACH to delete all records.
** 033 CA 20240918 Removed default methods from interfaces and moved them as concrete implementations, as
** default methods cause the Java metaspace to spike one order of magnitude.
** 034 OM 20241128 Multi-tenant runtime support: selected the proper persistence context, eventually
** based on [sharedDb] parameter.
** 035 ICP 20241202 In copyTempTable fixed call to copyAllRows.
** 036 OM 20250206 TEMP-TABLE:ORIGIN-HANDLE is reset on EMPTY-DATASET() and EMPTY-TEMP-TABLE().
** TEMP-TABLE:ORIGIN-HANDLE it's actually writable, in spite of what docs say.
** 037 ICP 20250131 Used character.of to leverage cached instances.
** 038 ES 20250314 Added label methods.
** 039 ES 20250313 Moved TempTable:writeXml from temp-table to the default buffer.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.persist;
import com.goldencode.p2j.persist.TableMapper.MutableFieldInfo;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.persist.serial.*;
import com.goldencode.p2j.util.*;
import java.util.*;
import java.util.function.*;
/**
* Abstract base class for static and dynamic temp-table 4GL objects.
*/
public abstract class AbstractTempTable
extends HandleChain
implements TempTable
{
/**
* The default {@code SCHEMA-MARSHAL} level. Encodes the {@code unknown} value of the attribute. It is
* semantically equivalent to {@code SM_FULL}.
*/
public static final int SM_DEFAULT = 0; // = unknown
/** Encodes the {@code NONE} {@code SCHEMA-MARSHAL} level. No attributes are marshaled. */
public static final int SM_NONE = 1;
/** Encodes the {@code MIN} {@code SCHEMA-MARSHAL} level. The marshaled schema is minimized. At this level
* the following data will still BE marshaled:
* <ul>
* <li>field names, data types, and extents;</li>
* <li>temp-table ERROR-STRING</li>
* </ul>
* while following data will NOT be marshal:
* <ul>
* <li>index descriptions;</li>
* <li>field information (label, help, field validation expression, and so on)</li>
* </ul>
*/
public static final int SM_MIN = 2;
/** Encodes the {@code MIN} {@code SCHEMA-MARSHAL} level. The full schema is marshalled. */
public static final int SM_FULL = 3;
/** Default prefix for the name of a temp-table copied with COPY-TEMP-TABLE. */
protected static final String DEFAULT_COPY_TEMP_TABLE_PREFIX = "cpy";
/** Default buffer. */
protected BufferImpl defaultBuffer = null;
/** Corresponds ERROR-STRING attribute. */
protected String errorString = null;
/** Corresponds ERROR attribute. **/
protected Boolean error = false;
/** Corresponds ADM-DATA attribute. */
protected String admData = null;
/** {@code true} if changes made to the records the temporary table can be undone. */
protected boolean undoable = false;
/** Direct reference to the table mapper's structure. */
TableMapper.LegacyTableInfo tableInfo = null;
/**
* The current value of the {@code SCHEMA-MARSHAL} attribute.
* <p>
* The following encoding is used internally: 0 = unknown, 1: NONE, 2: MIN, 3: FULL (see above, the SM_*
* static constants).
*/
private int schemaMarshal = SM_DEFAULT;
/**
* The current value of boolean {@code REJECTED} attribute: indicates whether a change to the
* data of this {@code DataSet} is rejected. The attribute is writable.
*/
private boolean rejected = false;
/**
* The set of child buffers, in registration order. The first buffer added (the default buffer) is always
* guaranteed to be the last processed. Its existence was first needed when a buffer or complete table was
* serialized and all the buffers are validated in this order, default buffer last.
*/
protected final Set<Buffer> allBuffers = new LinkedHashSet<>();
/**
* Enumeration which represents some temp-table functions. Required because error handing may
* vary depending on the calling function.
*/
protected enum CallingFunction
{
COPY_TEMP_TABLE,
ADD_FIELDS_FROM,
CREATE_LIKE
}
/** Corresponds UNIQUE-ID attribute. */
private Long uniqueId = null;
/** The current value of {@code TRACKING-CHANGES} attribute. */
private boolean trackingChanges = false;
/** The current value resource of the {@code ORIGIN-HANDLE} attribute. */
private TempTable originTable = null;
/** Is this an AFTER-TABLE or BEFORE-TABLE? Cached after value is computed. */
protected BeforeType beforeTableType = BeforeType.UNKNOWN;
/**
* The cached peer buffer. For AFTER-TABLE it is the BEFORE-TABLE and vice-versa. The SIMPLE
* buffers do not have a peer table.
*/
protected TempTable peerTable = null;
/** Current value of the {@code DATA-SOURCE-MODIFIED} attribute. */
private boolean dataSourceModified = false;
/** The mutable field info for this temp-table instance. */
private Map<String, TableMapper.MutableFieldInfo> fieldInfo = null;
/**
* Default constructor.
*
* @param dynamic
* Flag indicating if this is a static or a dynamic temp-table.
*/
public AbstractTempTable(boolean dynamic)
{
super(dynamic);
}
/**
* Get the PRIMARY attribute.
*
* @return The primary index.
*/
@Override
public character primaryIndex()
{
if (defaultBuffer == null)
{
return new character();
}
return defaultBuffer.primaryIndex();
}
/**
* Set the PRIMARY attribute.
*
* @param primary
* The primary index.
*/
@Override
public void primaryIndex(Text primary)
{
primaryIndex(primary.toJavaType());
}
/**
* Set the PRIMARY attribute. This method handles the cases of static or prepared tables and the case of
* null parameter (all with errors).
*
* @param primary
* The primary index.
*/
@Override
public void primaryIndex(String primary)
{
if (primary == null)
{
ErrorManager.recordOrShowError(4083, "PRIMARY", LegacyResource.TEMP_TABLE + " widget");
// **Unable to assign UNKNOWN value to attribute <attribute> on <widget id>. (4083)
return;
}
ErrorManager.recordOrShowError(9065, "", "");
// Cannot set PRIMARY on a TEMP-TABLE that has been prepared with TEMP-TABLE-PREPARE. (9065)
ErrorManager.recordOrThrowError(3131, "PRIMARY", "", LegacyResource.TEMP_TABLE);
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-tpe>. (3131)
}
/**
* 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(UniqueIdGenerator.IdKind.TEMP_TABLE);
}
return new integer(this.uniqueId);
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other)
{
return copyTempTable(other, logical.of(false));
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other, logical append)
{
return copyTempTable(other, append, logical.of(false));
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other, logical append, logical replace)
{
return copyTempTable(other, append, replace, logical.of(false));
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
* @param looseCopy
* Loose copy mode.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other, logical append, logical replace, logical looseCopy)
{
return copyTempTable(other, append, replace, looseCopy, new character());
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other, boolean append)
{
return copyTempTable(other, logical.of(append));
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other, boolean append, boolean replace)
{
return copyTempTable(other, logical.of(append), logical.of(replace));
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
* @param looseCopy
* Loose copy mode.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other, boolean append, boolean replace, boolean looseCopy)
{
return copyTempTable(other,
logical.of(append),
logical.of(replace),
logical.of(looseCopy));
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
* @param looseCopy
* Loose copy mode.
* @param prefix
* Prefix for naming the copied temp-table.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other,
boolean append,
boolean replace,
boolean looseCopy,
String prefix)
{
return copyTempTable(other,
logical.of(append),
logical.of(replace),
logical.of(looseCopy),
new character(prefix));
}
/**
* Copies the other temp-table. other must be a temp-tables handle By default, all current
* records are dropped before copy operation.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
* @param looseCopy
* Loose copy mode.
* @param prefix
* Prefix for naming the copied temp-table.
*
* @return true if operation is successful
*/
public logical copyTempTable(handle other,
logical append,
logical replace,
logical looseCopy,
character prefix)
{
RecordBuffer otherBuf = getRecordBufferByHandle(other, CallingFunction.COPY_TEMP_TABLE);
if (otherBuf == null)
{
return logical.of(false);
}
if (!prepared().booleanValue())
{
if (addFunctionWasCalled())
{
displayCopyTempTableHalfDefined();
return logical.of(false);
}
boolean res = createLike(other).booleanValue();
if (!res)
{
return logical.of(false);
}
String tableName = (prefix.isUnknown() ? DEFAULT_COPY_TEMP_TABLE_PREFIX : prefix.getValue()) +
otherBuf.getParentTable()._name();
tempTablePrepare(tableName);
}
Temporary srcDMO = (Temporary) otherBuf.getDMOProxy();
Temporary dstDMO = (Temporary) defaultBufferHandleNative();
boolean res = TemporaryBuffer.copyAllRows(null,
srcDMO,
dstDMO,
append.booleanValue() || replace.booleanValue(),
TemporaryBuffer.CopyTableMode.COPY_TEMP_TABLE_MODE,
append.booleanValue() || replace.booleanValue(),
replace.booleanValue(),
looseCopy.booleanValue());
return logical.of(res);
}
/**
* Emitted for <code>::</code> operator used as r-value in a dynamic extent assignment, as the conversion
* rules doesn't know if the target field is extent or scalar.
*
* @param memberName
* The name of the member to be extracted.
*
* @return The value of the member (scalar or extent) or unknown (?) if there is no such member.
*/
@Override
public Object derefPoly(String memberName)
{
if (!_prepared())
{
displayNotPrepared();
return null;
}
// delegate to default buffer
return defaultBuffer.derefPoly(memberName);
}
/**
* Dereference a named member from this collection. If this collection is a ProDataSet then
* the returned value is a {@link handle} to a named buffer, if the collection is a buffer
* the returned value is the field value with the respective name. In the case that this
* collection does not have a named member with the name specified <code>memberName</code>
* the unknown value is returned.
*
* @param index
* The subscript to be applied to an extent field.
* @param memberName
* The name of the member to be extracted.
*
* @return The value of the member or unknown (?) if there is no such member.
*/
@Override
public BaseDataType dereference(long index, String memberName)
{
UnimplementedFeature.todo("subscripted dereference is not implemented");
return new unknown();
}
/**
* This overloaded method is used when the dereference is the left-side of an assignment.
* The implementation will extract the member from the collection and assign it the new value.
*
* @param index
* The subscript to be applied to an extent field.
* @param memberName
* The name of the member to be extracted.
* @param value
* The new value that will be assigned to the extracted field.
*/
@Override
public void dereference(long index, String memberName, Object value)
{
UnimplementedFeature.todo("subscripted dereference is not implemented");
}
/**
* Dereference a named member from this collection. If this collection is a ProDataSet then
* the returned value is a {@link handle} to a named buffer, if the collection is a buffer
* the returned value is the field value with the respective name. In the case that this
* collection does not have a named member with the name specified <code>memberName</code>
* the unknown value is returned.
*
* @param index
* The subscript to be applied to an extent field.
* @param memberName
* The name of the member to be extracted.
*
* @return The value of the member or unknown (?) if there is no such member.
*/
@Override
public BaseDataType dereference(NumberType index, String memberName)
{
UnimplementedFeature.todo("subscripted dereference is not implemented");
return new unknown();
}
/**
* This overloaded method is used when the dereference is the left-side of an assignment.
* The implementation will extract the member from the collection and assign it the new value.
*
* @param index
* The subscript to be applied to an extent field.
* @param memberName
* The name of the member to be extracted.
* @param value
* The new value that will be assigned to the extracted field.
*/
@Override
public void dereference(NumberType index, String memberName, Object value)
{
UnimplementedFeature.todo("subscripted dereference is not implemented");
}
/**
* All kind of temp-tables can be dereferenced. This should be propagated to the
* {@link #defaultBuffer} if not null. Otherwise, the table was not prepared
* (for dynamic temp-tables).
*
* @param memberName
* The name of the member to be extracted.
*
* @return The value of the member - processed by the {@link #defaultBuffer}.
*/
@Override
public BaseDataType dereference(String memberName)
{
if (!_prepared())
{
displayNotPrepared();
return null;
}
return defaultBuffer.dereference(memberName);
}
/**
* All kind of temp-tables can be dereferenced. This should be propagated to the
* {@link #defaultBuffer} if not null. Otherwise, the table was not prepared
* (for dynamic temp-tables).
*
* @param type
* The expected type of the returned value.
* @param memberName
* The name of the member to be extracted.
*
* @return The value of the member - processed by the {@link #defaultBuffer}.
*/
@Override
public <T extends BaseDataType> T dereference(Class<T> type, String memberName)
{
if (!_prepared())
{
displayNotPrepared();
return null;
}
return defaultBuffer.dereference(type, memberName);
}
/**
* All kind of temp-tables can be dereferenced. This should be propagated to the
* {@link #defaultBuffer} if not null. Otherwise, the table was not prepared
* (for dynamic temp-tables) and the uninitialized error should be displayed.
*
* @param memberName
* The name of the member to be extracted.
* @param value
* The new value that will be assigned to the extracted field.
*/
@Override
public void dereference(String memberName, Object value)
{
if (!_prepared())
{
displayUninitialized();
return;
}
defaultBuffer.dereference(memberName, value);
}
/**
* Returns <code>true</code> if this temp-table is in prepared state (after a successful call
* to {@link #tempTablePrepare}).
* This method implements the read-only PREPARED attribute from 4GL.
*
* @return true if operation is successful
*/
public logical prepared()
{
return logical.of(_prepared());
}
/**
* Sets the ERROR-STRING attribute of 4GL temp-table.
*
* @param errStr
* The new value for the ERROR-STRING attribute.
*/
public void changeErrorString(character errStr)
{
if (!prepared().getValue())
{
ErrorManager.recordOrShowError(12346,
"ERROR-STRING can only be used with a PREPARED temp-table" +
" that is not a BEFORE-TABLE",
true,
false);
ErrorManager.recordOrThrowError(3131,
"Unable to set attribute ERROR-STRING in widget of type TEMP-TABLE",
false);
return;
}
this.errorString = errStr.getValue();
}
/**
* Obtain the current ERROR-STRING attribute of 4GL temp-table.
*
* @return the current ERROR-STRING of the temp-table
*/
public character errorString()
{
return new character(errorString);
}
/**
* 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.getValue();
}
/**
* Returns the state of the ERROR attribute
*
* @return Current state of the ERROR attribute.
*/
@Override
public logical error()
{
if (!prepared().booleanValue())
{
displayNotPrepared();
}
return logical.of(error);
}
/**
* Setter for ERROR attribute.
*
* @param value
* The new value of ERROR attribute.
*/
@Override
public void error(boolean value)
{
error(logical.of(value));
}
/**
* Setter for ERROR attribute.
*
* @param value
* The new value of ERROR attribute.
*/
@Override
public void error(logical value)
{
if (value.isUnknown())
{
unableToAssignUnknown("ERROR", "TEMP-TABLE widget");
return;
}
if (!prepared().booleanValue())
{
displayNotPrepared();
unableToAssignUnknown("ERROR", "TEMP-TABLE widget");
return;
}
error = value.toJavaType();
}
/**
* Sets the UNDO attribute of 4GL temp-table. The change is allowed only if at the moment of the
* call, the following are true:
* - the table was not prepared yet (static temp-tables are always prepared);
* - there is no transaction in progress (so UNDO operations cannot be executed with current state);
* - there are no records in the table (an undo operation won't affect records in this table).
*
* @param undo
* The new value for the attribute.
*/
public void setCanUndo(logical undo)
{
if (undo.isUnknown())
{
displayUnableAssignUnknownUndo();
return;
}
final boolean isPrepared = _prepared();
if (isPrepared && TransactionManager.isTransaction() && _hasRecords())
{
String[] msgs =
{
"From within a transaction, UNDO cannot be set on a TEMP-TABLE " +
"that has been already prepared with TEMP-TABLE-PREPARE",
"Unable to set attribute UNDO in widget of type TEMP-TABLE"
};
ErrorManager.recordOrThrowError(new int[] { 9064, 3131 }, msgs, false);
return;
}
this.undoable = undo.booleanValue();
}
/**
* Obtain the current UNDO attribute of 4GL temp-table.
*
* @return true if the temp-table can undo.
*/
public logical canUndo()
{
return logical.of(isUndoable());
}
/**
* Checks whether this temp-table contains any records.
*
* @return true if this temp-table has records.
*/
@Override
public logical hasRecords()
{
return logical.of(_hasRecords());
}
/**
* Get the current value of UNDO attribute of this temp-table.
*
* @return the current value of UNDO attribute of this temp-table.
*/
@Override
public final boolean isUndoable()
{
return this.undoable;
}
/**
* Bulk delete all records associated with the {@code TempTable}.
*
* @return {@code true} if the delete was possible.
*
* @throws ErrorConditionException
* If an error occurs performing the bulk delete operation.
*/
@Override
public logical deleteAll()
{
if (trackingChanges)
{
ErrorManager.recordOrShowError(12758, _name(), "");
// You may not EMPTY temp-table <name> when TRACKING-CHANGES is true.
return logical.of(false);
}
if (computeTableType() == BeforeType.BEFORE && defaultBuffer.hasBeforeBufferConstraints())
{
ErrorManager.recordOrShowError(11946);
// You cannot EMPTY a BEFORE-TABLE, Use ACCEPT-CHANGES or EMPTY the AFTER-TABLE.
return logical.of(false);
}
AbstractTempTable bTable = (AbstractTempTable) getBeforeTableNative();
if (bTable != null)
{
((BufferImpl) bTable.defaultBuffer().getDMOProxy()).setUpBeforeBuffer(true);
try
{
// drop the before images
bTable.deleteAllImpl();
}
finally
{
((BufferImpl) bTable.defaultBuffer().getDMOProxy()).setUpBeforeBuffer(false);
}
}
deleteAllImpl();
originTable = null;
return logical.of(true);
}
/**
* Bulk delete all records associated with the {@code Buffer} or {@code TempTable}. This allows bulk
* delete when TRACKING-CHANGES is on, as this emulates a {@code FOR EACH tt1. DELETE tt1. end.} block.
*
* @throws ErrorConditionException
* If an error occurs performing the bulk delete operation.
*/
@Override
public void deleteForAll()
{
if (trackingChanges ||
computeTableType() == BeforeType.BEFORE && defaultBuffer.hasBeforeBufferConstraints())
{
// emulate a FOR EACH block
TemporaryBuffer buffer = defaultBuffer();
TempTableBuffer proxy = (TempTableBuffer) buffer.getDMOProxy();
String alias = buffer.getDMOAlias();
AdaptiveQuery query1 = new AdaptiveQuery();
BlockManager.forEach(query1, "##deleteForAll", new Block((Init) () ->
{
query1.initialize(proxy, ((String) null), null, alias + ".recid asc");
},
(Body) () ->
{
proxy.deleteRecord();
}));
return;
}
deleteAllImpl();
}
/**
* Obtain the current {@code SCHEMA-MARSHAL} level. Refer to {@code SM_DEFAULT}, {@code SM_NONE},
* {@code SM_MIN}, and {@code SM_FULL} static constants for details.
*
* @return the current {@code SCHEMA-MARSHAL} level.
*/
public int getSchemaMarshalLevel()
{
return schemaMarshal;
}
/**
* Set the current {@code SCHEMA-MARSHAL} level. Refer to {@code SM_DEFAULT}, {@code SM_NONE},
* {@code SM_MIN}, and {@code SM_FULL} static constants for details.
*/
public void setSchemaMarshalLevel(int schemaMarshalLevel)
{
this.schemaMarshal = schemaMarshalLevel;
}
/**
* Implements the {@code NO-SCHEMA-MARSHAL} attribute getter.
*
* @return {@code true} when {@code SCHEMA-MARSHAL} attribute is set to {@code "NONE"}.
*/
@Override
public logical isNoSchemaMarshal()
{
return logical.of(schemaMarshal == SM_NONE);
}
/**
* Sets the {@code SCHEMA-MARSHAL} attribute to {@code "NO"} value when {@code on = true} or
* back to default value {@code on = false}.
*
* @param on
* The new value of {@code SCHEMA-MARSHAL} attribute.
*/
@Override
public void setNoSchemaMarshal(boolean on)
{
schemaMarshal = on ? SM_NONE : SM_DEFAULT;
}
/**
* Sets the {@code SCHEMA-MARSHAL} attribute to {@code "NO"} value when {@code on = true} or
* back to default value {@code on = false}.
*
* @param on
* The new value of {@code SCHEMA-MARSHAL} attribute.
*/
@Override
public void setNoSchemaMarshal(logical on)
{
if (on == null || on.isUnknown())
{
unableToAssignUnknown("NO-SCHEMA-MARSHALL", "TEMP-TABLE widget");
return;
}
setNoSchemaMarshal(on.toJavaType());
}
/**
* Implements the {@code MIN-SCHEMA-MARSHAL} attribute getter.
*
* @return {@code true} when {@code SCHEMA-MARSHAL} attribute is set to {@code "MIN"}.
*/
@Override
public logical isMinSchemaMarshal()
{
return logical.of(schemaMarshal == SM_MIN);
}
/**
* Sets the {@code SCHEMA-MARSHAL} attribute to {@code "MIN"} value when {@code on = true} or
* back to default value {@code on = false}.
*
* @param on
* The new value of {@code SCHEMA-MARSHAL} attribute.
*/
@Override
public void setMinSchemaMarshal(boolean on)
{
schemaMarshal = on ? SM_MIN : SM_DEFAULT;
}
/**
* Sets the {@code SCHEMA-MARSHAL} attribute to {@code "MIN"} value when {@code on = true} or
* back to default value {@code on = false}.
*
* @param on
* The new value of {@code SCHEMA-MARSHAL} attribute.
*/
@Override
public void setMinSchemaMarshal(logical on)
{
if (on == null || on.isUnknown())
{
unableToAssignUnknown("MIN-SCHEMA-MARSHAL", "TEMP-TABLE widget");
return;
}
setMinSchemaMarshal(on.toJavaType());
}
/**
* Obtain the current value of {@code SCHEMA-MARSHAL} attribute.
*
* @return the current value of {@code SCHEMA-MARSHAL} attribute.
*/
@Override
public character getSchemaMarshal()
{
switch (schemaMarshal)
{
case SM_DEFAULT:
return new character();
case SM_NONE:
return new character(TempTable.SCHEMA_MARSHAL_NONE);
case SM_MIN:
return new character(TempTable.SCHEMA_MARSHAL_MIN);
case SM_FULL:
return new character(TempTable.SCHEMA_MARSHAL_FULL);
}
throw new IllegalStateException("Illegal state for TEMP-TABLE:SCHEMA-MARSHAL attribute.");
}
/**
* Sets a new value for {@code SCHEMA-MARSHAL} attribute.
*
* @param level
* the new value or {@code SCHEMA-MARSHAL} attribute.
*/
@Override
public void setSchemaMarshal(String level)
{
if (level == null)
{
schemaMarshal = SM_DEFAULT;
return;
}
switch (level.toUpperCase())
{
case SCHEMA_MARSHAL_FULL:
schemaMarshal = SM_FULL;
// full table schema is serialized with table parameter
break;
case SCHEMA_MARSHAL_MIN:
schemaMarshal = SM_MIN;
// marshals:
// field names, data types, and extents;
// temp-table ERROR-STRING
// does NOT marshal:
// index descriptions
// field information (label, help, field validation expression, and so on (?))
break;
case SCHEMA_MARSHAL_NONE:
schemaMarshal = SM_NONE;
// just table data (?)
break;
default:
ErrorManager.recordOrThrowError(12790, level, "");
// Invalid value given for SCHEMA-MARSHAL <value>.
}
}
/**
* Sets a new value for {@code SCHEMA-MARSHAL} attribute.
*
* @param level
* the new value or {@code SCHEMA-MARSHAL} attribute.
*/
@Override
public void setSchemaMarshal(Text level)
{
setSchemaMarshal(level == null ? null : level.toJavaType());
}
/**
* Checks whether changes in this temp-table is enabled for a {@code DataSet}.
*
* @return The current value for {@code TRACKING-CHANGES} as described above.
*/
@Override
public logical isTrackingChanges()
{
return logical.of(trackingChanges);
}
/**
* Returns the state of the REJECTED attribute
*
* @return Current state of the REJECTED attribute.
*/
@Override
public logical rejected()
{
return logical.of(rejected);
}
/**
* Setter for REJECTED attribute.
*
* @param value The new value of REJECTED attribute.
*/
@Override
public void rejected(boolean value)
{
error(true); // TODO probably
rejected = value;
}
/**
* Setter for REJECTED attribute.
*
* @param value The new value of REJECTED attribute.
*/
@Override
public void rejected(logical value)
{
if (value == null || value.isUnknown())
{
unableToAssignUnknown("REJECTED", "TEMP-TABLE widget");
return;
}
rejected(value.getValue());
}
/**
* Enable or disable changes tracking this temp-table is enabled for a {@code DataSet}.
*
* @param val
* The new value for {@code TRACKING-CHANGES} attribute.
*/
@Override
public void setTrackingChanges(logical val)
{
if (val == null || val.isUnknown())
{
unableToAssignUnknown("TRACKING-CHANGES", "TEMP-TABLE widget");
return;
}
setTrackingChanges(val.getValue());
}
/**
* Enable or disable changes tracking this temp-table is enabled for a {@code DataSet}.
*
* @param on
* The new value for {@code TRACKING-CHANGES} attribute.
*/
@Override
public void setTrackingChanges(boolean on)
{
if (!on)
{
// quick out: disabling TRACKING-CHANGES, nothing to validate
trackingChanges = false;
return;
}
// look for a dataset buffer
handle dataSet = null;
for (Buffer buffer : allBuffers)
{
dataSet = buffer.dataSet();
if (!dataSet.isUnknown() && dataSet._isValid())
{
break; // found a buffer with a valid dataset, not mandatory the default buffer
}
dataSet = null;
}
if (dataSet == null)
{
ErrorManager.recordOrShowError(12355);
// TRACKING-CHANGES may not be turned on for a non-dataset temp-table.
ErrorManager.recordOrThrowError(3131, "TRACKING-CHANGES", "", "TEMP-TABLE");
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
return;
}
TempTable bt = getBeforeTableNative();
if (bt == null)
{
if (_dynamic())
{
// automatically create implicit before table for this dynamic temp-table
((TempTableBuilder) this).createBeforeTable("BI" + _name(), _name());
}
else
{
ErrorManager.recordOrShowError(12344);
// Cannot turn on TRACKING-CHANGES for a static temp-table unless DEFINED with a BEFORE-TABLE phrase.
ErrorManager.recordOrThrowError(3131, "TRACKING-CHANGES", "", "TEMP-TABLE");
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
return;
}
}
// all good, set the flag
trackingChanges = true;
}
/**
* Get the handle of the before-image table that corresponds to the after-image table
* currently associated with this temp-table handle.
*
* @return the value of {@code BEFORE-TABLE} as described above.
*/
@Override
public handle getBeforeTable()
{
return new handle(getBeforeTableNative());
}
/**
* Get the handle of the before-image table that corresponds to the after-image table
* currently associated with this temp-table handle.
*
* @return the value of {@code BEFORE-TABLE} as described above.
*/
@Override
public TempTable getBeforeTableNative()
{
if (computeTableType() == BeforeType.AFTER)
{
return peerTable;
}
return null;
}
/**
* Get the handle of the after-image table that corresponds to the before-image table
* currently associated with this temp-table handle.
*
* @return the value of {@code AFTER-TABLE} as described above.
*/
@Override
public handle getAfterTable()
{
return new handle(getAfterTableNative());
}
/**
* Get the handle of the after-image table that corresponds to the before-image table
* currently associated with this temp-table handle.
*
* @return the value of {@code AFTER-TABLE} as described above.
*/
@Override
public TempTable getAfterTableNative()
{
if (computeTableType() == BeforeType.BEFORE)
{
return peerTable;
}
return null;
}
/**
* Get the handle of the TEMP-TABLE in the original source {@code DataSet} object that
* corresponds to the TEMP-TABLE currently associated with this TEMP-TABLE handle.
*
* @return the current value of the {@code ORIGIN-HANDLE} attribute.
*/
@Override
public handle getOriginHandle()
{
return new handle(originTable);
}
/**
* Set the handle of the TEMP-TABLE in the original source {@code DataSet} object that
* corresponds to the TEMP-TABLE currently associated with this TEMP-TABLE handle.
* <p>
* NOTE:
* In spite of reference manual specifying this is a read-only attribute, in practice this is not the case!
*
* @param newVal
* The new value of the {@code ORIGIN-HANDLE} attribute.
*/
@Override
public void setOriginHandle(handle newVal)
{
originTable = newVal.unwrapTempTable();
}
/**
* Get the handle of the TEMP-TABLE in the original source {@code DataSet} object that
* corresponds to the TEMP-TABLE currently associated with this TEMP-TABLE handle.
*
* @return the current origin table.
*/
@Override
public TempTable getOriginTable()
{
return originTable;
}
/**
* Implementation of the READ-XML method. Reads XML data from the specified data source into
* this object.
* <p>
* Defaults applied:
* <ul>
* <li>Default field type mappings</li>
* <li>LOOSE schema verification mode</li>
* </ul>
*
* @param source
* Data source.
* @param readMode
* Mode in which data is read into buffer. Must evaluate to {@code APPEND},
* {@code EMPTY}, {@code MERGE}, or {@code REPLACE}.
* @param schemaLocation
* Name of an external XML schema file, or empty string or unknown value to use the
* {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
* @param overrideDefaultMapping
* {@code true} to override the default string-to-binary data type mappings when
* creating a temp-table schema from an XML schema.
*
* @return {@code true} if the operation completed successfully, else {@code false}.
*/
@Override
public logical readXml(SourceData source,
character readMode,
character schemaLocation,
logical overrideDefaultMapping)
{
return readXml(source, readMode, schemaLocation, overrideDefaultMapping,
new character(), new character());
}
/**
* Implementation of the READ-XML method. Reads XML data from the specified data source into
* this object.
*
* @param source
* Data source.
* @param readMode
* Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
* EMPTY}, {@code MERGE}, or {@code REPLACE}.
* @param schemaLocation
* Name of an external XML schema file, or empty string or unknown value to use the
* {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
* @param overrideDefaultMapping
* {@code True} to override the default string-to-binary data type mappings when
* creating a temp-table schema from an XML schema.
* @param fieldTypeMapping
* Comma-delimited list of field name and data type name pairs to use to override
* data type mappings of specific fields. Specify unknown value to use default
* mappings.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical readXml(SourceData source,
character readMode,
character schemaLocation,
logical overrideDefaultMapping,
character fieldTypeMapping)
{
return readXml(source, readMode, schemaLocation, overrideDefaultMapping,
fieldTypeMapping, new character());
}
/**
* Implementation of the READ-XML method. Reads XML data from the specified data source into
* this object.
*
* @param source
* Data source.
* @param readMode
* Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
* EMPTY}, {@code MERGE}, or {@code REPLACE}.
* @param schemaLocation
* Name of an external XML schema file, or empty string or unknown value to use the
* {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
* @param overrideDefaultMapping
* {@code True} to override the default string-to-binary data type mappings when
* creating a temp-table schema from an XML schema.
* @param fieldTypeMapping
* Comma-delimited list of field name and data type name pairs to use to override
* data type mappings of specific fields. Specify unknown value to use default
* mappings.
* @param verifySchemaMode
* Schema verification mode. Must evaluate to {@code IGNORE}, {@code LOOSE}, or
* {@code STRICT}. Default is {@code LOOSE}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical readXml(SourceData source,
character readMode,
character schemaLocation,
logical overrideDefaultMapping,
character fieldTypeMapping,
character verifySchemaMode)
{
if (!_prepared() && !_clear())
{
ErrorManager.recordOrShowError(13159);
// Dynamic temp-table must be in CLEAR or PREPARED state for READ-XML Methods.
return logical.of(false);
}
Map<String, String> mapping = null;
if (fieldTypeMapping != null)
{
String javaStrMapping = fieldTypeMapping.toJavaType();
if (javaStrMapping != null)
{
mapping = new HashMap<>();
String[] splits = javaStrMapping.split(",");
int len = splits.length;
if (len == 0 || len % 2 == 1)
{
ErrorManager.recordOrShowError(13200);
// Odd number of values in field-type-mapping argument.
return logical.of(false);
}
// no other validation at this time
for (int k = 0; k < len; k += 2)
{
mapping.putIfAbsent(splits[k], splits[k + 1]); // do not overwrite if field is duplicated
}
}
}
Buffer defaultBuffer = defaultBufferHandleNative();
// normalize parameters
XmlImport xmlImport = new XmlImport(
source,
readMode.toJavaType(),
schemaLocation.toJavaType(),
!overrideDefaultMapping.isUnknown() && overrideDefaultMapping.booleanValue(),
mapping,
verifySchemaMode.toJavaType());
// perform import
return logical.of(xmlImport.importTable(defaultBuffer, RecordBuffer::loadRecord));
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
* <p>
* Defaults applied:
* <ul>
* <li>Unformatted text</li>
* <li>UTF-8 encoding</li>
* <li>xsi:noNamespaceSchemaLocation=?</li>
* <li>No XML schema written</li>
* <li>No before-image data written</li>
* <li>Do not omit temp-table fields which contain their initial values</li>
* </ul>
*
* @param target
* Data target.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeXml(TargetData target)
{
return writeXml(target, null, null, null, null, null, null, null);
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
* <p>
* Defaults applied:
* <ul>
* <li>UTF-8 encoding</li>
* <li>xsi:noNamespaceSchemaLocation=?</li>
* <li>No XML schema written</li>
* <li>No before-image data written</li>
* <li>Do not omit temp-table fields which contain their initial values</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the XML to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeXml(TargetData target, logical formatted)
{
return writeXml(target, formatted, null, null, null, null, null, null);
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
* <p>
* Defaults applied:
* <ul>
* <li>xsi:noNamespaceSchemaLocation=?</li>
* <li>No XML schema written</li>
* <li>No before-image data written</li>
* <li>Do not omit temp-table fields which contain their initial values</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the XML to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeXml(TargetData target, logical formatted, character encoding)
{
return writeXml(target, formatted, encoding, null, null, null, null, null);
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
* <p>
* Defaults applied:
* <ul>
* <li>No XML schema written</li>
* <li>No before-image data written</li>
* <li>Do not omit temp-table fields which contain their initial values</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the XML to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
* @param schemaLocation
* Name of an external XML schema file, or empty string or unknown value to set the
* {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeXml(TargetData target,
logical formatted,
character encoding,
character schemaLocation)
{
return writeXml(target, formatted, encoding, schemaLocation, null, null, null, null);
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
* <p>
* Defaults applied:
* <ul>
* <li>No before-image data written</li>
* <li>Do not omit temp-table fields which contain their initial values</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the XML to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
* @param schemaLocation
* Name of an external XML schema file, or empty string or unknown value to set the
* {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
* @param writeXmlSchema
* {@code True} to write XML schema information, else {@code false}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeXml(TargetData target,
logical formatted,
character encoding,
character schemaLocation,
logical writeXmlSchema)
{
return writeXml(target, formatted, encoding, schemaLocation, writeXmlSchema, null, null, null);
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
* <p>
* Defaults applied:
* <ul>
* <li>No before-image data written</li>
* <li>Do not omit temp-table fields which contain their initial values</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the XML to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
* @param schemaLocation
* Name of an external XML schema file, or empty string or unknown value to set the
* {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
* @param writeXmlSchema
* {@code True} to write XML schema information, else {@code false}.
* @param minXmlSchema
* {@code True} to write minimal XML schema information, else {@code false}. Must be
* {@code false} if {@code writeXmlSchema} is {@code false}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeXml(TargetData target,
logical formatted,
character encoding,
character schemaLocation,
logical writeXmlSchema,
logical minXmlSchema)
{
return writeXml(target,
formatted,
encoding,
schemaLocation,
writeXmlSchema,
minXmlSchema,
null,
null);
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
* <p>
* Defaults applied:
* <ul>
* <li>Do not omit temp-table fields which contain their initial values</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the XML to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
* @param schemaLocation
* Name of an external XML schema file, or empty string or unknown value to set the
* {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
* @param writeXmlSchema
* {@code True} to write XML schema information, else {@code false}.
* @param minXmlSchema
* {@code True} to write minimal XML schema information, else {@code false}. Must be
* {@code false} if {@code writeXmlSchema} is {@code false}.
* @param writeBeforeImage
* {@code True} to write before-image data and error information, else {@code false}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeXml(TargetData target,
logical formatted,
character encoding,
character schemaLocation,
logical writeXmlSchema,
logical minXmlSchema,
logical writeBeforeImage)
{
return writeXml(target,
formatted,
encoding,
schemaLocation,
writeXmlSchema,
minXmlSchema,
writeBeforeImage,
null);
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the XML to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
* @param schemaLocation
* Name of an external XML schema file, or empty string or unknown value to set the
* {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
* @param writeXmlSchema
* {@code True} to write XML schema information, else {@code false}.
* @param minXmlSchema
* {@code True} to write minimal XML schema information, else {@code false}. Must be
* {@code false} if {@code writeXmlSchema} is {@code false}.
* @param beforeImage
* {@code True} to write before-image data and error information, else {@code false}.
* @param noInitialVals
* {@code True} to omit data for fields whose values match their initial values.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeXml(TargetData target,
logical formatted,
character encoding,
character schemaLocation,
logical writeXmlSchema,
logical minXmlSchema,
logical beforeImage,
logical noInitialVals)
{
if (!_prepared())
{
ErrorManager.recordOrShowError(13160);
// Dynamic temp-table must be in PREPARED state for WRITE-XML Methods.
return logical.of(false);
}
Buffer buf = defaultBufferHandleNative();
if (buf == null)
{
return logical.of(false);
}
// buffers will be flushed in [buf.writeJson()]
return buf.writeXml(target,
formatted,
encoding,
schemaLocation,
writeXmlSchema,
minXmlSchema,
beforeImage,
noInitialVals);
}
/**
* Writes an XML representation of the definition of this {@code TempTable}.
*
* @param target
* Data target.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical writeXmlSchema(TargetData target)
{
return writeXmlSchema(target, null, null, null, null);
}
/**
* Writes an XML representation of the definition of this {@code TempTable}.
*
* @param target
* Data target.
* @param formatted
* Use {@code true} for a more human-readable document.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical writeXmlSchema(TargetData target, logical formatted)
{
return writeXmlSchema(target, formatted, null, null, null);
}
/**
* Writes an XML representation of the definition of this {@code TempTable}.
*
* @param target
* Data target.
* @param formatted
* Use {@code true} for a more human-readable document.
* @param encoding
* The character encoding. The default is UTF-8.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical writeXmlSchema(TargetData target, logical formatted, Text encoding)
{
return writeXmlSchema(target, formatted, encoding, null, null);
}
/**
* Writes an XML representation of the definition of this {@code TempTable}.
*
* @param target
* Data target.
* @param formatted
* Use {@code true} for a more human-readable document.
* @param encoding
* The character encoding. The default is UTF-8.
* @param minXmlSchema
* If {@code true}, the minimum amount of schema for the object will be written.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical writeXmlSchema(TargetData target,
logical formatted,
Text encoding,
logical minXmlSchema)
{
return writeXmlSchema(target, formatted, encoding, minXmlSchema, null);
}
/**
* Writes an XML representation of the definition of this {@code TempTable}.
*
* @param target
* Data target.
* @param formatted
* Use {@code true} for a more human-readable document.
* @param encoding
* The character encoding. The default is UTF-8.
* @param minXmlSchema
* If {@code true}, the minimum amount of schema for the object will be written.
* @param omitInitVal
* Use {@code true} to omit the initial values.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical writeXmlSchema(TargetData target,
logical formatted,
Text encoding,
logical minXmlSchema,
logical omitInitVal)
{
if (!_prepared())
{
ErrorManager.recordOrShowError(13160);
// Dynamic temp-table must be in PREPARED state for WRITE-XML Methods.
return logical.of(false);
}
Buffer buf = defaultBufferHandleNative();
if (buf == null)
{
return logical.of(false);
}
return buf.writeXmlSchema(target, formatted, encoding, minXmlSchema, omitInitVal);
}
/**
* Reads XML Schema from an XML document and uses that schema to either create a schema for a
* {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
* {@code TempTable} or temp-table {@code Buffer} object.
*
* @param source
* Data source.
* @param override
* If {@code true} the default mapping between XML Schema string and binary data types
* and ABL data types when creating an ABL temp-table schema from an XML Schema will
* be overwritten.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical readXmlSchema(SourceData source, logical override)
{
return readXmlSchema(source, override, new character(), new character());
}
/**
* Reads XML Schema from an XML document and uses that schema to either create a schema for a
* {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
* {@code TempTable} or temp-table {@code Buffer} object.
*
* @param source
* Data source.
* @param override
* If {@code true} the default mapping between XML Schema string and binary data types
* and ABL data types when creating an ABL temp-table schema from an XML Schema will
* be overwritten.
* @param mapping
* A comma-delimited list of field name, data type pairs. This allows to specify the
* data type for a specific field.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical readXmlSchema(SourceData source, logical override, Text mapping)
{
return readXmlSchema(source, override, mapping, new character());
}
/**
* Reads XML Schema from an XML document and uses that schema to either create a schema for a
* {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
* {@code TempTable} or temp-table {@code Buffer} object.
*
* @param source
* Data source.
* @param override
* If {@code true} the default mapping between XML Schema string and binary data types
* and ABL data types when creating an ABL temp-table schema from an XML Schema will
* be overwritten.
* @param strMapping
* A comma-delimited list of field name, data type pairs. This allows to specify the
* data type for a specific field.
* @param verifyMode
* Specifies how the XML will be verified against the schema. Valid values: "LOOSE" or
* "STRICT".
*
* @return {@code true} if operation is successful.
*/
@Override
public logical readXmlSchema(SourceData source,
logical override,
Text strMapping,
Text verifyMode)
{
Map<String, String> mapping = null;
if (strMapping != null)
{
String javaStrMapping = strMapping.toJavaType();
if (javaStrMapping != null)
{
mapping = new HashMap<>();
String[] splits = javaStrMapping.split(",");
int len = splits.length;
if (len == 0 || len % 2 == 1)
{
ErrorManager.recordOrShowError(13200);
// Odd number of values in field-type-mapping argument.
return logical.of(false);
}
// no other validation at this time
for (int k = 0; k < len; k += 2)
{
mapping.putIfAbsent(splits[k], splits[k + 1]); // do not overwrite if field is duplicated
}
}
}
XmlImport xmlImport = new XmlImport(source, null, null,
!override.isUnknown() && override.booleanValue(),
mapping,
verifyMode == null ? null : verifyMode.toJavaType());
return logical.of(xmlImport.importTempTableSchema(this));
}
/**
* Implementation of the READ-JSON method. Reads JSON data from the specified data source into
* this object.
* <p>
* Defaults applied:
* <ul>
* <li>{@code MERGE} read mode</li>
* </ul>
*
* @param source
* Data source.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical readJson(SourceData source)
{
return readJson(source, new character("MERGE"));
}
/**
* Implementation of the READ-JSON method. Reads JSON data from the specified data source into
* this object.
*
* @param source
* Data source.
* @param readMode
* Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
* EMPTY}, {@code MERGE}, or {@code REPLACE}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical readJson(SourceData source, character readMode)
{
if (!source.configureSupportedSources(SourceData.SD_READ_JSON,
LegacyResource.TEMP_TABLE + " widget"))
{
return logical.of(false);
}
if (_dynamic() && !_prepared())
{
// does not have a table structure. We will query the source and build the object. Then the 'read'
// will go on as usual.
if (Boolean.FALSE.equals(
JsonImport.createTableStructure((TempTableBuilder) this, source)))
{
return logical.of(false);
}
}
RecordBuffer buffer = name == null ? null :
((BufferImpl) defaultBufferHandleNative()).buffer();
// sanity
if (buffer != null && !buffer.isTemporary())
{
ErrorManager.recordOrShowError(15378);
// JSON methods only allowed on TEMP-TABLE buffers.
return logical.of(false);
}
// normalize parameters
JsonImport jsonImport = new JsonImport(source, readMode.toJavaType());
boolean success;
try
{
// perform import
success = buffer == null ?
jsonImport.readTable((TempTableBuilder)this, RecordBuffer::loadRecord) :
jsonImport.readTable((TemporaryBuffer) buffer, RecordBuffer::loadRecord);
}
catch (PersistenceException exc)
{
// TODO: log
success = false;
}
return logical.of(success);
}
/**
* Implementation of the WRITE-JSON method. Writes JSON data from this object into the
* specified data target.
* <p>
* Defaults applied:
* <ul>
* <li>Unformatted text</li>
* <li>UTF-8 encoding</li>
* <li>Do not omit temp-table fields which contain their initial values</li>
* <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
* JsonArray} object.
* <li>No before-image data written</li>
* </ul>
*
* @param target
* Data target.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeJson(TargetData target)
{
return writeJson(target,
logical.of(false),
new character(),
logical.of(false),
logical.of(false),
logical.of(false));
}
/**
* Implementation of the WRITE-JSON method. Writes JSON data from this object into the
* specified data target.
* <p>
* Defaults applied:
* <ul>
* <li>UTF-8 encoding</li>
* <li>Do not omit temp-table fields which contain their initial values</li>
* <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
* JsonArray} object.
* <li>No before-image data written</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the JSON to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeJson(TargetData target, logical formatted)
{
return writeJson(target,
formatted,
new character(),
logical.of(false),
logical.of(false),
logical.of(false));
}
/**
* Implementation of the WRITE-JSON method. Writes JSON data from this object into the
* specified data target.
* <p>
* Defaults applied:
* <ul>
* <li>Do not omit temp-table fields which contain their initial values</li>
* <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
* JsonArray} object.
* <li>No before-image data written</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the JSON to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeJson(TargetData target, logical formatted, character encoding)
{
return writeJson(target,
formatted,
encoding,
logical.of(false),
logical.of(false),
logical.of(false));
}
/**
* Implementation of the WRITE-JSON method. Writes JSON data from this object into the
* specified data target.
* <p>
* Defaults applied:
* <ul>
* <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
* JsonArray} object.
* <li>No before-image data written</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the JSON to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
* @param omitInitialValues
* {@code True} to omit data for fields whose values match their initial values.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeJson(TargetData target,
logical formatted,
character encoding,
logical omitInitialValues)
{
return writeJson(target,
formatted,
encoding,
omitInitialValues,
logical.of(false),
logical.of(false));
}
/**
* Implementation of the WRITE-JSON method. Writes JSON data from this object into the
* specified data target.
* <p>
* Defaults applied:
* <ul>
* <li>No before-image data written</li>
* </ul>
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the JSON to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
* @param omitInitialValues
* {@code True} to omit data for fields whose values match their initial values.
* @param omitOuterObject
* {@code True} to omit data for the outermost object from the output.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeJson(TargetData target,
logical formatted,
character encoding,
logical omitInitialValues,
logical omitOuterObject)
{
return writeJson(target,
formatted,
encoding,
omitInitialValues,
omitOuterObject,
logical.of(false));
}
/**
* Implementation of the WRITE-JSON method. Writes JSON data from this object into the
* specified data target.
*
* @param target
* Data target.
* @param formatted
* {@code True} to format the JSON to make it more readable; {@code false} to write it
* as unformatted text. Unknown value is treated as {@code false}.
* @param encoding
* Name of the character encoding to use.
* @param omitInitialValues
* {@code True} to omit data for fields whose values match their initial values.
* @param omitOuterObject
* {@code True} to omit data for the outermost object from the output.
* @param writeBeforeImage
* {@code True} to write before-image data and error information, else {@code false}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeJson(TargetData target,
logical formatted,
character encoding,
logical omitInitialValues,
logical omitOuterObject,
logical writeBeforeImage)
{
if (defaultBufferHandle() == null)
{
// throw something?
return logical.of(false);
}
Buffer buf = defaultBufferHandleNative();
if (buf == null)
{
return logical.of(false);
}
// buffers will be flushed in [buf.writeJson()]
return buf.writeJson(target, formatted, encoding, omitInitialValues, omitOuterObject, writeBeforeImage);
}
/**
* Obtain the name of the XML element or attribute representing the target object (a {@code TEMP-TABLE})
* name in an XML document. If not set the {@code name} is returned.
*
* @return the name of the XML element for this object.
*/
@Override
public character getXmlNodeName()
{
return defaultBuffer.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)
{
defaultBuffer.setXmlNodeName(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)
{
setXmlNodeName(name.toJavaType());
}
/**
* 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()
{
handle.invalidAttribute("XML-DATA-TYPE", false, LegacyResource.TEMP_TABLE);
return new character();
}
/**
* 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)
{
handle.invalidAttribute("XML-DATA-TYPE", true, LegacyResource.TEMP_TABLE);
}
/**
* 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)
{
handle.invalidAttribute("XML-DATA-TYPE", true, LegacyResource.TEMP_TABLE);
}
/**
* 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()
{
handle.invalidAttribute("XML-NODE-TYPE", false, LegacyResource.TEMP_TABLE);
return new character();
}
/**
* Allows specifying how 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)
{
handle.invalidAttribute("XML-NODE-TYPE", true, LegacyResource.TEMP_TABLE);
}
/**
* Allows specifying how 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)
{
handle.invalidAttribute("XML-NODE-TYPE", true, LegacyResource.TEMP_TABLE);
}
/**
* Getter for {@code NUM-REFERENCES} attribute: obtains number of references to this {@code TempTable}
* object that is defined as a parameter to which reference-only objects are bound.
* <p>
* @return number of references to this object.
*/
@Override
public integer numReferences()
{
UnimplementedFeature.missing("TEMP-TABLE:NUM-REFERENCES");
return new integer();
}
/**
* Returns the value of the NAMESPACE-URI attribute.
*
* @return the value of the NAMESPACE-URI attribute.
*/
@Override
public character namespaceURI()
{
return defaultBuffer.namespaceURI();
}
/**
* Setter for NAMESPACE-URI attribute.
*
* @param uri
* The new value of the NAMESPACE-URI attribute.
*/
@Override
public void namespaceURI(String uri)
{
if (uri == null)
{
ErrorManager.recordOrShowError(4083, " NAMESPACE-URI", LegacyResource.TEMP_TABLE + " widget");
return;
}
defaultBuffer.namespaceURI(uri);
}
/**
* Setter for NAMESPACE-URI attribute.
*
* @param uri
* The new value of the NAMESPACE-URI attribute.
*/
@Override
public void namespaceURI(character uri)
{
namespaceURI(uri.toJavaType());
}
/**
* Returns the value of the NAMESPACE-PREFIX attribute. Only for ELEMENT or ATTRIBUTE, for other
* attributes return unknown value.
*
* @return the current value of the the NAMESPACE-PREFIX attribute.
*/
public character namespacePrefix()
{
return defaultBuffer.namespacePrefix();
}
/**
* Setter for NAMESPACE-PREFIX attribute.
*
* @param prefix
* The new value of the NAMESPACE-PREFIX attribute.
*/
@Override
public void namespacePrefix(String prefix)
{
if (prefix == null)
{
ErrorManager.recordOrShowError(4083, "NAMESPACE-PREFIX", LegacyResource.TEMP_TABLE + " widget");
return;
}
defaultBuffer.namespacePrefix(prefix);
}
/**
* Setter for NAMESPACE-PREFIX attribute.
*
* @param prefix
* The new value of the NAMESPACE-PREFIX attribute.
*/
@Override
public void namespacePrefix(character prefix)
{
namespacePrefix(prefix.toJavaType());
}
/**
* Checks whether the data in the data source has been modified.
*
* @return {@code true} when the data has been modified.
*/
@Override
public logical isDataSourceModified()
{
return logical.of(dataSourceModified);
}
/**
* Manually mark the data in the target structure as modified.
*
* @param mod
* {@code true} when the data is marked as modified and {@code false} otherwise.
*/
@Override
public void setDataSourceModified(logical mod)
{
if (mod == null || mod.isUnknown())
{
unableToAssignUnknown("DATA-SOURCE-MODIFIED", "DATASET widget");
return;
}
setDataSourceModified(mod.getValue());
}
/**
* Manually mark the data in the target structure as modified.
*
* @param mod
* {@code true} when the data is marked as modified and {@code false} otherwise.
*/
@Override
public void setDataSourceModified(boolean mod)
{
this.dataSourceModified = mod;
}
/**
* 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()
{
return defaultBuffer.getSerializeName();
}
/**
* 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)
{
defaultBuffer.setSerializeName(sName);
}
/**
* Implementation of the read access of the {@code LABEL}.
*
* @return The current value of the {@code LABEL}.
*/
public character label()
{
ErrorManager.recordOrShowError(4052, "LABEL", "queryable", LegacyResource.TEMP_TABLE + " widget");
return character.UNKNOWN;
}
/**
* Implementation of the write access of the {@code LABEL}.
*
* @param sName
* the new value for the {@code LABEL}.
*/
public void label(Text sName)
{
ErrorManager.recordOrShowError(4052, "LABEL", "setable" /*sic*/, LegacyResource.TEMP_TABLE + " widget");
}
/**
* Implementation of the write access of the {@code LABEL}.
*
* @param sName
* The new value for the {@code LABEL}.
*/
public void label(String sName)
{
ErrorManager.recordOrShowError(4052, "LABEL", "setable" /*sic*/, LegacyResource.TEMP_TABLE + " widget");
}
/**
* Constructs and returns a string containing the P4GL schema definition of the table. The result is not
* cached so this method is to be used only for debug.
*
* @return a string containing the P4GL legacy schema definition of the table.
*/
public String tableDefinition()
{
WrappedResource r = this.defaultBuffer;
if (r != null && r.valid())
{
return ((BufferImpl) r).tableDefinition();
}
return "(unknown)";
}
/**
* Validate all buffers from this table, in the reverse order they were created. It is guaranteed that the
* default buffer is the last one processed.
*
* @return {@code true} on success.
*
* @throws ErrorConditionException
* If any of the buffer fails validation.
*/
public boolean validateAllBuffers()
{
// We use a LinkedHashSet for fast remove performance, but this set stores its elements in the
// opposite order we need to iterate here. So, we create an intermediate list and iterate it backwards.
// This operation will be slower than a simple iteration because of having to add all the elements to
// the intermediate list, but this method should be invoked much less frequently than removeBuffer,
// so the tradeoff is deemed acceptable.
List<Buffer> list = new ArrayList<>(allBuffers);
ListIterator<Buffer> iter = list.listIterator(list.size());
while (iter.hasPrevious())
{
Buffer buffer = iter.previous();
buffer.validate();
}
// if we reach this point, the content of all buffers passed the validation.
return true;
}
/**
* Get the collection of table fields.
*
* @return the collection of table fields.
*/
public Collection<P2JField> getFields()
{
return Collections.emptyList();
}
/**
* Register a new buffer with this table.
*
* @param buf
* The buffer to be registered. Must not be {@code null}.
*/
void registerBuffer(Buffer buf)
{
allBuffers.add(buf);
}
/**
* Deregister a buffer which has gone out of scope.
*
* @param buf
* The buffer to be deregistered. Must not be {@code null}.
*/
void deregisterBuffer(Buffer buf)
{
allBuffers.remove(buf);
}
/**
* Get default buffer for the static temp-table.
*
* @return default buffer for the static temp-table.
*/
TemporaryBuffer defaultBuffer()
{
if (defaultBuffer == null)
{
return null;
}
return (TemporaryBuffer) RecordBuffer.get((DataModelObject) defaultBuffer);
}
/**
* Checks whether changes in this temp-table is enabled for a {@code DataSet}.
*
* @return The current value for {@code TRACKING-CHANGES} as described above.
*/
boolean _isTrackingChanges()
{
return trackingChanges;
}
/**
* Determine if an ADD-* function was called for the table (CLEAR function resets the state).
* Makes sense only for unprepared tables.
*
* @return <code>true</code> if an ADD-* function was called for the table.
*/
protected abstract boolean addFunctionWasCalled();
/**
* Read XML/JSON data from the input source and store it in the just created empty TEMP-TABLE.
*
* @param tableName
* table name
* @param reader
* XML/JSON source reader and table creator.
*
* @return {@code true} on success.
*
* @throws PersistenceException
* if there is any error reading or storing XML/JSON data.
*/
public abstract boolean readTable(String tableName, TableReader reader)
throws PersistenceException;
/**
* Delete all the buffers (master and slave) associated with this temporary table (static or
* dynamic).
*/
protected void deleteAllBuffers()
{
TemporaryBuffer defaultBuffer = defaultBuffer();
if (defaultBuffer == null)
{
return;
}
Consumer<TemporaryBuffer> cleanup = (buff) ->
{
Buffer buffer = (Buffer) buff.getDMOProxy();
if (buffer.valid())
{
pm.deleteResource(buffer);
}
// clean up the domains for this buffer
buff.cleanupSlaveBuffers();
};
Set<TemporaryBuffer> allBuffers = defaultBuffer.getAllSlaveBuffers();
if (allBuffers != null)
{
allBuffers.add(defaultBuffer);
for (TemporaryBuffer buff : allBuffers)
{
cleanup.accept(buff);
}
}
else
{
cleanup.accept(defaultBuffer);
}
}
/**
* Worker for {@code hasRecords}. Checks whether this temp-table contains any records.
*
* @return true if this temp-table has records.
*/
protected boolean _hasRecords()
{
TemporaryBuffer defBuffer = defaultBuffer();
Boolean hasRecords = defBuffer.fastHasRecords();
// if we detected that there is no record the fast-way, fall-back
// to the bufferManager usage
if (hasRecords != null && hasRecords)
{
return hasRecords;
}
Persistence persistence = defBuffer.getPersistence();
// couldn't detect if there is any record in the fast-way
if (hasRecords == null)
{
StringBuilder sb = new StringBuilder("select 1 from ");
sb.append(defBuffer.getTable());
sb.append(" where ");
sb.append(TemporaryBuffer.MULTIPLEX_FIELD_NAME);
sb.append(" = ?");
Object[] args = new Object[] {defBuffer.getMultiplexID()};
try
{
Object res = persistence.getSingleSQLResult(sb.toString(), Persistence.TEMP_CTX, args);
if (res != null)
{
return true;
}
}
catch (PersistenceException e)
{
ErrorManager.recordOrThrowError(e);
return false;
}
}
// Iterate the buffers of the current table and check for any active buffer.
Iterator<Buffer> iter = allBuffers.iterator();
while (iter.hasNext())
{
RecordBuffer next = ((BufferReference) iter.next()).buffer();
if (defBuffer.bufferManager.isActiveBuffer(next, persistence, true) &&
next.getParentTable().equals(this))
{
return true;
}
}
return false;
}
/**
* Display "CREATE-LIKE must be the first method on a newly CREATEd TEMP-TABLE, or a newly
* CLEARed one" error message.
*/
protected void displayCreateLikeNotFirst()
{
ErrorManager.recordOrShowError(9041,
"CREATE-LIKE must be the first method on a newly CREATEd TEMP-TABLE," +
" or a newly CLEARed one",
true,
false);
}
/**
* Display "Definitional TEMP-TABLE methods (ADD-NEW/LIKE) may not be called after
* TEMP-TABLE-PREPARE has been called unless a CLEAR is done first, and may never be called on
* a DEFINED static table" error message.
*/
protected void displayPrepared()
{
ErrorManager.recordOrShowError(9042,
"Definitional TEMP-TABLE methods (ADD-NEW/LIKE) may not be called after " +
"TEMP-TABLE-PREPARE has been called unless a CLEAR is done first, and may never " +
"be called on a DEFINED static table",
true,
false);
}
/**
* Display "TEMP-TABLE is already prepared--ignoring" error message.
*/
protected void displayPreparedIgnoring()
{
ErrorManager.recordOrShowError(9043, "TEMP-TABLE is already prepared--ignoring", true, false);
}
/**
* Display "Unable to assign UNKNOWN value to attribute UNDO on TEMP-TABLE widget" error
* message.
*/
protected void displayUnableAssignUnknownUndo()
{
ErrorManager.recordOrShowError(4083,
"Unable to assign UNKNOWN value to attribute UNDO on TEMP-TABLE widget",
true,
true);
}
/**
* Display "CLEAR method may not be called on a static temp-table object, nor on a dynamic one
* with records; use buffer:EMPTY-TEMP-TABLE" error message.
*/
protected void displayCannotClear()
{
ErrorManager.recordOrShowError(9085,
"CLEAR method may not be called on a static temp-table object, " +
"nor on a dynamic one with records; use buffer:EMPTY-TEMP-TABLE",
true,
false);
}
/**
* Display "TEMP-TABLE must be PREPARED before referencing NAME/DEFAULT-BUFFER-HANDLE etc"
* error message.
*/
protected void displayNotPrepared()
{
ErrorManager.recordOrShowError(9040,
"TEMP-TABLE must be PREPARED before referencing NAME/DEFAULT-BUFFER-HANDLE etc",
true,
false);
}
/**
* Display "Attempt to reference uninitialized temp-table" error message.
*/
protected void displayUninitialized()
{
ErrorManager.recordOrShowError(12378,
"Attempt to reference uninitialized temp-table",
true,
false);
}
/**
* Display "Unable to CREATE-LIKE for TEMP-TABLE--try using a smaller source table" error
* message.
*/
protected void displayUnableCreateLike()
{
ErrorManager.recordOrShowError(9047,
"Unable to CREATE-LIKE for TEMP-TABLE--try using a smaller source table",
true,
false,
false, // ERROR-STATUS:ERROR is not set
false);
}
/**
* Display "Unable to ADD-FIELDS-FROM for TEMP-TABLE" error message.
*/
protected void displayUnableAddFieldsFrom()
{
ErrorManager.recordOrShowError(9049, "Unable to ADD-FIELDS-FROM for TEMP-TABLE", true, false);
}
/**
* Display "TEMP-TABLE method could not find LIKE table from BUFFER handle" error message.
*/
protected void displayCouldNotFindLikeTableFromHandle()
{
ErrorManager.recordOrShowError(9058,
"TEMP-TABLE method could not find LIKE table from BUFFER handle",
true,
false);
}
/**
* Display "COPY-TEMP-TABLE must have valid buffer/temp-table handle as first argument" error
* message.
*/
protected void displayCopyTempTableInvalidFirstArg()
{
ErrorManager.recordOrShowError(12339,
"COPY-TEMP-TABLE must have valid buffer/temp-table handle as first argument",
true,
false);
}
/**
* Display "COPY-TEMP-TABLE target temp-table must be either PREPARED or CLEAR, but not
* half-defined" error message.
*/
protected void displayCopyTempTableHalfDefined()
{
ErrorManager.recordOrShowError(12341,
"COPY-TEMP-TABLE target temp-table must be either PREPARED or CLEAR," +
" but not half-defined",
true,
false);
}
/**
* Get record buffer by buffer or temp-table handle. Displays error if the handle is not valid
* or of a wrong type.
*
* @param hbuf
* Buffer or temp-table handle.
* @param callingFunction
* Calling function. Affects error messaging.
*
* @return target record buffer or <code>null</code> if the case of error.
*/
protected RecordBuffer getRecordBufferByHandle(handle hbuf, CallingFunction callingFunction)
{
if (!hbuf._isValid())
{
switch (callingFunction)
{
case COPY_TEMP_TABLE:
displayCopyTempTableInvalidFirstArg();
break;
case ADD_FIELDS_FROM:
displayUnableAddFieldsFrom();
break;
case CREATE_LIKE:
displayUnableCreateLike();
break;
}
return null;
}
Object handleValue = hbuf.getResource();
Class handleClass = handleValue.getClass();
Buffer buffer;
if (Buffer.class.isAssignableFrom(handleClass))
{
buffer = (Buffer) handleValue;
}
else if (TempTable.class.isAssignableFrom(handleClass))
{
TempTable tempTable = (TempTable) handleValue;
handle defaultBufferHandle = tempTable.prepared().booleanValue() ?
tempTable.defaultBufferHandle() :
null;
if (defaultBufferHandle == null)
{
switch (callingFunction)
{
case COPY_TEMP_TABLE:
displayCopyTempTableInvalidFirstArg();
break;
case ADD_FIELDS_FROM:
displayCouldNotFindLikeTableFromHandle();
displayUnableAddFieldsFrom();
break;
case CREATE_LIKE:
displayCouldNotFindLikeTableFromHandle();
displayUnableCreateLike();
break;
}
return null;
}
buffer = (Buffer) defaultBufferHandle.getResource();
}
else
{
switch (callingFunction)
{
case COPY_TEMP_TABLE:
displayCopyTempTableInvalidFirstArg();
break;
case ADD_FIELDS_FROM:
displayCouldNotFindLikeTableFromHandle();
displayUnableAddFieldsFrom();
break;
case CREATE_LIKE:
displayCouldNotFindLikeTableFromHandle();
displayUnableCreateLike();
break;
}
return null;
}
return ((BufferReference) buffer).buffer();
}
/**
* Returns {@code true} if this temp-table is in prepared state (after a successful call to
* {@link #tempTablePrepare}).
* This method implements the read-only PREPARED getter attribute from 4GL.
*
* @return {@code true} if table is in PREPARED state.
*/
public abstract boolean _prepared();
/**
* Check whether this TEMP-TABLE is in CLEAR state: I.e. the temp-table is first created or immediately
* after the CLEAR() method is applied. The other two state of a temp-table are: UNPREPARED and PREPARED.
* The UNPREPARED state between the first definitional method has been applied and before the
* TEMP-TABLE-PREPARE() method is applied.
* <p>
* This method is FWD-internal. The programmer can check whether the temp-table is in an UNPREPARED or
* PREPARED state by checking the PREPARED attribute, but the CLEAR state is transparent.
*
* @return {@code true} if this table is in CLEAR state.
*/
public abstract boolean _clear();
/**
* Sets the origin table. Implementation internal method. The origin table is the table where
* the content of this table came from (source of the {@code GET-CHANGES method}).
*
* @param origin
* The origin table.
*/
void setOriginTable(TempTable origin)
{
this.originTable = origin;
}
/**
* Make sure content from all buffers of this temp-table are flushed. If the flush operation fails (usually
* because of failing validation) the message is displayed for first buffer, as usual and {@code false}
* is returned.
* <p>
* All buffers from both this table and its peer table will be flushed (in other words, the after table
* and the before table, if it exists, will flush all buffers).
*
* @return {@code true} on success.
*/
boolean flushBuffers()
{
Function<Set<Buffer>, Boolean> flush = (allBuffers) ->
{
for (Buffer aBuff : allBuffers)
{
try
{
((BufferImpl) aBuff).buffer().flush();
}
catch (ValidationException e)
{
ErrorManager.recordOrShowError(e.getNumber(), e.getMessage(), true, true);
return false;
}
}
return true;
};
if (!flush.apply(allBuffers))
{
return false;
}
if (peerTable != null && !flush.apply(((AbstractTempTable) peerTable).allBuffers))
{
return false;
}
return true;
}
/**
* Computes the temp-table type and if it is an {@code AFTER_TABLE} or a {@code BEFORE_TABLE}
* the peer (complementary) buffer is also stored in {@code peerTable}.
*
* @return The computed type (which is the value of the {@code beforeTableType}).
*/
private BeforeType computeTableType()
{
if (beforeTableType != BeforeType.UNKNOWN)
{
return beforeTableType; // this line should not execute
}
if (!this._dynamic())
{
RecordBuffer defBuffer = defaultBuffer();
Class<?> defType = defBuffer.getDefiningType();
DmoMeta dmoInfo = defBuffer.getDmoInfo();
String beforeTable = dmoInfo.beforeTable;
if (!beforeTable.isEmpty())
{
peerTable = defBuffer.bufferManager.getStaticTempTable(defType, beforeTable);
beforeTableType = BeforeType.AFTER;
return beforeTableType;
}
else
{
String afterTable = dmoInfo.afterTable;
if (!afterTable.isEmpty())
{
peerTable = defBuffer.bufferManager.getStaticTempTable(defType, afterTable);
beforeTableType = BeforeType.BEFORE;
return beforeTableType;
}
}
beforeTableType = BeforeType.SIMPLE;
}
else
{
// this should also not happen, because the type is set by TempTableBuilder after the
// table was prepared
}
return beforeTableType;
}
/**
* Implementation of bulk delete all records associated with the {@code TempTable}.
*
* @throws ErrorConditionException
* If an error occurs performing the bulk delete operation.
*/
void deleteAllImpl()
{
TemporaryBuffer defBuffer = defaultBuffer();
if (defBuffer == null)
{
return; // nothing to delete, this dynamic TEMP-TABLE was not prepared yet.
}
defBuffer.deleteAll();
}
/**
* Resolve the mutable information for the given field. If this is for a getter call, return
* <code>null</code> if the info has not yet been added to {@link #fieldInfo}.
*
*
* @param fieldLegacyName
* The legacy field name, already lowercased.
* @param setter
* Flag indicating if this call is for a setter or a getter call.
* @param field
* The function to calculate the mutable field info.
*
* @return The mutable field info from {@link #fieldInfo} or calculated, or <code>null</code> in case of
* a getter call and this info has not yet been calculated.
*/
MutableFieldInfo getMutableInfo(String fieldLegacyName,
boolean setter,
BiFunction<TempTable, String, MutableFieldInfo> field)
{
if (!setter)
{
TableMapper.MutableFieldInfo info = (fieldInfo == null ? null : fieldInfo.get(fieldLegacyName));
return info;
}
if (fieldInfo == null)
{
fieldInfo = new HashMap<>();
}
TableMapper.MutableFieldInfo info = fieldInfo.get(fieldLegacyName);
if (info == null)
{
fieldInfo.put(fieldLegacyName, info = field.apply(this, fieldLegacyName));
}
return info;
}
}