DataSet.java
/*
** Module : DataSet.java
** Abstract : Abstracts the DATASET ABL structure.
**
** Copyright (c) 2019-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM 20190403 Created initial version with stubbed methods.
** 002 OM 20190508 Ported createDynamicDataSet().
** CA 20190513 DATASET can use the '::' operator. Added 'asHandle'.
** 003 OM 20190508 Added runtime implementation for ABL methods and attributes.
** HC 20190707 Implemented read(dataSet) and read(tempTable) methods of JsonObject legacy
** class.
** 004 CA 20190712 Added missing associate API.
** OM 20190716 Fixed handling of OUTPUT dataset parameters.
** CA 20190720 Some fixes for BY-REFERENCE and OUTPUT parameters. These fixes are not proved
** and BY-REFERENCE, OUTPUT and DELETE combinations are most likely related to
** NUM-REFERENCES attribute.
** OM 20190726 Callback DataSet handles are sent BY_REFERENCE.
** CA 20190728 If a callback raises an ERROR condition, invokeCallback must return false.
** Also, any FILL operation must be aborted.
** For BY-REFERENCE/REFERENCE-ONLY transfer the buffer's record, too.
** EVL 20190729 Fix from CA to skip same reference processing in associate() method.
** OM 20190731 Added REFERENCE-ONLY and NUM-REFERENCES support.
** CA 20190812 Allow this class to be mutable, to allow switching between resources, in cases
** of BY-REFERENCE or REFERENCE-ONLY. WARNING: it is not allowed to access
** instance fields, if they are in another instance than 'this' reference!
** Use the 'ref()' API to get the runtime instance or add the field to an
** accessor method.
** CA 20190826 Added basic support for 'inverse binding', from callee to caller, via the BIND
** parameter option.
** OM 20190909 Clear the ERROR flag in some methods.
** 005 CA 20191119 Track the executing legacy class, to be able to register any buffer to its
** source definition class.
** CA 20200110 More fixes for DATASET parameters and resource delete.
** 006 CA 20200427 Allow a DATASET to be read from JSON argument, in case of a remote REST call.
** 007 OM 20201030 Invalid attribute API support for getters/setters.
** OM 20201118 Extracted SERIALIZE-HIDDEN into independent interface.
** OM 20201120 Fixed of methods and attributes implementations.
** OM 20201218 Fixed implementation for error and rejected attributes/hidden fields.
** OM 20210115 OE quirk/bug: allowed duplicate relations for same buffer pair.
** OM 20210219 bufferHandle() accepts int64 expression parameters.
** OM 20210309 Do not use DmoMeta as key in TableMapper because temp-tables may share the same
** DmoMeta instance.
** ECF 20210511 Replaced DynamicTablesHelper.normalizeName with TextOps.rightTrimLower.
** CA 20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
** initialized at the method's execution, and not at the caller's arguments.
** SVL 20210614 Remote OUTPUT TABLE/DATASET-HANDLE parameters are treated like INPUT-OUTPUT parameters
** with no input data rows, which allows to access table structure if non-unknown
** table/dataset handle has been passed from the calling side.
** OM 20210809 Completed implementation of SERIALIZE-ROW|WRITE-JSON}(JsonObject|JsonArray).
** CA 20210831 For GET-CHANGES, only the tables which have a before-table are included - the changes
** are available even if the TRACKING-CHANGES was disabled before this call.
** CA 20210928 Implicit deletion of buffers (when the dataset gets deleted) is done only for dynamic
** buffers associated with dynamic temp-tables.
** CA 20211005 DATASET can't be added to unnamed pools.
** OM 20211006 Unbinding/output copy occur on procedure return instead of TM's scope finished.
** CA 20211112 Allow the DATASET and TABLE parameters for remote SOAP calls to be transferred via XML
** (so that relations, schema, etc is done on the server-side).
** CA 20211227 Fixed the DATASET:NAME when neither name or serialize-name are set - defaults to ProDataSet.
** CA 20220105 readFromXml must force UTF-8 as codepage at the longchar created to hold the XML string.
** CA 20220106 Fixed CREATE-LIKE when relations are involved, their active flag was not being copied
** properly.
** OM 20220422 Allow Xml/JsonImport to load records into buffer without exposing package private methods.
** OM 20221003 Added missing STREAM targets for JSON/XML serializations.
** CA 20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
** allow caching of the resolved target.
** CA 20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration
** is now specific to each type of scopeable. For each case, the block will be registered
** for scope support (for that particular scopeable) only when the scopeable is 'active'
** (i.e. unnamed streams or accumulators are used). This allows a lazy registration of
** scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
** and every block.
** CA 20220923 Internal var definitions must be done via TypeFactory and not UndoableFactory.
** CA 20221006 UNIQUE-ID is kept as Java type instead of BDT. Refs #6827
** CA 20221031 Added JMX instrumentation for DATASET[-HANDLE] parameter processing.
** OM 20221206 Reworked REFERENCE-ONLY attribute.
** CA 20220524 The scope ends when the procedure returns, not on 'scopeFinished', to allow parameter
** processing.
** GES 20210501 Upgraded dataset parameter processing to match API changes. Reworked associate() and
** createDynamicDataSet() to remove unnecessary arguments.
** CA 20220601 Added 'derefPoly', emitted when '::' is used as r-value in a dynamic extent assignment.
** OM 20220604 Improved dereferenciation support.
** CA 20220606 Fixed COPY-DATASET, automatically create a before-table in the destination if the
** destination temp-table is dynamic. Raise error 12753 only if the source before-table is
** not empty and the destination table is static without a before-table.
** CA 20220608 Fixed CREATE-LIKE, copy the temp-table options (like XML-NODE-NAME), too.
** CA 20220618 Automatically delete datasets created for remote parameters.
** CA 20220704 A bound REFERENCE-ONLY dataset must act as a normal dataset if used as an OUTPUT parameter.
** No instance field can be directly accessed from static methods, as the dataset instances
** are proxied (added 'addOutputParam' method to fix 'outputParams' field access).
** OM 20220609 Fixed implementation of several 4GL attributes and methods.
** CA 20220727 Fixed OO dataset/table parameters (at the method definition and method call) when there is
** an APPEND option.
** CA 20220918 Automatically delete top-nav queries created dynamically by the DATASET.
** CA 20221031 Added JMX instrumentation for DATASET[-HANDLE] parameter processing.
** IAS 20221108 Fixed support for SCHEMA-MARSHAL == NONE.
** IAS 20221123 Added 'resetNames()' method.
** CA 20221123 Static DATASET instances must not be added to SESSION:FIRST-DATASET chain.
** OM 20221129 Fine-tuning _areRelationsActive() based on recent findings.
** SVL 20230108 Improved performance by replacing some "for-each" loops with indexed "for" loops.
** CA 20230110 Cache the mutable info at the temp-table and not in a separate map at TableMapper. The
** mutable info for a field is calculated only if this info has changed, when creating a
** 'like' dataset.
** CA 20230110 Do not create 'callbacks', 'topNavQueries', 'deleteQueries' or 'refs' until they are
** actually added something.
** CA 20230110 UNIQUE-ID is generated only when is read by the application.
** 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.
** HC 20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
** for performance.
** CA 20230208 Backed out a change in 'OM/20221206 Reworked REFERENCE-ONLY attribute.', as it was causing
** regressions - the 'thisIsParam' flag was changed to 'false' in 'associateImpl'.
** 008 CA 20230303 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.
** 009 IAS 20230405 Fixed FILL support for recursive DATA-RELATION
** Fixed support for recursive DATA-SET.
** Added 'getRelations()' method.
** 010 OM 20230315 The Dataset keeps the original proxy buffers and use these when copying data to output
** parameters.
** 011 IAS 20230407 Fixed 'getChanges'.
** 012 VVT 20230405 Method _getNumTopBuffers made public and minor non-functional changes.
** 013 IAS 20230414 Fixed 'addParentIdRelation' method.
** IAS 20230501 Added 'emergency exit' for WRITE-XML
** 014 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 015 IAS 20230519 Added method for getting NS URI as String.
** 016 AL2 20230613 Use procedure helper where possible to avoid context look-ups.
** 017 CA 20230724 Further reduce context-local usage.
** 018 IT 20230809 temp-table member of a dataset cannot be delete without deleting the dataset first.
** 019 CA 20230917 Backed out H018, the solution is to use 'allowDelete' called for an explicit DELETE OBJECT
** statement.
** 020 OM 20231027 Added getBufferBySerializeName() method.
** 021 HC 20240222 Enabled JMX on FWD Client.
** 022 CA 20231026 Release the buffers after 'readJson' and 'readXml' complete.
** 023 CA 20240320 Avoid BDTs within internal FWD runtime. Replaced iterators with Java 'for', where it
** applies.
** 024 CA 20240322 SET-BUFFERS(handle...) must use the DEFAULT-BUFFER-HANDLE for any TEMP-TABLE in the list.
** 025 CA 20240328 Save and expose the proxy for a static dataset.
** 026 CA 20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** 027 CA 20240404 Static temp-table or dataset resolution by name must be done using the currently executing
** type and the static temp-table/dataset's defining type.
** 028 CA 20240422 Allow the query to be reset to its initialization state, so the same instance can be used
** to execute child buffer FILL operations.
** 029 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.
** 030 CA 20240512 All buffers (including peer buffers) from all temp-tables for this dataset must be flushed
** when using WRITE-XML or WRITE-JSON.
** 031 EAB 20240423 Rewrote getDataAsJson method in order to imitate the result returned by writeJson().
** 032 CA 20240523 Remote requests over appserver allow the parameter dataset to be REFERENCE-ONLY.
** 033 AS 20240729 Added support for bind parameter option in createDynamicDataSetImpl.
** 034 CA 20240809 Skip using JMX timers when JMX_DEBUG flag is not set.
** 035 AS 20240808 registerOutputDataSetHandleCopier only for Proxy in createDynamicDataSetImpl.
** Throw 13009 error only if the source dataset is not a handle and is not a reference.
** 036 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.
** 037 AS 20240924 Throw 4GL accurate errors in isCompatibleWith().
** 038 DDF 20241009 When provided a prefix (even the default one), the table name must not exceed 32
** characters.
** 039 AP 20241017 Always return the bound buffer, if it exists.
** 040 ES 20241017 Added verification for possible misplacement of tables names in pairs-list parameter
** in copyDatasetImpl.
** 041 CA 20241030 Improved 'isCompatibleWith' - do not check if the same DMO is used.
** 042 AS 20241104 Fixed _getNumTopBuffers for recursive relations.
** 043 RNC 20241030 Added support for XML serialization coming from a SOAP request.
** 044 AP 20241210 Improved dataset binding in order to take into account only the BIND on the caller side.
** 045 AP 20250129 Refactored getDataAsJson to use the RecordBufferSerializer and do the actual JSON
** serialization using jackson library.
** 046 ICP 20250123 Used integer.of to leverage caches instances.
** 047 CA 20250321 Added 'setCallback' overloads.
** 048 AS 20250404 Take into account the reposition attribute of the relations for a fill operation.
** 049 AS 20250416 For create-like, use buffer's parent table name instead of the buffer name.
** 050 AS 20250529 Improved isCompatibleWith, use dmo signatures to check for dataset compatibility.
** Improved copyTempTableOptions(), use existingFieldsMap()
** from the dmo info for acquiring source buffer fields.
*/
/*
** 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.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.goldencode.p2j.jmx.*;
import com.goldencode.p2j.persist.TemporaryBuffer.CopyTableMode;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.persist.serial.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.util.*;
import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
/**
* {@code DataSet} objects are collections of one or more related temp-tables. Each temp-table of
* these can attach to a {@code DataSource} object that allows filling of the temp-table from the
* data source, or updating the data source from the temp-table. A {@code DataSet} object can
* optionally contain a set of data relations between the temp-tables.
*/
@LegacyResource(resource = LegacyResource.DATASET)
public class DataSet
extends HandleChain
implements ADMData,
BufferCollection,
CallbackProcedure,
Clearable,
CreateLike,
DataSetChangeable,
DataSetInterface,
DataSourceModifiable,
Dereferenceable,
DynamicResource,
Errorable,
Fillable,
JsonData,
NamedSerializable,
NamespaceURI,
Rejectable,
RefCounter,
SerializeHiddenable,
TempTableDuplicator,
UniqueID,
XmlData,
XmlNode
{
/** The {@code BEFORE-FILL} event string constant in upper case. */
public static final String BEFORE_FILL_EVENT = "BEFORE-FILL";
/** The {@code AFTER-FILL} event string constant in upper case. */
public static final String AFTER_FILL_EVENT = "AFTER-FILL";
/** The call-site for the {@link #BEFORE_FILL_EVENT} event callback. */
private static final InvokeConfig BEFORE_FILL_CALL_SITE = new InvokeConfig();
/** The call-site for the {@link #AFTER_FILL_EVENT} event callback. */
private static final InvokeConfig AFTER_FILL_CALL_SITE = new InvokeConfig();
/** Corresponds to UNIQUE-ID attribute. */
private Long uuid = null;
/** Instrumentation for {@link #associate}. */
private static final NanoTimer STATIC_PARAM = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmDataSetParam);
/** Instrumentation for {@link #createDynamicDataSet}. */
private static final NanoTimer DYN_PARAM = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmDynamicDataSetParam);
/** Logger for objects of this type. */
private static final CentralLogger log = CentralLogger.get(DataSet.class.getName());
/** The value of {@code ADM-DATA} ABL attribute. */
private String admData = null;
/** The value of {@code NAMESPACE-URI} attribute of this {@code DataSet}. */
private String nsUri = null;
/** The namespace prefix of this {@code DataSet}. */
private String nsPrefix = null;
/** The name of the XML node for serialized data of this {@code DataSet}. */
private String xmlNodeName = null;
/** The serialization name of this {@code DataSet}. The default is dataset's name. */
private String serializeName = null;
/** The default value for active attribute for first {@code DataRelation}. */
private boolean defaultActive = true;
/**
* The current value of {@code NAME} attribute. It can be modified for dynamic {@code DataSet}s
* but immutable for static {@code DataSet}s.
*/
private String name = "";
/**
* The current value of boolean {@code ERROR} attribute: indicates whether an error occurred
* during a {@code FILL} or {@code SAVE-ROW-CHANGES} operation, but can also be altered
* programmatically (writable).
*/
private boolean error = false;
/**
* 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;
/** A map with all known callbacks. The keys MUST be in UPPERCASE.*/
private Map<String, CallbackData> callbacks = null;
/**
* Flags the dynamically constructed objects (using {@code CREATE} as opposed to statically
* defined objects, with {@code DEFINE}).
*/
private final boolean dynamic;
/** Flags the {@code REFERENCE-ONLY} {@code DataSet}s. */
protected boolean referenceOnly = false;
/**
* Flags the short period of time when the {@code Builder} constructs the object. During this
* period, some setters are allowed for STATICally build DATASETs.
*/
private boolean buildInProgress = false;
/** Flag indicating the delete was explicitly called for a BY-REFERENCE handle. */
private boolean postponedDelete = false;
/**
* Flags a hidden (name is not written) {@code DataSet} when serialized. This flag overlaps
* with {@code XML-NODE-TYPE} attribute. A value of {@code false} is equivalent to
* {@code XML-NODE-TYPE} set to "ELEMENT" (the default) and {@code false} means that the
* {@code XML-NODE-TYPE} is "HIDDEN".
*/
private boolean serializeHidden = false;
/** Marks the valid objects. When destroyed, the flag is set to {@code false}. */
private boolean deleted = false;
/** Current value of the {@code DATA-SOURCE-MODIFIED} attribute. */
private boolean dataSourceModified = false;
/** The ordered list of buffers contained in this {@code DataSet}. */
private final ArrayList<BufferImpl> buffers = new ArrayList<>();
/** The original proxy object associated with the buffers contained in this {@code DataSet}. */
private final Map<BufferImpl, BufferImpl> proxies = new IdentityHashMap<>();
/** The current set of {@code TOP-NAV-QUERY} values mapped by their buffers. */
private Map<BufferImpl, QueryWrapper> topNavQueries = null;
/** The set of queries created by this dataset which need to be cleaned. */
private Set<QueryWrapper> deleteQueries = null;
/** The list of {@code DataRelation} s from this data set. */
private final ArrayList<DataRelation> relations = new ArrayList<>();
/** Destination {@code DataSet} parameters for output copy if necessary before scope closes. */
private Set<DataSetParameter> outputParams = null;
/** The list of references of this object. */
private Set<StaticDataSet> refs = null;
/** Back-reference to the defining static dataset proxy. */
private StaticDataSet proxyDataSet = null;
/** Flag indicating that the fill query for buffers can be re-used. */
private boolean reuseFillQuery;
/** Flag indicating that this instance will be serialized due to a SOAP request. */
private boolean serializedBySOAP;
/**
* Factory method. Corresponds to {@code CREATE DATASET} statement. Creates a new
* {@code DataSet} and stores the object in the supplied handle.
*
* @param dsHandle
* The destination handle.
*/
public static void create(handle dsHandle)
{
DataSet.create(dsHandle, (character) null);
}
/**
* Factory method. Corresponds to {@code CREATE DATASET} statement. Creates a new
* {@code DataSet} and stores the object in the supplied handle.
*
* @param dsHandle
* The destination handle.
* @param widgetPool
* The widget pool to created the new {@code DataSet} into.
*/
public static void create(handle dsHandle, String widgetPool)
{
DataSet.create(dsHandle, new character(widgetPool));
}
/**
* Factory method. Corresponds to {@code CREATE DATASET} statement. Creates a new
* {@code DataSet} and stores the object in the supplied handle.
*
* @param dsHandle
* The destination handle.
* @param widgetPool
* The widget pool to created the new {@code DataSet} into.
*/
public static void create(handle dsHandle, character widgetPool)
{
// validate widget pool
if (!WidgetPool.validWidgetPool(widgetPool))
{
return;
}
DataSet ds = new DataSet("", true, false); // by default the name is empty string, not unknown
dsHandle.assign(ds);
if (widgetPool != null)
{
// unnamed widget pools are not allowed.
ds.addToPool(widgetPool);
}
}
/**
* Associate two {@code DataSet} with one another, such that all relevant records can be copied
* between them at prescribed times.
*
* @param dsLocal
* The local object relative to the current procedure. If this is an INPUT parameter
* then this is the 'source'. This encapsulates the actual parameter sent by caller.
* @param dsTarget
* A dataset defined in current external procedure.
* @param input
* If {@code true}, records are copied from source table to destination table
* immediately upon invocation of this method, otherwise they are not.
* @param output
* When {@code true}, the records are copied from destination table back to source
* table when the current transaction or subtransaction is committed; otherwise they
* are not.
*
* @see TemporaryBuffer#associate(TableParameter, Temporary, boolean, boolean)
*/
public static void associate(DataSetParameter dsLocal, DataSet dsTarget, boolean input, boolean output)
{
associate(dsLocal, dsTarget, input, output, ParameterOption.NONE);
}
/**
* Associate two {@code DataSet} with one another, such that all relevant records can be copied
* between them at prescribed times.
*
* @param dsSrc
* The local object relative to the current procedure. If this is an INPUT parameter
* then this is the 'source'. This encapsulates the actual parameter sent by caller.
* @param dsTarget
* A dataset defined in current external procedure.
* @param input
* If {@code true}, records are copied from source table to destination table
* immediately upon invocation of this method, otherwise they are not.
* @param output
* When {@code true}, the records are copied from destination table back to source
* table when the current transaction or subtransaction is committed; otherwise they
* are not.
* @param option
* Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
*
* @see TemporaryBuffer#associate(TableParameter, Temporary, boolean, boolean)
*/
public static void associate(DataSetParameter dsSrc,
DataSet dsTarget,
boolean input,
boolean output,
ParameterOption option)
{
associate(dsSrc, dsTarget, input, output, EnumSet.of(option));
}
/**
* Associate two {@code DataSet} with one another, such that all relevant records can be copied
* between them at prescribed times.
*
* @param dsSrc
* The local object relative to the current procedure. If this is an INPUT parameter
* then this is the 'source'. This encapsulates the actual parameter sent by caller.
* @param dsTarget
* A dataset defined in current external procedure.
* @param input
* If {@code true}, records are copied from source table to destination table
* immediately upon invocation of this method, otherwise they are not.
* @param output
* When {@code true}, the records are copied from destination table back to source
* table when the current transaction or subtransaction is committed; otherwise they
* are not.
* @param options
* Dataset parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
*
* @see TemporaryBuffer#associate(TableParameter, Temporary, boolean, boolean)
*/
public static void associate(DataSetParameter dsSrc,
DataSet dsTarget,
boolean input,
boolean output,
EnumSet<ParameterOption> options)
{
dsSrc.setInputMode(input);
dsSrc.setOutputMode(output);
associate(dsSrc, dsTarget, options);
}
/**
* Associate two {@code DataSet} with one another, such that all relevant records can be copied
* between them at prescribed times.
*
* @param dsLocal
* The local object relative to the current procedure. If this is an INPUT parameter
* then this is the 'source'. This encapsulates the actual parameter sent by caller.
* @param dsTarget
* A dataset defined in current external procedure.
*
* @see TemporaryBuffer#associate(TableParameter, Temporary)
*/
public static void associate(DataSetParameter dsLocal, DataSet dsTarget)
{
associate(dsLocal, dsTarget, ParameterOption.NONE);
}
/**
* Associate two {@code DataSet} with one another, such that all relevant records can be copied
* between them at prescribed times.
*
* @param dsSrc
* The local object relative to the current procedure. If this is an INPUT parameter
* then this is the 'source'. This encapsulates the actual parameter sent by caller.
* @param dsTarget
* A dataset defined in current external procedure.
* @param option
* Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
*
* @see TemporaryBuffer#associate(TableParameter, Temporary)
*/
public static void associate(final DataSetParameter dsSrc,
final DataSet dsTarget,
ParameterOption option)
{
associate(dsSrc, dsTarget, EnumSet.of(option));
}
/**
* Associate two {@code DataSet} with one another, such that all relevant records can be copied
* between them at prescribed times.
*
* @param dsSrc
* The local object relative to the current procedure. If this is an INPUT parameter
* then this is the 'source'. This encapsulates the actual parameter sent by caller.
* @param dsTarget
* A dataset defined in current external procedure.
* @param options
* Dataset parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
*
* @see TemporaryBuffer#associate(TableParameter, Temporary)
*/
public static void associate(DataSetParameter dsSrc, DataSet dsTarget, EnumSet<ParameterOption> options)
{
if (FwdServerJMX.JMX_DEBUG)
{
STATIC_PARAM.timer(() -> associateImpl(dsSrc, dsTarget, options));
}
else
{
associateImpl(dsSrc, dsTarget, options);
}
}
/**
* Associate two {@code DataSet} with one another, such that all relevant records can be copied
* between them at prescribed times.
*
* @param dsSrc
* The local object relative to the current procedure. If this is an INPUT parameter
* then this is the 'source'. This encapsulates the actual parameter sent by caller.
* @param dsTarget
* A dataset defined in current external procedure.
* @param options
* Dataset parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
*
* @see TemporaryBuffer#associate(TableParameter, Temporary)
*/
private static void associateImpl(final DataSetParameter dsSrc,
final DataSet dsTarget,
EnumSet<ParameterOption> options)
{
boolean input = dsSrc.isInputMode();
boolean output = dsSrc.isOutputMode();
boolean append = options.contains(ParameterOption.APPEND);
DataSetManager.registerScopeable();
StaticDataSet localDS = (StaticDataSet) dsTarget;
if (!dsSrc.isRemoteParameter())
{
DataSet sourceDS = dsSrc.getOriginalDataSet();
if (dsSrc.isBind() && options.contains(ParameterOption.BIND) &&
sourceDS != null && !localDS.referenceOnly)
{
if (sourceDS.referenceOnly)
{
// bind from the target to the source; this is always permanent
// CA: 'thisIsParam' was changed to 'false' because it was found to cause regressions.
((StaticDataSet) sourceDS).bind(localDS, input, output, true, false);
}
else if (dsSrc.isOriginalDataSetProxy())
{
String pname = localDS.instantiatingProcedure().unwrap().name().toStringMessage();
ErrorManager.recordOrThrowError(13009, pname, localDS.name().toJavaType());
// <program name> BIND modifier not allowed for cases where neither the caller nor the called
// TABLE or DATASET parameter <parameter-name> has been defined REFERENCE-ONLY and neither
// caller nor called parameter is a TABLE-HANDLE or DATASET-HANDLE. (13009)
}
return;
}
if (localDS.referenceOnly)
{
if (dsSrc.isByReference() || dsSrc.isBind() || options.contains(ParameterOption.BIND))
{
// temporary bind dsSrc.getDataset() to dsTarget
localDS.bind(dsSrc.getDataset(), input, output,
dsSrc.isBind() || options.contains(ParameterOption.BIND), false);
{
// TODO: check for both usages of [bind] what happens if the check fails for a
// subsequent parameter in case there are many DataSet: roll back the
// bindings for the former parameter? Keep them?
}
return;
}
if (!localDS.isBound())
{
String pname = localDS.instantiatingProcedure().unwrap().name().toStringMessage();
String dsName = dsSrc.getDataset().name().toJavaType();
ErrorManager.recordOrThrowError(13012, pname, dsName);
// <program-name> BIND or BY-REFERENCE modifier required on RUN, FUNCTION or METHOD invocation
// parameter when called TABLE or DATASET parameter <parameter-name> is REFERENCE-ONLY and is
// still not bound. (13012)
}
// else, we continue with the normal processing, as the local REFERENCE-ONLY dataset is bound
}
if (dsSrc.isByReference())
{
handle h = dsSrc.getDatasetHandle();
if (h.isUnknown())
{
if (input)
{
// TODO: error, how to map the target DATASET?
}
else
{
DataSet targetRefDS = dsTarget.ref();
targetRefDS.addOutputParam(dsSrc);
targetRefDS.pm.executeOnReturn(() -> targetRefDS.performOutputCopy(dsSrc),
WeightFactor.FIRST);
return;
}
}
else
{
// temporarily bind dsSrc.getDataset() to dsTarget
localDS.bind(dsSrc.getDataset(), input, output, false, false);
return;
}
}
// NONE to BIND will do nothing, unless the REFERENCE-ONLY is used at target
// BIND to NONE will error out
else if (dsSrc.isBind() && options.contains(ParameterOption.NONE))
{
String procName = localDS.instantiatingProcedure().unwrap().name().toStringMessage();
String dsName = dsSrc.getDataset().name().toJavaType();
ErrorManager.recordOrThrowError(13009, procName, dsName);
// <program name> BIND modifier not allowed for cases where neither the caller nor the
// called TABLE or DATASET parameter <parameter-name> has been defined REFERENCE-ONLY and
// neither caller nor called parameter is a TABLE-HANDLE or DATASET-HANDLE. (13009)
return;
}
}
if (dsSrc.getDataset() == dsTarget.ref())
{
// same reference, nothing to do; BY-REF/REF-ONLY still needs to do processing related
// to keeping all mutable buffers in sync
return;
}
if (input)
{
if (!dsSrc.isRemoteParameter())
{
DataSet dsSource = dsSrc.getDataset();
if (dsSource != null)
{
int bufCnt = dsSource.numBuffers().intValue();
for (int k = 1; k <= bufCnt; k++)
{
// copy data for each corresponding temp-table
Buffer srcBuf = dsSource.getBufferByIndex(k);
Temporary srcDMO = TemporaryBuffer.getDefaultBuffer(srcBuf.tableHandle());
BufferImpl targetDmo = dsTarget.getBufferByIndex(k);
rowid srcRec = dsTarget.referenceOnly && srcBuf._available() ? srcBuf.rowID() : null;
boolean res = TemporaryBuffer.copyAllRows(
srcRec,
srcDMO,
(Temporary) targetDmo,
append,
TemporaryBuffer.CopyTableMode.INPUT_PARAM_MODE);
if (!res)
{
BlockManager.returnError();
}
}
}
}
else if (dsSrc.getDatasetWrapper().getXmlDataset() != null)
{
String xmlDataset = dsSrc.getDatasetWrapper().getXmlDataset();
readFromXml(xmlDataset, append, new handle(dsTarget));
}
else if (localDS.referenceOnly)
{
// even if we are remote, REFERENCE-ONLY is allowed - in this case, always bind target to source
// argument
localDS.bind(dsSrc.getDataset(), input, output, false, false);
}
else
{
int bufCnt = dsSrc.getDatasetWrapper().getTableDefs().size();
Map<String, TableWrapper> legacyTableToWrapper = null;
boolean fromJson = dsSrc.getDatasetWrapper().isFromJson();
if (fromJson)
{
legacyTableToWrapper = new HashMap<>();
for (int k = 1; k <= bufCnt; k++)
{
TableWrapper wrapper = dsSrc.getDatasetWrapper().getTableWrapper(k - 1);
legacyTableToWrapper.put(wrapper.getTableName().toLowerCase(), wrapper);
}
}
for (int k = 1; k <= bufCnt; k++)
{
// copy data for each corresponding temp-table
BufferImpl targetDmo = dsTarget.getBufferByIndex(k);
TableWrapper wrapper = null;
if (fromJson)
{
AbstractTempTable tt = ((AbstractTempTable) targetDmo.tableHandleNative());
wrapper = legacyTableToWrapper.get(tt._name().toLowerCase());
}
else
{
wrapper = dsSrc.getDatasetWrapper().getTableWrapper(k - 1);
}
if (dsSrc.isRemoteParameter() && !wrapper.isReceivedSchema())
{
RecordBuffer rb = ((BufferReference) targetDmo).buffer();
DmoMeta meta = rb.getDmoInfo();
Iterator<Property> props = meta.getFields(false);
List<PropertyDefinition> pdl = new ArrayList<>();
props.forEachRemaining(p -> pdl.add(new PropertyDefinition(p)));
wrapper.setProperties(pdl);
}
TemporaryBuffer.insertAllRows(
wrapper,
(Temporary) targetDmo,
append,
TemporaryBuffer.CopyTableMode.INPUT_PARAM_MODE,
dsSrc.getParameterIndex());
}
}
}
if (output)
{
DataSet targetRefDS = dsTarget.ref();
targetRefDS.addOutputParam(dsSrc);
targetRefDS.pm.executeOnReturn(() -> targetRefDS.performOutputCopy(dsSrc), WeightFactor.FIRST);
}
}
/**
* Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
* {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
* a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
* constructs.
*
* @param dsParam
* The {@code DataSetParameter}. This is the actual parameter for the routine.
* @param dsHandle
* A handle to store the reference to the new {@code DataSet} during the scope of this
* routine.
*/
public static void createDynamicDataSet(DataSetParameter dsParam, handle dsHandle)
{
createDynamicDataSet(dsParam, dsHandle, ParameterOption.NONE);
}
/**
* Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
* {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
* a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
* constructs.
*
* @param dsParam
* The {@code DataSetParameter}. This is the actual parameter for the routine.
* @param dsHandle
* A handle to store the reference to the new {@code DataSet} during the scope of this
* routine.
* @param input
* If {@code true} then this is an {@code INPUT} or {@code INPUT-OUTPUT} parameter.
* @param output
* If {@code true} then this is an {@code OUTPUT} or {@code INPUT-OUTPUT} parameter.
*/
public static void createDynamicDataSet(DataSetParameter dsParam,
handle dsHandle,
boolean input,
boolean output)
{
createDynamicDataSet(dsParam, dsHandle, input, output, ParameterOption.NONE);
}
/**
* Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
* {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
* a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
* constructs.
*
* @param dsParam
* The {@code DataSetParameter}. This is the actual parameter for the routine (src).
* @param dsHandle
* A handle to store the reference to the new {@code DataSet} during the scope of this
* routine (target).
* @param input
* If {@code true} then this is an {@code INPUT} or {@code INPUT-OUTPUT} parameter.
* @param output
* If {@code true} then this is an {@code OUTPUT} or {@code INPUT-OUTPUT} parameter.
* @param option
* Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
*/
public static void createDynamicDataSet(DataSetParameter dsParam,
handle dsHandle,
boolean input,
boolean output,
ParameterOption option)
{
dsParam.setInputMode(input);
dsParam.setOutputMode(output);
createDynamicDataSet(dsParam, dsHandle, option);
}
/**
* Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
* {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
* a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
* constructs.
*
* @param dsParam
* The {@code DataSetParameter}. This is the actual parameter for the routine (src).
* @param dsHandle
* A handle to store the reference to the new {@code DataSet} during the scope of this
* routine (target).
* @param option
* Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
*/
public static void createDynamicDataSet(DataSetParameter dsParam,
handle dsHandle,
ParameterOption option)
{
if (FwdServerJMX.JMX_DEBUG)
{
DYN_PARAM.timer(() -> createDynamicDataSetImpl(dsParam, dsHandle, option));
}
else
{
createDynamicDataSetImpl(dsParam, dsHandle, option);
}
}
/**
* Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
* {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
* a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
* constructs.
*
* @param dsParam
* The {@code DataSetParameter}. This is the actual parameter for the routine (src).
* @param dsHandle
* A handle to store the reference to the new {@code DataSet} during the scope of this
* routine (target).
* @param option
* Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
*/
private static void createDynamicDataSetImpl(DataSetParameter dsParam,
handle dsHandle,
ParameterOption option)
{
DataSetManager manager = DataSetManager.instance();
manager._registerScopeable();
boolean input = dsParam.isInputMode();
boolean output = dsParam.isOutputMode();
boolean append = (option == ParameterOption.APPEND) || dsParam.isAppend();
boolean bind = dsParam.isBind();
if (output)
{
manager.registerCurrent(dsHandle);
if (bind && dsParam.getOriginalDataSet() == null)
{
ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
pm.executeOnReturn(() -> dsParam.getDatasetHandle().assign(dsHandle), WeightFactor.LEVEL_10);
return;
}
}
//if bind, assign source dataset to the new ds handle,
//this will ensure that the new dataset has the same data source
if (bind)
{
if (!dsParam.getDataset().referenceOnly)
{
dsHandle.assign(dsParam.getDataset());
}
if (output && dsParam.isOriginalDataSetProxy())
{
manager.registerOutputDataSetHandleCopier(dsParam, dsHandle);
}
return;
}
if (dsParam.isByReference())
{
dsHandle.assign(dsParam.getDataset());
if (dsHandle._isValid())
{
DataSet ds = (DataSet) dsHandle.getResource();
manager.addByReferenceParameter(ds);
ds.pm.executeOnReturn(() -> manager.removeByReferenceParameter(ds),
WeightFactor.LEVEL_10);
}
else if (output)
{
manager.registerOutputDataSetHandleCopier(dsParam, dsHandle);
}
return;
}
if (input || dsParam.isRemoteParameter())
{
if (dsParam.isRemoteParameter() && dsParam.getDatasetWrapper().getXmlDataset() != null)
{
String xmlDataset = dsParam.getDatasetWrapper().getXmlDataset();
readFromXml(xmlDataset, append, dsHandle);
return;
}
DataSet newDS = null;
handle srcHandle = dsParam.getDatasetHandle();
if (srcHandle._isValid())
{
if (dsParam.isByReference() || dsParam.isRemoteParameter())
{
// check if all tables contain schema.
if (!dsParam.getDatasetWrapper().getTableDefs().stream().
allMatch(DsTableDefinition::isReceivedSchema))
{
// A NO-SCHEMA-MARSHAL table cannot be used as a parameter where the receiving side
// does not have a pre-prepared schema
ErrorManager.recordOrThrowError(12323);
}
// if [dsParam] is remote, the dataset was rehydrated when [dsParam] was created
// do not copy now for 2nd time. Since it is an [input] param we can use the
// dataset handle directly.
dsHandle.assign(dsParam.get());
}
else
{
handle newDSh = TypeFactory.handle();
DataSet.create(newDSh);
newDS = (DataSet) newDSh.getResource();
logical res = newDS.copyDatasetImpl(srcHandle, false, false, false, null, false, "");
if (!res.booleanValue())
{
BlockManager.returnError();
}
dsHandle.assign(newDSh);
}
}
}
if (output)
{
manager.registerOutputDataSetHandleCopier(dsParam, dsHandle);
}
}
/**
* Constructor to allow building a dataset with no linkage (used for the case of the surrogate
* resource {@link StaticDataSet}, which delegates the calls to either the default dataset
* or the bound dataset.
*/
DataSet()
{
super(false, false);
this.dynamic = false;
name = "";
}
/**
* Initialize this resource by determining its type.
*
* @param legacyName
* The legacy name of the {@code DataSet}. Used for lookup.
* @param dynamic
* Flag indicating if this is a static or dynamic resource.
* @param referenceOnly
* Flag indicating if this is a REFERENCE-ONLY resource.
*
* @throws IllegalStateException
* If the resource type could not be determined from a {@link LegacyResource} annotation, after
* checking the interface hierarchy, or more than one interface is annotated.
*/
DataSet(String legacyName, boolean dynamic, boolean referenceOnly)
{
super(dynamic, dynamic);
this.dynamic = dynamic;
this.referenceOnly = dynamic ? false : referenceOnly; // dynamic DSes cannot be reference-only
name = legacyName; // the name not checked at this time, it was analyzed by the conversion
if (!dynamic)
{
buildInProgress = true;
}
}
/**
* Set the back-reference to the defining static proxy dataset.
*
* @param proxyDataSet
* The defining proxy dataset.
*/
public void setProxyDataSet(StaticDataSet proxyDataSet)
{
this.proxyDataSet = proxyDataSet;
}
/**
* Get the defining {@link #proxyDataSet};
*
* @return See above.
*/
public StaticDataSet getProxyDataSet()
{
return proxyDataSet;
}
/**
* Convert this dataset to a handle.
*
* @return See above.
*/
public handle asHandle()
{
return new handle(this);
}
/**
* Reset name, nsPrefix and nsURI.
*/
public void resetNames()
{
name = "";
nsPrefix = null;
nsUri = "";
}
/**
* Sets the name of this handle. The name cannot be unknown.
*
* @param name
* The new name to be set.
*/
@Override
public void name(character name)
{
if (name == null || name.isUnknown())
{
unableToAssignUnknown("NAME", "DATASET widget");
return;
}
name(name.getValue());
}
/**
* Get the {@code name} attribute of handle.
*
* @return See above.
*/
@Override
public character name()
{
return new character(this.name);
}
/**
* Get the <code>name</code> attribute of handle as a string.
*
* @return See above.
*/
@Override
public String _name()
{
return this.name;
}
/**
* Sets the {@code name} attribute of the object referred by this handle.
*
* @param name
* The new name to be set to target object.
*/
@Override
public void name(String name)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(12411);
// Cannot set attribute for a static object.
ErrorManager.recordOrThrowError(3131, "NAME", "", LegacyResource.DATASET);
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
return;
}
if (!validateName(name))
{
ErrorManager.recordOrThrowError(3131, "NAME", this.name, LegacyResource.DATASET);
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
return;
}
this.name = name;
}
/**
* 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");
}
/**
* 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)
{
// there are no arrays in a dataset, just independent buffers
return dereference(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}
* the unknown value is returned.
*
* @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(String memberName)
{
handle buffHnd = bufferHandle(memberName);
if (buffHnd.isUnknown())
{
ErrorManager.recordOrShowError(12785, memberName, name().toStringMessage());
// Table <name> not found in dataset <name>.
return new handle();
}
return buffHnd;
}
/**
* 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}
* the unknown value is returned.
*
* @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 or unknown (?) if there is no such member.
*/
@Override
public <T extends BaseDataType> T dereference(Class<T> type, String memberName)
{
BaseDataType val = dereference(memberName);
if (val.isUnknown() || !type.isAssignableFrom(val.getClass()))
{
ErrorManager.recordOrShowError(10068);
// Lead attributes in a chained-attribute expression (a:b:c) must be type HANDLE or a user-defined type and valid (not UNKNOWN).
return (T) BaseDataType.generateUnknown(type);
}
return (T) val;
}
/**
* 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 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)
{
ErrorManager.recordOrThrowError(7372);
// Object of type DATASET widget used where BUFFER-FIELD object required.
}
/**
* Get the {@code PREV-SIBLING} attribute of this DataSet. Actually always returns {@code null} because
* this attribute is not supported by DataSets. Only {@code NEXT-SIBLING} is supported.
*
* @return Always {@code null}.
*/
@Override
public handle getPrevSibling()
{
handle.invalidAttribute("PREV-SIBLING", false, LegacyResource.DATASET);
return new handle();
}
/**
* Get the {@code NEXT-SIBLING} attribute of this DataSet. Actually always returns the super's
* {@code getPrevSibling()} because DataSets are queued in reverse order(!).
* <p>
* TODO: investigate other handle resource that manifest the same from the point of NEXT/PREV-SIBLING.
*
* @return Always {@code null}.
*/
@Override
public handle getNextSibling()
{
return super.getPrevSibling();
}
/**
* Returns the resource to it's clear state. The buffers and relations are removed. However,
* the content of the dataset tables is not dropped (ie, the tables are not cleared and will
* keep their current data).
*
* @return true if operation is successful
*/
@Override
public logical clear()
{
if (!_dynamic())
{
ErrorManager.recordOrShowError(11967);
// CLEAR may not be used on a static dataset.
return logical.of(false);
}
clearImpl(false, true);
// TODO: when is false returned?
return logical.of(true);
}
/**
* Clears the dataset. Deletes the relations, unlinks the buffers and reset all internal data
* to default values.
* <p>
* After being invoked on a dynamic {@code DataSet} the object gets ready to be repopulated.
* In this case the method can be invoked multiple times.
* <p>
* In the case of a static {@code DataSet}s, the method is called only once, when the object is
* destroyed. It only makes it easy for GC to free the occupied resources.
*
* @param delete
* The dataset will be deleted. If there are dynamic buffers associated with it having AUTO-DELETE
* attribute set they will be also deleted.
* @param resetProperties
* If {@code true} all attributes of the dataset are set to unknown values.
*
*/
private void clearImpl(boolean delete, boolean resetProperties)
{
for (int i = 0; i < relations.size(); i++)
{
DataRelation rel = relations.get(i);
rel.delete();
}
relations.clear();
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl buf = buffers.get(i);
// delete dynamic buffers associated with dynamic temp-tables, unless the buffer's AUTO-DELETE
// attribute is set to FALSE
if (delete &&
buf.autoDelete().booleanValue() &&
buf._dynamic() &&
buf.tableHandleNative().valid() &&
buf.tableHandleNative()._dynamic())
{
AbstractTempTable tt = (AbstractTempTable) buf.tableHandleNative();
if (tt != null && tt.valid() && tt._dynamic())
{
// delete the temp-table
tt.delete();
}
// delete the buffer
buf.delete();
}
buf.setDataSet(null);
}
buffers.clear();
proxies.clear();
if (resetProperties)
{
callbacks = null;
name = null;
admData = null;
nsUri = null;
nsPrefix = null;
xmlNodeName = null;
serializeName = null;
defaultActive = true;
error = false;
rejected = false;
}
}
/**
* Returns {@code true} if the buffer is dynamic.
* <p>
* This is not emitted by conversion, is used internally by FWD.
*
* @return {@code true} if the resource is dynamic, {@code false} otherwise.
*/
@Override
public boolean _dynamic()
{
return this.dynamic;
}
/**
* Checks whether the resource is dynamic or static.
*
* @return {@code true} if the resource is dynamic, {@code false} otherwise.
*/
@Override
public logical dynamic()
{
return logical.of(dynamic);
}
/**
* Loads the empty target ({@code DataSet} or {@code TempTable}) object with changed rows from
* original (source) object.
*
* @param original
* Handle to original object ({@code DataSet} or {@code TempTable}) that contains the
* changed rows to load into the target object.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical getChanges(handle original)
{
return getChanges(original, false);
}
/**
* Loads the empty target ({@code DataSet} or {@code TempTable}) object with changed rows from
* original (source) object.
*
* @param original
* Handle to original object ({@code DataSet} or {@code TempTable}) that contains the
* changed rows to load into the target object.
* @param parentMode
* When {@code TRUE} the target gets the changed parent rows of each changed child row.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical getChanges(handle original, logical parentMode)
{
return getChanges(original,
parentMode != null && !parentMode.isUnknown() && parentMode.getValue());
}
/**
* Loads the empty target ({@code DataSet} or {@code TempTable}) object with changed rows from
* original (source) object.
*
* @param original
* Handle to original object ({@code DataSet} or {@code TempTable}) that contains the
* changed rows to load into the target object.
* @param parentMode
* When {@code TRUE} the target gets the changed parent rows of each changed child row.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical getChanges(handle original, boolean parentMode)
{
// the parameter must be a valid dataset handle:
if (original == null || original.isUnknown() || !original.isType(LegacyResource.DATASET))
{
ErrorManager.recordOrShowError(11953);
// Dataset GET-CHANGES and MERGE-CHANGES must have a valid dataset handle as the first argument.
return logical.of(false);
}
DataSet dsSource = (DataSet) original.getResource();
// check 'original' to be a compatible dataset handle
if (!isCompatibleWith(dsSource))
{
ErrorManager.recordOrShowError(11953);
// Dataset GET-CHANGES and MERGE-CHANGES must have a change dataset and an original dataset with the same number of members and the member buffers must have matching schema.
return logical.of(false);
}
// this keeps the set of parent jobs that need to be executed after all buffers were
// processed in non-parentMode, otherwise they risk to be dropped if the parent is processed
// after the child buffer.
ArrayList<Object[]> parentJobs = new ArrayList<>();
// processing stops at first non-matching table, returns FALSE, but KEEPS copied/processed tables
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl src = dsSource.getBuffers().get(i);
TempTable srcTempTable = TempTableBuilder.asTempTable((Temporary) src);
BufferImpl dst = buffers.get(i);
logical ok = dst.getChanges(new handle(src));
if (!ok.getValue())
{
return ok;
}
if (parentMode)
{
List<DataRelation> relationSet = dsSource.getRelations(src, false, true, true);
if (!relationSet.isEmpty())
{
// there can be maximum one of this relation because a buffer cannot be child in
// more then one active relation
DataRelation dataRelation = relationSet.get(0);
BufferImpl parentSrc = dataRelation.getParentBufferNative();
BufferImpl parentDst = null;
for (int k = 0; k < dsSource.getBuffers().size(); k++)
{
if (dsSource.getBuffers().get(k) == parentSrc.ref())
{
// get the corresponding buffer
parentDst = buffers.get(k);
}
}
if (parentDst != null)
{
// check whether the parentDst has an (PRIMARY?) UNIQUE index
boolean found = false;
int idxNo = 0;
TableMapper.LegacyIndexInfo index = TableMapper.getLegacyIndexInfo(parentDst.buffer(), idxNo);
while (index != null)
{
if (index.isUnique()) // and PRIMARY? At least this is implied from error message
{
found = true;
break;
}
index = TableMapper.getLegacyIndexInfo(parentDst.buffer(), ++idxNo);
}
if (!found)
{
ErrorManager.recordOrShowError(11885);
// A unique primary index is required in the target table, each field of which is mapped to some source field, in order to do a replace-mode or parent-mode COPY/GET/MERGE/FILL type of operation on a dataset table.
ErrorManager.recordOrShowError(11891);
// Found error during GET-CHANGES method.
}
// copy only unchanged records from [parentSrc] to [parentDst] for changed
// records from [src.before], using index [index] for iteration. These are
// normally skipped when [parentDst] was populated from [parentSrc] but now are
// required because of the [parentMode] flag. Only AFTER records are copied:
// since they were NOT modified they do not have a BEFORE pair.
String whereStr = dataRelation.getWhereString().toJavaType();
parentJobs.add(new Object[] { parentSrc, parentDst, src, whereStr});
// parentSrc.copyParentUnchangedRecords(parentDst, src, whereStr);
}
}
}
}
if (!parentJobs.isEmpty())
{
for (int i = 0; i < parentJobs.size(); i++)
{
Object[] job = parentJobs.get(i);
BufferImpl parentSrc = (BufferImpl) job[0];
BufferImpl parentDst = (BufferImpl) job[1];
BufferImpl childSrc = (BufferImpl) job[2];
String whereStr = (String) job[3];
TemporaryBuffer.copyParentUnchangedRecords(parentDst, parentSrc, childSrc, whereStr);
}
}
return logical.of(true);
}
/**
* Merges the changed rows of all temp-tables or a single temp-table in a source
* {@code DataSet} object loaded with the {@code GET-CHANGES()} method into the corresponding
* rows of all temp-tables or a single temp-table (respectively) in the original (target)
* {@code DataSet} object.
*
* @param original
* A handle to the original (target) {@code DataSet} object or the buffer object of a
* single target {@code DataSet} temp-table to merge with the changed rows from the
* source object.
* @return {@code true} if operation is successful.
*/
@Override
public logical mergeChanges(handle original)
{
return mergeChanges(original, false);
}
/**
* Merges the changed rows of all temp-tables or a single temp-table in a source
* {@code DataSet} object loaded with the {@code GET-CHANGES()} method into the corresponding
* rows of all temp-tables or a single temp-table (respectively) in the original (target)
* {@code DataSet} object.
*
* @param original
* A handle to the original (target) {@code DataSet} object or the buffer object of a
* single target {@code DataSet} temp-table to merge with the changed rows from the
* source object.
* @param allCopyMode
* Use {@code TRUE} to indicates that all rows of the source after-image table must be
* merged.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical mergeChanges(handle original, logical allCopyMode)
{
boolean copyAll = allCopyMode != null && !allCopyMode.isUnknown() && allCopyMode.getValue();
return mergeChanges(original, copyAll);
}
/**
* Merges the changed rows of all temp-tables or a single temp-table in a source
* {@code DataSet} object loaded with the {@code GET-CHANGES()} method into the corresponding
* rows of all temp-tables or a single temp-table (respectively) in the original (target)
* {@code DataSet} object.
*
* @param original
* A handle to the original (target) {@code DataSet} object or the buffer object of a
* single target {@code DataSet} temp-table to merge with the changed rows from the
* source object.
* @param allCopyMode
* Use {@code TRUE} to indicates that all rows of the source after-image table must be
* merged.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical mergeChanges(handle original, boolean allCopyMode)
{
// the parameter must be a valid dataset handle:
if (original == null || original.isUnknown() || !original.isType(LegacyResource.DATASET))
{
ErrorManager.recordOrShowError(11953);
// Dataset GET-CHANGES and MERGE-CHANGES must have a valid dataset handle as the first argument.
return logical.of(false);
}
DataSet dsOriginal = (DataSet) original.getResource();
// check 'original' to be a compatible dataset handle
if (!isCompatibleWith(dsOriginal))
{
ErrorManager.recordOrShowError(11953);
// Dataset GET-CHANGES and MERGE-CHANGES must have a change dataset and an original dataset with the same number of members and the member buffers must have matching schema.
return logical.of(false);
}
if (buffers.isEmpty())
{
return logical.of(true);
}
// NOTE: this is probably a bug / quirk: if an unrelated (ie. not original) dataset is passed as
// parameter, the method returns with success without altering the tables
// TODO: prove the last affirmation
TempTable ttChanges = buffers.get(0).tableHandleNative();
TempTable ttOriginal = dsOriginal.getBuffers().get(0).tableHandleNative();
WrappedResource ufo = ttChanges.getOriginTable();
if (ufo != null && ufo != ttOriginal)
{
return logical.of(true);
}
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl myBuf = buffers.get(i); // contains the changes
BufferImpl origBuf = dsOriginal.getBuffers().get(i); // the target temp-table
logical res = myBuf.mergeChanges(new handle(origBuf), allCopyMode);
if (!res.getValue())
{
return res;
}
}
error(false); // clear the ERROR attribute in the process
return logical.of(true);
}
/**
* Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
* This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
*
* @param hds
* A handle to a {@code DataSet} from which to copy the definitions.
*
* @return {@code true} on success.
*/
@Override
public logical createLike(handle hds)
{
if (hds == null || hds.isUnknown() || !hds.isType(LegacyResource.DATASET))
{
ErrorManager.recordOrShowError(11968);
// First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
return logical.of(false);
}
return createLikeImpl((DataSet) hds.getResource(), null);
}
/**
* Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
* This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
*
* @param dsName
* The name of {@code DataSet} from which to copy the definitions.
*
* @return {@code true} on success.
*/
@Override
public logical createLike(String dsName)
{
DataSet sourceDS = DataSetManager.instance().byName(dsName);
if (sourceDS == null || !sourceDS.valid())
{
ErrorManager.recordOrShowError(11968);
// First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
return logical.of(false);
}
return createLikeImpl(sourceDS, null);
}
/**
* Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
* This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
*
* @param dsName
* The name of {@code DataSet} from which to copy the definitions.
*
* @return {@code true} on success.
*/
@Override
public logical createLike(character dsName)
{
DataSet sourceDS = DataSetManager.instance().byName(dsName.toJavaType());
if (sourceDS == null || !sourceDS.valid())
{
ErrorManager.recordOrShowError(11968);
// First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
return logical.of(false);
}
return createLikeImpl(sourceDS, null);
}
/**
* Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
* This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
*
* @param hds
* A handle to a {@code DataSet} from which to copy the definitions.
* @param prefix
* The prefix to prepend to each of the source {@code DataSet} member buffer names,
* which creates a new name for each new member buffer.
*
* @return {@code true} on success.
*/
@Override
public logical createLike(handle hds, String prefix)
{
if (hds == null || hds.isUnknown() || !hds.isType(LegacyResource.DATASET))
{
ErrorManager.recordOrShowError(11968);
// First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
return logical.of(false);
}
return createLikeImpl((DataSet) hds.getResource(), prefix);
}
/**
* Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
* This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
*
* @param hds
* A handle to a {@code DataSet} from which to copy the definitions.
* @param prefix
* The prefix to prepend to each of the source {@code DataSet} member buffer names,
* which creates a new name for each new member buffer.
*
* @return {@code true} on success.
*/
@Override
public logical createLike(handle hds, character prefix)
{
if (hds == null || hds.isUnknown() || !hds.isType(LegacyResource.DATASET))
{
ErrorManager.recordOrShowError(11968);
// First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
return logical.of(false);
}
return createLikeImpl((DataSet) hds.getResource(), prefix.toJavaType());
}
/**
* Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
* This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
*
* @param dsName
* The name of {@code DataSet} from which to copy the definitions.
* @param prefix
* The prefix to prepend to each of the source {@code DataSet} member buffer names,
* which creates a new name for each new member buffer.
*
* @return {@code true} on success.
*/
@Override
public logical createLike(String dsName, String prefix)
{
DataSet sourceDS = DataSetManager.instance().byName(dsName);
if (sourceDS == null || !sourceDS.valid())
{
ErrorManager.recordOrShowError(11968);
// First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
return logical.of(false);
}
return createLikeImpl(sourceDS, prefix);
}
/**
* Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
* This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
*
* @param dsName
* The name of {@code DataSet} from which to copy the definitions.
* @param prefix
* The prefix to prepend to each of the source {@code DataSet} member buffer names,
* which creates a new name for each new member buffer.
*
* @return {@code true} on success.
*/
@Override
public logical createLike(character dsName, character prefix)
{
DataSet sourceDS = DataSetManager.instance().byName(dsName.toJavaType());
if (sourceDS == null || !sourceDS.valid())
{
ErrorManager.recordOrShowError(11968);
// First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
return logical.of(false);
}
return createLikeImpl(sourceDS, prefix.toJavaType());
}
/**
* Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
* records are dropped before copy operation.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @param other
* Some other temp-table to copy from.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical copyTempTable(handle other)
{
return copyTempTable(other, false, false, false, null);
}
/**
* Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
* records are dropped before copy operation.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical copyTempTable(handle other, logical append)
{
return copyTempTable(other, append, logical.of(false), logical.of(false), new character());
}
/**
* Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
* records are dropped before copy operation.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
*
* @return {@code true} if operation is successful
*/
@Override
public logical copyTempTable(handle other, logical append, logical replace)
{
return copyTempTable(other, append, replace, logical.of(false), new character());
}
/**
* Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
* records are dropped before copy operation.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
* @param looseCopy
* Loose copy mode.
*
* @return {@code true} if operation is successful.
*/
@Override
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-TABLE handle. By default, all current
* records are dropped before copy operation.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical copyTempTable(handle other, boolean append)
{
return copyTempTable(other, append, false, false, null);
}
/**
* Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
* records are dropped before copy operation.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical copyTempTable(handle other, boolean append, boolean replace)
{
return copyTempTable(other, append, replace, false, null);
}
/**
* Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
* records are dropped before copy operation.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @param other
* Some other temp-table to copy from.
* @param append
* Append mode.
* @param replace
* Replace mode.
* @param looseCopy
* Loose copy mode.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical copyTempTable(handle other, boolean append, boolean replace, boolean looseCopy)
{
return copyTempTable(other, append, replace, looseCopy, null);
}
/**
* Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
* records are dropped before copy operation.
* Only the first parameter is mandatory so this method will probably be heavy overloaded.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @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 {@code true} if operation is successful.
*/
@Override
public logical copyTempTable(handle other,
logical append,
logical replace,
logical looseCopy,
character prefix)
{
handle.invalidAttribute("COPY-TEMP-TABLE", false, LegacyResource.DATASET);
return logical.UNKNOWN;
}
/**
* Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
* records are dropped before copy operation.
* Only the first parameter is mandatory so this method will probably be heavy overloaded.
* <p>
* The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
* we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
* referenced via handle interface, the {@code handle} would automatically unwrap it to an
* {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
* however, makes it mandatory that the {@code DataSet} to implement the interface.
*
* @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 {@code true} if operation is successful.
*/
@Override
public logical copyTempTable(handle other,
boolean append,
boolean replace,
boolean looseCopy,
String prefix)
{
handle.invalidAttribute("COPY-TEMP-TABLE", false, LegacyResource.DATASET);
return logical.UNKNOWN;
}
/**
* Check if the buffer's fill query can be {@link #reuseFillQuery re-used}.
*/
boolean isReuseFillQuery()
{
return reuseFillQuery;
}
/**
* Fills a {@code DataSet} object, recursively, based on its defined data sources, data
* relations, and queries. When invoked on one of its member {@code Buffer} objects a partial
* fill is performed, by starting at the respective level.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical fill()
{
if (!invokeCallback(asHandle(), callbacks == null ? null : callbacks.get(BEFORE_FILL_EVENT)))
{
error(true);
return logical.of(false);
}
ErrorManager.ErrorHelper errHlp = ErrorManager.getErrorHelper();
boolean wmOn = errHlp.isWarningMode();
if (!wmOn)
{
errHlp.setWarningMode(true);
}
try
{
this.reuseFillQuery = true;
// start with the current set of top-buffers
List<Buffer> topBuffers = getTopBufferList();
for (Buffer crtBuffer : topBuffers)
{
// fill this buffer. It will invoke fill for all buffers in parent relation (nesting)
logical fillOk = crtBuffer.fill();
if (!fillOk.getValue())
{
return fillOk;
}
}
}
finally
{
this.reuseFillQuery = false;
for (BufferImpl buffer : buffers)
{
buffer.clearQuery();
}
if (!wmOn)
{
errHlp.setWarningMode(false);
}
}
boolean ok = invokeCallback(asHandle(), callbacks == null ? null : callbacks.get(AFTER_FILL_EVENT));
error(!ok);
return logical.of(ok);
}
/**
* Obtain the mode in which the {@link DataSet#fill()} method fills a {@code DataSet} member buffer. The
* default mode is {@code MERGE}.
* <p>
* <strong>Note:</strong>
* Undocumented feature: query all buffers and returns the common fill mode if one exist. Otherwise
* returns unknown value.
*
* @return always unknown value.
*/
@Override
public character fillMode()
{
String mode = null;
for (int i = 0; i < buffers.size(); i++)
{
String crtFillMode = buffers.get(i).fillMode().toJavaType();
if (i == 0)
{
// assume the fill-mode value of first buffer
mode = crtFillMode;
}
else if (mode == null || !mode.equals(crtFillMode))
{
return new character();
}
}
return new character(mode);
}
/**
* Configures the mode in which the {@link DataSet#fill()} method fills a {@code DataSet} member buffer.
* The allowed modes are: {@code APPEND}, {@code EMPTY}, {@code MERGE}, {@code NO-FILL}, and
* {@code REPLACE}.
* <p>
* <strong>Note:</strong>
* Undocumented feature: set the fill mode on all buffers.
*
* @param mode
* The new fill mode for the buffers of this dataset.
*/
@Override
public void fillMode(String mode)
{
if (mode == null)
{
unableToAssignUnknown("FILL-MODE", "DATASET widget");
return;
}
String upperMode = mode.toUpperCase();
switch (upperMode)
{
case FM_APPEND:
case FM_EMPTY:
case FM_MERGE:
case FM_NOFILL:
case FM_REPLACE:
// one of these is expected
break;
default:
// but this is not. Complain about it.
ErrorManager.recordOrShowError(11960);
// FILL-MODE must be one of NO-FILL, MERGE, EMPTY, etc.
ErrorManager.recordOrShowError(4058, "FILL-MODE", "DATASET widget", mode);
// **Attribute <attribute> for the <widget id> has an invalid value of <string>.
}
// set the fill mode on all buffers
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl buffer = buffers.get(i);
buffer.fillMode(mode);
}
}
/**
* Configures the mode in which the {@link DataSet#fill()} method fills a {@code DataSet} member buffer.
* The allowed modes are: {@code APPEND}, {@code EMPTY}, {@code MERGE}, {@code NO-FILL},
* and {@code REPLACE}.
* <p>
* <strong>Note:</strong>
* Undocumented feature: set the fill mode on all buffers.
*
* @param mode
* the new fill mode for this buffer.
*/
@Override
public void fillMode(Text mode)
{
fillMode(mode == null ? null : mode.toJavaType());
}
/**
* Obtain number of references to a {@code Buffer}, {@code DataSet}, or {@code TempTable}
* object that is defined as a parameter to which reference-only objects are bound.
* This corresponds to the read-only {@code NUM-REFERENCES} attribute.
*
* @return number of references to this object.
*/
@Override
public integer numReferences()
{
return new integer(refs == null ? 0 : refs.size());
}
/**
* 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;
}
/**
* Accepts changes to the data in target object (a temp-table or all temp-tables in a
* {@code DataSet}).
*
* @return {@code true} if operation is successful.
*/
@Override
public logical acceptChanges()
{
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl buffer = buffers.get(i);
if (!buffer.isAfterBuffer())
{
// skip this buffer
continue;
}
logical ok = buffer.beforeBufferNative().acceptChanges();
if (!ok.booleanValue())
{
return ok;
}
}
error(false); // clear the ERROR attribute in the process
return logical.of(true);
}
/**
* Rejects changes to the data in target object (a temp-table or all temp-tables in a
* {@code DataSet}).
*
* @return {@code true} if operation is successful.
*/
@Override
public logical rejectChanges()
{
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl buffer = buffers.get(i);
if (!buffer.isAfterBuffer())
{
// skip this buffer
continue;
}
logical ok = buffer.beforeBufferNative().rejectChanges();
if (!ok.booleanValue())
{
return ok;
}
}
error(false); // clear the ERROR attribute in the process
return logical.of(true);
}
/**
* Applies a callback procedure, which allows execution of a defined event without duplicating
* the event procedure definition.
*
* @param eventName
* The event whose callback will be called.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical applyCallback(String eventName)
{
if (!isCallbackName(eventName))
{
return logical.of(false);
}
return logical.of(invokeCallback(asHandle(),
callbacks == null ? null : callbacks.get(eventName.toUpperCase())));
}
/**
* Retrieves the name of the internal procedure associated with the ABL callback for the
* specified event.
*
* @param eventName
* The name of the event.
*
* @return the name of the internal procedure associated with specified event.
*/
@Override
public character getCallbackProcName(String eventName)
{
if (!isCallbackName(eventName))
{
return new character();
}
CallbackData cbd = callbacks == null ? null : callbacks.get(eventName.toUpperCase());
if (cbd == null)
{
return new character();
}
return new character(cbd.getTarget());
}
/**
* Retrieves the handle of the procedure that contains the internal procedure associated
* with the ABL callback for the specified event
*
* @param eventName
* The name of the event.
*
* @return a handle to the procedure that contains the callback procedure.
*/
@Override
public handle getCallbackProcContext(String eventName)
{
if (!isCallbackName(eventName))
{
return new handle();
}
CallbackData cbd = callbacks == null ? null : callbacks.get(eventName.toUpperCase());
if (cbd == null || cbd.getProcedure() == null)
{
return new handle();
}
return new handle(cbd.getProcedure());
}
/**
* Associates an internal procedure with an ABL callback event.
* <p>
* This method returns false if the event name is not valid.
*
* @param event
* The event name, must evaluate to one of the events supported by implementing object.
* @param proc
* The internal procedure to be executed for this event, which will be executed using
* the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
* parameter to the empty string.
*
* @return {@code true} if the event was registered.
*/
@Override
public logical setCallbackProcedure(String event, String proc)
{
return setCallbackProcedure(event, proc, null);
}
/**
* Associates an internal procedure with an ABL callback event.
* <p>
* This method returns false if the event name is not valid.
*
* @param event
* The event name.
* @param proc
* The internal procedure to be executed for this event, which will be executed using
* the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
* parameter to the empty string.
*
* @return {@code true} if the event was registered.
*/
@Override
public logical setCallbackProcedure(String event, character proc)
{
return setCallbackProcedure(event, proc.toJavaType(), null);
}
/**
* Associates an internal procedure with an ABL callback event.
* <p>
* This method returns false if the event name is not valid.
*
* @param event
* The event name.
* @param proc
* The internal procedure to be executed for this event, which will be executed using
* the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
* parameter to the empty string.
*
* @return {@code true} if the event was registered.
*/
@Override
public logical setCallbackProcedure(character event, character proc)
{
return setCallbackProcedure(event.toJavaType(), proc.toJavaType(), null);
}
/**
* Associates an internal procedure with an ABL callback event.
* <p>
* This method returns false if the event name is not valid or if the procedure's context is
* not a valid handle.
*
* @param event
* The event name, must evaluate to one of the events supported by implementing object.
* @param proc
* The internal procedure to be executed for this event. To remove a callback
* mapping, set the this parameter to the empty string.
* @param context
* The procedure handle where the internal procedure will be searched and executed.
*
* @return {@code true} if the event was registered.
*/
@Override
public logical setCallbackProcedure(character event, String proc, handle context)
{
return setCallbackProcedure(event.toJavaType(), proc, context);
}
/**
* Associates an internal procedure with an ABL callback event.
* <p>
* This method returns false if the event name is not valid or if the procedure's context is
* not a valid handle.
*
* @param event
* The event name.
* @param proc
* The internal procedure to be executed for this event, which will be executed using
* the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
* parameter to the empty string.
* @param context
* The procedure handle where the internal procedure will be searched and executed.
*
* @return {@code true} if the event was registered.
*/
@Override
public logical setCallbackProcedure(String event, character proc, handle context)
{
return setCallbackProcedure(event, proc.toJavaType(), context);
}
/**
* Associates an internal procedure with an ABL callback event.
* <p>
* This method returns false if the event name is not valid or if the procedure's context is not a valid
* handle.
*
* @param event
* The event name.
* @param proc
* The internal procedure to be executed for this event, which will be executed using the current
* THIS-PROCEDURE reference. To remove a callback mapping, set the this parameter to the empty
* string.
* @param context
* The procedure handle where the internal procedure will be searched and executed.
*
* @return {@code true} if the event was registered.
*/
@Override
public logical setCallbackProcedure(character event, character proc, handle context)
{
return setCallbackProcedure(event.toJavaType(), proc.toJavaType(), context);
}
/**
* Applies a callback procedure, which allows execution of a defined event without duplicating
* the event procedure definition.
*
* @param eventName
* The event whose callback will be called.
*
* @return {@code true} if operation is successful.
*/
@Override
public logical applyCallback(Text eventName)
{
return applyCallback(eventName.toJavaType());
}
/**
* Retrieves the name of the internal procedure associated with the ABL callback for the
* specified event.
*
* @param eventName
* The name of the event.
*
* @return the name of the internal procedure associated with specified event.
*/
@Override
public character getCallbackProcName(Text eventName)
{
return getCallbackProcName(eventName.toJavaType());
}
/**
* Retrieves the handle of the procedure that contains the internal procedure associated
* with the ABL callback for the specified event
*
* @param eventName
* The name of the event.
*
* @return a handle to the procedure that contains the callback procedure.
*/
@Override
public handle getCallbackProcContext(Text eventName)
{
return getCallbackProcContext(eventName.toJavaType());
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
* @param context
* The context in which the callback will be executed.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(Text callbackName, Text routineName, object<?> context)
{
return setCallback(callbackName.toJavaType(), routineName.toJavaType(), context);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
* @param context
* The context in which the callback will be executed.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(String callbackName, String routineName, handle context)
{
return setCallbackProcedure(callbackName, routineName, context);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
* @param context
* The context in which the callback will be executed.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(Text callbackName, Text routineName, handle context)
{
return setCallbackProcedure(callbackName.toJavaType(), routineName.toJavaType(), context);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
* @param context
* The context in which the callback will be executed.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(String callbackName, Text routineName, handle context)
{
return setCallbackProcedure(callbackName, routineName.toJavaType(), context);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(String callbackName, String routineName)
{
return setCallback(callbackName, routineName, (object<?>) null);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(Text callbackName, Text routineName)
{
return setCallback(callbackName.toJavaType(), routineName.toJavaType(), (object<?>) null);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(Text callbackName, String routineName)
{
return setCallback(callbackName.toJavaType(), routineName);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(String callbackName, Text routineName)
{
return setCallback(callbackName, routineName.toJavaType());
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
* @param context
* The context in which the callback will be executed.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(String callbackName, Text routineName, object<?> context)
{
return setCallback(callbackName, routineName.toJavaType(), context);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
* @param context
* The context in which the callback will be executed.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(Text callbackName, String routineName, object<?> context)
{
return setCallback(callbackName.toJavaType(), routineName, context);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
* @param context
* The context in which the callback will be executed.
*
* @return {@code true} if operation is successful and {@code false} if the parameters are
* invalid.
*/
@Override
public logical setCallback(Text callbackName, String routineName, handle context)
{
return setCallback(callbackName.toJavaType(), routineName, context);
}
/**
* Associates an internal procedure with an ABL callback event.
* <p>
* This method returns false if the event name is not valid.
*
* @param event
* The event name.
* @param proc
* The internal procedure to be executed for this event, which will be executed using
* the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
* parameter to the empty string.
*
* @return {@code true} if the event was registered.
*/
@Override
public logical setCallbackProcedure(character event, String proc)
{
return setCallbackProcedure(event.toJavaType(), proc, null);
}
/**
* Configures a callback. Creates an association between a method within a class instance or
* an internal procedure within a persistent procedure, with an ABL callback event.
*
* @param callbackName
* The name of a callback.
* @param routineName
* The name of a method or an internal procedure to be associated.
* @param context
* The context in which the callback will be executed.
*
* @return {@code true} if {@code callbackName} is a correct callback name. The rest of
* parameters are ignored (note: manual states otherwise).
*/
@Override
public logical setCallback(String callbackName, String routineName, object<?> context)
{
if (!isCallbackName(callbackName))
{
return logical.of(false);
}
if (routineName == null)
{
return logical.of(true);
}
if (routineName.isEmpty())
{
if (callbacks != null)
{
callbacks.remove(callbackName.toUpperCase());
}
return logical.of(true);
}
CallbackData cbd;
if (context == null || context.isUnknown())
{
// defaulting to THIS-OBJECT or to THIS-PROCEDURE, depending on the context
if (ObjectOps.thisObject().isUnknown())
{
InvokeConfig callSite = getCallSite(callbackName);
cbd = new CallbackData(routineName, pm.thisProcedure(), null, callSite);
}
else
{
cbd = new CallbackData(routineName, null, ObjectOps.thisObject(), null);
}
}
else
{
cbd = new CallbackData(routineName, null, context, null);
}
if (callbacks == null)
{
callbacks = new HashMap<>();
}
callbacks.put(callbackName.toUpperCase(), cbd);
return logical.of(true);
}
/**
* Associates an internal procedure with an ABL callback event.
*
* @param eventName
* The eventName of the event.
* @param callback
* The eventName of the internal procedure associated with the callback event.
* @param context
* A handle to a procedure that contains the internal procedure specified by
* {@code callback}. If not specified, {@code THIS-PROCEDURE} is used as the procedure
* context.
*
* @return {@code true} when the method ends with success.
*/
@Override
public logical setCallbackProcedure(String eventName, String callback, handle context)
{
if (!isCallbackName(eventName))
{
return logical.of(false);
}
if (callback == null)
{
return logical.of(true);
}
if (callback.isEmpty())
{
if (callbacks != null)
{
callbacks.remove(eventName.toUpperCase());
}
return logical.of(true);
}
if (context == null || context.isUnknown())
{
// defaulting to THIS-PROCEDURE
context = pm.thisProcedure();
}
else
{
if (!context._isValid() || !(context.getResource() instanceof PersistentProcedure))
{
ErrorManager.recordOrShowError(13273);
// Third argument to SET-CALLBACK must be a valid procedure handle or object reference.
return logical.of(false);
}
}
InvokeConfig callSite = getCallSite(eventName);
if (callbacks == null)
{
callbacks = new HashMap<>();
}
callbacks.put(eventName.toUpperCase(), new CallbackData(callback, context, null, callSite));
return logical.of(true);
}
/**
* Returns the value of the the NAMESPACE-URI attribute.
*
* @return See above.
*/
@Override
public character namespaceURI()
{
return new character(nsUri);
}
/**
* Setter for NAMESPACE-URI attribute.
*
* @param uri The new value of the NAMESPACE-URI attribute.
*/
@Override
public void namespaceURI(String uri)
{
if (uri == null)
{
unableToAssignUnknown("NAMESPACE-URI", "DATASET widget");
return;
}
nsUri = uri;
}
/**
* Setter for NAMESPACE-URI attribute.
*
* @param uri The new value of the NAMESPACE-URI attribute.
*/
@Override
public void namespaceURI(character uri)
{
namespaceURI(uri == null || uri.isUnknown() ? "" : uri.toJavaType());
}
/**
* Obtain the name of the XML element or attribute representing the target object (a
* {@code DataSet}) name in an XML document. If not set the {@code name} is returned. If the
* {@code name} is empty {@code "ProDataSet"} is returned.
*
* @return the name of the XML element for this object.
*/
@Override
public character getXmlNodeName()
{
return new character(getXmlNodeNameInternal());
}
/**
* Obtain the name of the XML element or attribute representing the target object. May return
* {@code null}, unlike the {@code getXmlNodeName()} attribute implementation.
*
* @return the name of the XML element for this object.
*/
public String getXmlNodeNameInternal()
{
return xmlNodeName != null ? xmlNodeName :
serializeName != null ? serializeName :
!name.isEmpty() ? name : "ProDataSet";
}
/**
* Returns the value of the the NAMESPACE-URI attribute as String.
*
* @return See above.
*/
public String getNsUri()
{
return nsUri == null ? "" : nsUri;
}
/**
* 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 (name != null)
{
xmlNodeName = name;
}
// no error is raised otherwise
}
/**
* 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());
}
/**
* 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()
{
String jsName = serializeName != null ? serializeName : name;
if (jsName == null || jsName.isEmpty())
{
jsName = "ProDataSet";
}
return new character(jsName);
}
/**
* 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.toJavaType());
}
/**
* 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 (sName != null)
{
serializeName = sName;
}
// no error is raised otherwise
}
/**
* Returns the state of the ERROR attribute
*
* @return Current state of the ERROR attribute.
*/
@Override
public logical error()
{
return logical.of(error);
}
/**
* Setter for ERROR attribute.
*
* @param value The new value of ERROR attribute.
*/
@Override
public void error(boolean value)
{
this.error = value;
}
/**
* Setter for ERROR attribute.
*
* @param value The new value of ERROR attribute.
*/
@Override
public void error(logical value)
{
if (value == null || value.isUnknown())
{
unableToAssignUnknown("ERROR", "DATASET widget");
return;
}
error(value.getValue());
}
/**
* 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)
{
this.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", "DATASET widget");
return;
}
rejected(value.getValue());
}
/**
* Reports if this object is valid for use. The {@code DataSet} is valid
*
* @return {@code true} if we are valid (can be used).
*/
@Override
public boolean valid()
{
return !buildInProgress && !deleted;
}
/**
* Get the value of the ADM-DATA attribute.
*
* @return See above.
*/
@Override
public character getADMData()
{
return new character(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();
}
/**
* Empties this {@code DataSet} object of all records in its associated temp-tables.
*
* @return {@code true} on success.
*/
@Override
public logical emptyDataset()
{
// TODO: When you invoke this method, the ABL Virtual Machine (AVM) sets the ORIGIN-HANDLE attribute on
// any changed temp-tables corresponding to the source temp-tables of this DataSet to the Unknown
// value (?).
// If TRACKING-CHANGES is true, the AVM raises an error.
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl buffer = buffers.get(i);
buffer.deleteAll();
}
error(false); // clear the ERROR attribute in the process
return logical.of(true);
}
/**
* Gets the handle of the specified data-relation object.
*
* @return the handle of the specified data-relation object.
*/
@Override
public handle getRelation()
{
return getRelation(1);
}
/**
* Gets the handle of the specified data-relation object.
*
* @return the handle of the specified data-relation object.
*/
public DataRelation getRelationNative()
{
return getRelationNative(1);
}
/**
* Gets the handle of the specified data-relation object.
*
* @param index
* The index (1-based) of the data-relation to be returned.
*
* @return the handle of the specified data-relation object.
*/
@Override
public handle getRelation(NumberType index)
{
if (index == null || index.isUnknown())
{
return new handle();
}
return getRelation(index.longValue());
}
/**
* Gets the handle of the specified data-relation object.
*
* @param index
* The index (1-based) of the data-relation to be returned.
*
* @return the handle of the specified data-relation object.
*/
@Override
public handle getRelation(long index)
{
return new handle(getRelationNative(index));
}
/**
* Gets the handle of the specified data-relation object.
*
* @param index
* The index (1-based) of the data-relation to be returned.
*
* @return the handle of the specified data-relation object.
*/
public DataRelation getRelationNative(long index)
{
if (relations.isEmpty())
{
return null;
}
return index - 1 >= relations.size() ? null : relations.get((int) index - 1);
}
/**
* Get relations as stream.
*
* @return relations as stream
*/
public java.util.stream.Stream<DataRelation> getRelations()
{
return relations.stream();
}
/**
* Gets the handle of the specified data-relation object.
*
* @param name
* The name of the data-relation to be returned.
*
* @return the handle of the specified data-relation object.
*/
@Override
public handle getRelation(Text name)
{
if (name == null || name.isUnknown())
{
return new handle();
}
return getRelation(name.getValue());
}
/**
* Gets the handle of the specified data-relation object.
*
* @param name
* The name of the data-relation to be returned.
*
* @return the handle of the specified data-relation object.
*/
@Override
public handle getRelation(String name)
{
for (int i = 0; i < relations.size(); i++)
{
DataRelation relation = relations.get(i);
if (relation.name().toStringMessage().equalsIgnoreCase(name))
{
return new handle(relation);
}
}
return new handle();
}
/**
* Gets the top-level buffer of this {@code DataSet} object at the specified index position.
*
* @return the top-level buffer at the position 1.
*/
@Override
public handle getTopBuffer()
{
return getTopBuffer(1);
}
/**
* Gets the top-level buffer of this {@code DataSet} object at the specified index position.
*
* @param index
* The 1-based index of the top-level buffer.
*
* @return the top-level buffer at the specified index position.
*/
@Override
public handle getTopBuffer(long index)
{
return new handle(getTopBufferNative(index));
}
/**
* Gets the top-level buffer of this {@code DataSet} object at the specified index position.
*
* @param index
* The 1-based index of the top-level buffer.
*
* @return the top-level buffer at the specified index position.
*/
public BufferImpl getTopBufferNative(long index)
{
if (index < 1)
{
return null;
}
List<Buffer> candidates = getTopBufferList();
if (_getNumTopBuffers() != candidates.size())
{
// well, this should NEVER happen!
throw new RuntimeException("The size of TOP-BUFFER list does not match NUM-TOP-BUFFER");
}
return index - 1 >= candidates.size() ? null : (BufferImpl) candidates.get((int) (index - 1));
}
/**
* Gets the top-level buffer of this {@code DataSet} object at the specified index position.
*
* @param index
* The 1-based index of the top-level buffer.
*
* @return the top-level buffer at the specified index position.
*/
@Override
public handle getTopBuffer(NumberType index)
{
if (index == null || index.isUnknown())
{
return new handle();
}
return getTopBuffer(index.longValue());
}
/**
* Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
* This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @return the default navigation query for a top-level buffer.
*/
@Override
public handle getTopNavQuery()
{
return getTopNavQuery(1);
}
/**
* Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
* This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param index
* The 1-based index of the top-level buffer.
*
* @return the default navigation query for a top-level buffer.
*/
@Override
public handle getTopNavQuery(long index)
{
if (index < 0 || index > _getNumTopBuffers())
{
return new handle();
}
BufferImpl topBuffer = getTopBufferNative(index);
if (topBuffer == null)
{
return new handle();
}
return getTopNavQuery(topBuffer);
}
/**
* Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
* This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param index
* The 1-based index of the top-level buffer.
*
* @return the default navigation query for a top-level buffer.
*/
@Override
public handle getTopNavQuery(NumberType index)
{
if (index == null || index.isUnknown())
{
return new handle();
}
return getTopBuffer(index.intValue());
}
/**
* Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
* This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param bufName
* The name of the top-level buffer.
*
* @return the default navigation query for a top-level buffer.
*/
@Override
public handle getTopNavQuery(String bufName)
{
return getTopNavQuery(getBufferByName(bufName));
}
/**
* Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
* This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param bufName
* The name of the top-level buffer.
*
* @return the default navigation query for a top-level buffer.
*/
@Override
public handle getTopNavQuery(Text bufName)
{
if (bufName == null || bufName.isUnknown())
{
return new handle();
}
return getTopNavQuery(bufName.getValue());
}
/**
* Configures the default navigation query for a top-level buffer of this {@code DataSet}
* object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param queryHandle
* The new default navigation query for a top-level buffer.
*/
@Override
public void setTopNavQuery(handle queryHandle)
{
setTopNavQuery(1, queryHandle);
}
/**
* Configures the default navigation query for a top-level buffer of this {@code DataSet}
* object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param index
* The 1-based index of the top-level buffer.
* @param queryHandle
* The new default navigation query for a top-level buffer.
*/
@Override
public void setTopNavQuery(long index, handle queryHandle)
{
if (index < 1 || index > _getNumTopBuffers())
{
invalidTopNavQuery(4056, "");
return;
}
if (queryHandle.getResource() == null)
{
// silently accept invalid values
return;
}
BufferImpl topBuffer = getTopBufferNative(index);
_setTopBufferQuery(queryHandle, topBuffer);
}
/**
* Configures the default navigation query for a top-level buffer of this {@code DataSet}
* object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param index
* The 1-based index of the top-level buffer.
* @param queryHandle
* The new default navigation query for a top-level buffer.
*/
@Override
public void setTopNavQuery(NumberType index, handle queryHandle)
{
setTopNavQuery((index == null || index.isUnknown()) ? -1 : index.longValue(), queryHandle);
}
/**
* Configures the default navigation query for a top-level buffer of this {@code DataSet}
* object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param bufName
* The name of the top-level buffer.
* @param queryHandle
* The new default navigation query for a top-level buffer.
*/
@Override
public void setTopNavQuery(String bufName, handle queryHandle)
{
if (queryHandle.getResource() == null)
{
// silently accept invalid values
return;
}
_setTopBufferQuery(queryHandle, getBufferByName(bufName));
}
/**
* Configures the default navigation query for a top-level buffer of this {@code DataSet}
* object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
*
* @param bufName
* The name of the top-level buffer.
* @param queryHandle
* The new default navigation query for a top-level buffer.
*/
@Override
public void setTopNavQuery(Text bufName, handle queryHandle)
{
setTopNavQuery(bufName.toJavaType(), queryHandle);
}
/**
* Checks whether <strong>AT LEAST</strong> one data-relation objects in this {@code DataSet} object is
* active. All data-relation objects in a {@code DataSet} object are active by default.
*
* @return {@code true} when all data-relation objects are active.
*/
@Override
public logical areRelationsActive()
{
return logical.of(_areRelationsActive());
}
/**
* Configures whether all data-relation objects in this {@code DataSet} object to be active or
* inactive. Setting this attribute to {@code TRUE} will activate all data-relation objects.
* Setting it to {@code FALSE} will deactivate all data-relation objects instead.
*
* @param active
* The new value for {@code RELATIONS-ACTIVE} attribute.
*/
@Override
public void setRelationsActive(logical active)
{
if (active.isUnknown())
{
unableToAssignUnknown("RELATIONS-ACTIVE", "DATASET widget");
return;
}
setRelationsActive(active.getValue());
}
/**
* Configures whether all data-relation objects in this {@code DataSet} object to be active or
* inactive. Setting this attribute to {@code TRUE} will activate all data-relation objects.
* Setting it to {@code FALSE} will deactivate all data-relation objects instead.
*
* @param active
* The new value for {@code RELATIONS-ACTIVE} attribute.
*/
@Override
public void setRelationsActive(boolean active)
{
if (_areRelationsActive() == active)
{
// 'nothing' to do.
// NOTE: this is a bad assumption but is how ABL works!
return;
}
if (relations.isEmpty())
{
defaultActive = active;
return;
}
// check first allowsActiveChild() for all inactive relations
if (active)
{
List<Integer> childBuffers = new LinkedList<>();
for (int i = 0; i < relations.size(); i++)
{
DataRelation rel = relations.get(i);
Buffer cb = rel.getChildBufferNative();
if (cb == null)
{
continue;
}
int uuid = cb.getUniqueID().intValue();
if (childBuffers.contains(uuid))
{
ErrorManager.recordOrShowError(11979, cb.name().toStringMessage());
// Buffer <name> can have only 1 active parent relation.
return;
}
childBuffers.add(uuid);
}
}
for (int i = 0; i < relations.size(); i++)
{
DataRelation rel = relations.get(i);
rel.setActive(active);
}
}
/**
* Get the number of data-relation objects of this {@code DataSet} object.
*
* @return the number of data-relation objects of this {@code DataSet} object.
*/
@Override
public integer getNumRelations()
{
return new integer(relations.size());
}
/**
* Obtain the number of top-level buffers in this {@code DataSet} object.
*
* @return the number of top-level buffers in this {@code DataSet} object.
*/
@Override
public integer getNumTopBuffers()
{
return new integer(_getNumTopBuffers());
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
* object. This method applies only to {@code DataSet} objects dynamically created.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param pairs
* A comma-delimited list of parent-field, child-field pairs describing the relationship between
* parent and child buffers in the new data-relation object.
*
* @return A {@code handle} to the newly created {@code DataRelation}.
*/
@Override
public handle addRelation(handle parent, handle child, Text pairs)
{
return addRelationImpl(parent, child, pairs, null, null, null, null, null, false);
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
* object. This method applies only to {@code DataSet} objects dynamically created.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param pairs
* A comma-delimited list of parent-field, child-field pairs describing the relationship between
* parent and child buffers in the new data-relation object.
* @param reposition
* The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
* Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
*
* @return A {@code handle} to the newly created {@code DataRelation}.
*/
@Override
public handle addRelation(handle parent, handle child, Text pairs, logical reposition)
{
return addRelationImpl(parent, child, pairs, reposition, null, null, null, null, false);
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
* object. This method applies only to {@code DataSet} objects dynamically created.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param pairs
* A comma-delimited list of parent-field, child-field pairs describing the relationship between
* parent and child buffers in the new data-relation object.
* @param reposition
* The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
* Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
* @param nested
* Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
* rows when writing the XML representation of data. When {@code FALSE} is passed, all
* child rows are written after all parent rows are written. The default value is {@code FALSE}.
*
* @return A {@code handle} to the newly created {@code DataRelation}.
*/
@Override
public handle addRelation(handle parent,
handle child,
Text pairs,
logical reposition,
logical nested)
{
return addRelationImpl(parent, child, pairs, reposition, nested, null, null, null, false);
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
* object. This method applies only to {@code DataSet} objects dynamically created.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param pairs
* A comma-delimited list of parent-field, child-field pairs describing the relationship between
* parent and child buffers in the new data-relation object.
* @param reposition
* The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
* Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
* @param nested
* Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
* rows when writing the XML representation of data. When {@code FALSE} is passed, all
* child rows are written after all parent rows are written. The default value is {@code FALSE}.
* @param active
* Use {@code false} to make the newly created relation inactive.
*
* @return A {@code handle} to the newly created {@code DataRelation}.
*/
@Override
public handle addRelation(handle parent,
handle child,
Text pairs,
logical reposition,
logical nested,
logical active)
{
return addRelationImpl(parent, child, pairs, reposition, nested, active, null, null, false);
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
* object. This method applies only to {@code DataSet} objects dynamically created.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param pairs
* A comma-delimited list of parent-field, child-field pairs describing the relationship between
* parent and child buffers in the new data-relation object.
* @param reposition
* The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
* Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
* @param nested
* Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
* rows when writing the XML representation of data. When {@code FALSE} is passed, all
* child rows are written after all parent rows are written. The default value is {@code FALSE}.
* @param active
* Use {@code false} to make the newly created relation inactive.
* @param recursive
* Using {@code TRUE} till causes the data-relation to be filled recursively.
*
* @return A {@code handle} to the newly created {@code DataRelation}.
*/
@Override
public handle addRelation(handle parent,
handle child,
Text pairs,
logical reposition,
logical nested,
logical active,
logical recursive)
{
return addRelationImpl(parent, child, pairs, reposition, nested, active, recursive, null, false);
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
* object. This method applies only to {@code DataSet} objects dynamically created.
* <p>
* <b>Note:</b><br>
* There is a bug/quirk in OE when this is performed. If a relation (for same buffer pair) is added a
* second time, it is accepted, leading to two "parallel" relations to be created. FWD will warn this in
* log. When/if OE decides to fix the bug/quirk, the handling code should also be removed.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param pairs
* A comma-delimited list of parent-field, child-field pairs describing the relationship between
* parent and child buffers in the new data-relation object.
* @param reposition
* The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
* Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
* @param nested
* Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
* rows when writing the XML representation of data. When {@code FALSE} is passed, all
* child rows are written after all parent rows are written. The default value is {@code FALSE}.
* @param active
* Use {@code false} to make the newly created relation inactive.
* @param recursive
* Using {@code TRUE} till causes the data-relation to be filled recursively.
* @param fkeyHidden
* When set to {@code TRUE} the foreign key fields in nested data-relations will be omitted when
* writing the XML representation of data. If this option is used, the {@code NESTED} argument
* must be {@code TRUE}.
*
* @return A {@code handle} to the newly created {@code DataRelation}.
*/
@Override
public handle addRelation(handle parent,
handle child,
Text pairs,
logical reposition,
logical nested,
logical active,
logical recursive,
logical fkeyHidden)
{
return addRelationImpl(parent, child, pairs, reposition, nested, active, recursive, fkeyHidden, false);
}
/**
* Actual implementation of {@code addRelation}.<p>
*
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
* object. This method applies only to {@code DataSet} objects dynamically created.
* <p>
* <b>Note:</b><br>
* There is a bug/quirk in OE when this is performed. If a relation (for same buffer pair) is added a
* second time, it is accepted, leading to two "parallel" relations to be created. FWD will warn this in
* log. When/if OE decides to fix the bug/quirk, the handling code should also be removed.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param pairs
* A comma-delimited list of parent-field, child-field pairs describing the relationship between
* parent and child buffers in the new data-relation object.
* @param reposition
* The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
* Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
* @param nested
* Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
* rows when writing the XML representation of data. When {@code FALSE} is passed, all
* child rows are written after all parent rows are written. The default value is {@code FALSE}.
* @param active
* Use {@code false} to make the newly created relation inactive.
* @param recursive
* Using {@code TRUE} till causes the data-relation to be filled recursively.
* @param fkeyHidden
* When set to {@code TRUE} the foreign key fields in nested data-relations will be omitted when
* writing the XML representation of data. If this option is used, the {@code NESTED} argument
* must be {@code TRUE}.
* @param nullOnDuplicate
* if {@code true} the method will return {@code null} if a duplicate relation is detected.
* Otherwise it will throw error condition #11979, eventually returning invalid handle.
*
* @return A {@code handle} to the newly created {@code DataRelation}.
*/
public handle addRelationImpl(handle parent,
handle child,
Text pairs,
logical reposition,
logical nested,
logical active,
logical recursive,
logical fkeyHidden,
boolean nullOnDuplicate)
{
if (parent == null || parent.isUnknown() || !parent.isType(LegacyResource.BUFFER) ||
child == null || child.isUnknown() || !child.isType(LegacyResource.BUFFER))
{
ErrorManager.recordOrShowError(11955);
// The argument to Dataset-handle:ADD-RELATION method was invalid.
return new handle();
}
BufferImpl bufParent = ((BufferImpl) parent.getResource()).ref();
BufferImpl bufChild = ((BufferImpl) child.getResource()).ref();
boolean throw11979 = false;
if (!buffers.contains(bufParent) || !buffers.contains(bufChild))
{
ErrorManager.recordOrShowError(11961);
// Buffers in ADD-RELATION must be members of the dataset.
return new handle();
}
boolean bActive = _areRelationsActive();
if (active != null && !active.isUnknown())
{
// if this flag is not specified, use the negated value of RELATIONS-ACTIVE
bActive = active.getValue();
}
// check whether a new relation can be added at this moment for the child buffer. If the new relation is
// inactive it can be added anyway
if (bActive)
{
DataRelation dr = allowsActiveChild(bufChild);
if (dr != null)
{
//Note: There is a bug/quirk in OE when this is performed. If a relation (for same buffer pair) is
// added a second time, it is accepted, leading to two "parallel" relations to be created.
// FWD will warn this in * log. When/if OE decides to fix the bug/quirk, this code must be
// removed or conditioned on emulated OE version
BufferImpl existingParent = dr.getParentBufferNative();
if (existingParent != bufParent)
{
ErrorManager.recordOrShowError(11979, bufChild.doGetName());
// Buffer <name> can have only 1 active parent relation.
return new handle();
}
else
{
if (nullOnDuplicate)
{
return null;
}
throw11979 = true;
}
}
}
if (pairs == null || pairs.isUnknown())
{
ErrorManager.recordOrShowError(11964);
// ADD-RELATION join pairs list was not valid.
return new handle();
}
if (bufParent == bufChild)
{
if (!recursive.booleanValue())
{
ErrorManager.recordOrShowError(11965);
// Attempt to relate dataset buffer <name> to parent with itself in ancestry.
return new handle();
}
}
String strPairs = pairs.getValue();
String[] fields = strPairs.split(",");
if (fields.length == 0 || fields.length == 1 && fields[0].isEmpty())
{
ErrorManager.recordOrShowError(11964);
// ADD-RELATION join pairs list was not valid.
return new handle();
}
else if (fields.length % 2 == 1)
{
ErrorManager.recordOrShowError(11962);
// Odd number of join pairs in ADD-RELATON.
return new handle();
}
for (int i = 0; i < fields.length; i += 2)
{
// make sure the legacy names are in lowercase (BUT why?)
String fParent = fields[i];
String fChild = fields[i + 1];
String errfld = null;
if (!bufParent.hasField(fParent))
{
errfld = fParent;
}
else if (!bufChild.hasField(fChild))
{
errfld = fChild;
}
if (errfld != null)
{
ErrorManager.recordOrShowError(11963, errfld);
// ADD-RELATION join pairs list entry <name> not valid--list must have unqualified
// field names with no embedded blanks, in parent-fld,child-fld order, e.g. 'cust-num,cust-num'.
return new handle();
}
}
boolean bRepos = reposition != null && !reposition.isUnknown() && reposition.getValue();
boolean bNested = nested != null && !nested.isUnknown() && nested.getValue();
boolean bRecurs = recursive != null && !recursive.isUnknown() && recursive.getValue();
boolean bfkHidden = fkeyHidden != null && !fkeyHidden.isUnknown() && fkeyHidden.getValue();
handle ret = addRelationWorker(null, bufParent, bufChild, fields, strPairs,
bRepos, bNested, bActive, bRecurs, bfkHidden, true);
if (throw11979)
{
ErrorManager.recordOrShowError(11979, bufChild.doGetName());
// Buffer <name> can have only 1 active parent relation.
return new handle();
}
return ret;
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet} based
* on the {@code RECID} of the parent buffer to this {@code DataSet} object. This method applies only to
* {@code DataSet} objects dynamically created. The newly created object will have the
* {@code PARENT-ID-RELATION} attribute set to {@code TRUE}.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
*
* @return A handle to the newly created {@link DataRelation} object.
*/
@Override
public handle addParentIdRelation(handle parent, handle child)
{
return addParentIdRelation(parent, child, null, null, null);
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet} based
* on the {@code RECID} of the parent buffer to this {@code DataSet} object. This method applies only to
* {@code DataSet} objects dynamically created. The newly created object will have the
* {@code PARENT-ID-RELATION} attribute set to {@code TRUE}.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param parentIdField
* Denotes the {@code RECID} field in child-buffer-handle on which this
* parent-id-relation object is based.
*
* @return A handle to the newly created {@link DataRelation} object.
*/
@Override
public handle addParentIdRelation(handle parent, handle child, Text parentIdField)
{
return addParentIdRelation(parent, child, parentIdField, null, null);
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet} based
* on the {@code RECID} of the parent buffer to this {@code DataSet} object. This method applies only to
* {@code DataSet} objects dynamically created. The newly created object will have the
* {@code PARENT-ID-RELATION} attribute set to {@code TRUE}.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param parentIdField
* Denotes the {@code RECID} field in child-buffer-handle on which this
* parent-id-relation object is based.
* @param parentFieldsBefore
* A list of comma-separated fields from the parent record to be written to XML before
* any nested child-buffer-handle records.
*
* @return A handle to the newly created {@link DataRelation} object.
*/
@Override
public handle addParentIdRelation(handle parent,
handle child,
Text parentIdField,
Text parentFieldsBefore)
{
return addParentIdRelation(parent, child, parentIdField, parentFieldsBefore, null);
}
/**
* Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet} based
* on the {@code RECID} of the parent buffer to this {@code DataSet} object. This method applies only to
* {@code DataSet} objects dynamically created. The newly created object will have the
* {@code PARENT-ID-RELATION} attribute set to {@code TRUE}.
*
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param parentIdField
* Denotes the {@code RECID} field in child-buffer-handle on which this
* parent-id-relation object is based. The default value is the name of the
* parent-buffer-handle appended with {@code _id}.
* @param parentFieldsBefore
* A list of comma-separated fields from the parent record to be written to XML before
* any nested child-buffer-handle records.
* @param parentFieldsAfter
* A list of comma-separated fields from the parent record to be written to XML after
* all nested child-buffer-handle records.
*
* @return A handle to the newly created {@link DataRelation} object.
*/
@Override
public handle addParentIdRelation(handle parent,
handle child,
Text parentIdField,
Text parentFieldsBefore,
Text parentFieldsAfter)
{
// validate buffers:
if (parent == null || parent.isUnknown() || !parent.isType(LegacyResource.BUFFER) ||
child == null || child.isUnknown() || !child.isType(LegacyResource.BUFFER))
{
ErrorManager.recordOrShowError(16025);
// The argument to Dataset-handle:ADD-PARENT-ID-RELATION method was invalid.
return new handle();
}
// validate link to parent:
BufferImpl bufParent = ((BufferImpl) parent.getResource()).ref();
BufferImpl bufChild = ((BufferImpl) child.getResource()).ref();
if (!buffers.contains(bufParent) || !buffers.contains(bufChild))
{
ErrorManager.recordOrShowError(16027);
// Buffers in ADD-PARENT-ID-RELATION must be members of the dataset.
return new handle();
}
// if (!allowsActiveChild(bufChild))
// {
// return new handle();
// }
String parLink = null;
if (parentIdField == null || parentIdField.isUnknown() || parentIdField.toJavaType().isEmpty())
{
parLink = bufParent._name() + "_id";
}
else
{
parLink = parentIdField.getValue();
}
// check if the field exist in the child buffer
if (!bufChild.hasField(parLink))
{
ErrorManager.recordOrShowError(16028, parLink);
// ADD-PARENT-ID-RELATION parent-id-field <field> is not valid.
return new handle();
}
else
{
// and, if so, it is of the proper type (RECID)
BufferField fparent = bufChild.bufferFieldNative(parLink);
if (fparent == null || !"RECID".equalsIgnoreCase(fparent.getDataType().getValue()))
{
ErrorManager.recordOrShowError(16028, parLink);
// ADD-PARENT-ID-RELATION parent-id-field <field> is not valid.
return new handle();
}
}
// validate before fields
List<String> beforeFields = new ArrayList<>();
if (parentFieldsBefore != null && !parentFieldsBefore.isUnknown())
{
String[] beforeStrFields = parentFieldsBefore.getValue().split(",");
for (String field : beforeStrFields)
{
if (!bufParent.hasField(field))
{
ErrorManager.recordOrShowError(16029, field);
// ADD-PARENT-ID-RELATION parent-fields-before entry <field> is not valid.
return new handle();
}
beforeFields.add(field);
}
}
List<String> afterFields = new ArrayList<>();
// validate after fields
if (parentFieldsAfter!= null && !parentFieldsAfter.isUnknown())
{
String[] afterStrFields = parentFieldsAfter.getValue().split(",");
for (String field : afterStrFields)
{
if (!bufParent.hasField(field))
{
ErrorManager.recordOrShowError(16030, field);
// ADD-PARENT-ID-RELATION parent-fields-after entry <field> is not valid.
return new handle();
}
if (beforeFields.contains(field))
{
ErrorManager.recordOrShowError(16484, field);
// Duplicate field <field> found in PARENT-FIELDS-BEFORE / PARENT-FIELDS-AFTER.
return new handle();
}
afterFields.add(field);
}
}
DataRelation dR = new DataRelation(this, bufParent, bufChild, parLink, beforeFields, afterFields, true);
// the REPOSITION attribute might be set, depending on the OE version
// dR.setReposition(true);
dR.name("RELATION" + (relations.size() + 1));
relations.add(dR);
return new handle(dR);
}
/**
* Copies a source {@code DataSet} object into this {@code DataSet} object.
*
* @param srcDS
* The source {@code DataSet}.
*
* @return {@code true} on success.
*/
@Override
public logical copyDataset(handle srcDS)
{
return copyDatasetImpl(srcDS, false, false, false, null, false, "cpy");
}
/**
* Copies a source {@code DataSet} object into this {@code DataSet} object.
*
* @param srcDS
* The source {@code DataSet}.
* @param append
* When {@code true} the source temp-tables are appended to current temp-tables.
*
* @return {@code true} on success.
*/
@Override
public logical copyDataset(handle srcDS, logical append)
{
boolean isAppend = append != null && !append.isUnknown() && append.getValue();
return copyDatasetImpl(srcDS, isAppend, false, false, null, false, "cpy");
}
/**
* Copies a source {@code DataSet} object into this {@code DataSet} object.
*
* @param srcDS
* The source {@code DataSet}.
* @param append
* When {@code true} the source temp-tables are appended to current temp-tables.
* @param replace
* When {@code true} the records in this temp-tables are replaced with records from
* the temp-tables of source {@code DataSet}.
*
* @return {@code true} on success.
*/
@Override
public logical copyDataset(handle srcDS, logical append, logical replace)
{
boolean isAppend = append != null && !append.isUnknown() && append.getValue();
boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
return copyDatasetImpl(srcDS, isAppend, isReplace, false, null, false, "cpy");
}
/**
* Copies a source {@code DataSet} object into this {@code DataSet} object.
*
* @param srcDS
* The source {@code DataSet}.
* @param append
* When {@code true} the source temp-tables are appended to current temp-tables.
* @param replace
* When {@code true} the records in this temp-tables are replaced with records from
* the temp-tables of source {@code DataSet}.
* @param looseCopy
* When {@code TRUE}, each temp-table in the source DataSet object is copied to
* this {@code DataSet} object based on a field mapping between the source and target
* temp-table buffers.
*
* @return {@code true} on success.
*/
@Override
public logical copyDataset(handle srcDS, logical append, logical replace, logical looseCopy)
{
boolean isAppend = append != null && !append.isUnknown() && append.getValue();
boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
boolean isLoose = looseCopy != null && !looseCopy.isUnknown() && looseCopy.getValue();
return copyDatasetImpl(srcDS, isAppend, isReplace, isLoose, null, false, "cpy");
}
/**
* Copies a source {@code DataSet} object into this {@code DataSet} object.
*
* @param srcDS
* The source {@code DataSet}.
* @param append
* When {@code true} the source temp-tables are appended to current temp-tables.
* @param replace
* When {@code true} the records in this temp-tables are replaced with records from
* the temp-tables of source {@code DataSet}.
* @param looseCopy
* When {@code TRUE}, each temp-table in the source DataSet object is copied to
* this {@code DataSet} object based on a field mapping between the source and target
* temp-table buffers.
* @param pairList
* A list of the target and source temp-table PAIRS to be copied, comma-delimited.
*
* @return {@code true} on success.
*/
@Override
public logical copyDataset(handle srcDS,
logical append,
logical replace,
logical looseCopy,
Text pairList)
{
boolean isAppend = append != null && !append.isUnknown() && append.getValue();
boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
boolean isLoose = looseCopy != null && !looseCopy.isUnknown() && looseCopy.getValue();
return copyDatasetImpl(
srcDS, isAppend, isReplace, isLoose, pairList.toJavaType(), false, "cpy");
}
/**
* Copies a source {@code DataSet} object into this {@code DataSet} object.
*
* @param srcDS
* The source {@code DataSet}.
* @param append
* When {@code true} the source temp-tables are appended to current temp-tables.
* @param replace
* When {@code true} the records in this temp-tables are replaced with records from
* the temp-tables of source {@code DataSet}.
* @param looseCopy
* When {@code TRUE}, each temp-table in the source DataSet object is copied to
* this {@code DataSet} object based on a field mapping between the source and target
* temp-table buffers.
* @param pairList
* A list of the target and source temp-table PAIRS to be copied, comma-delimited.
* @param currentOnly
* Use {@code TRUE} indicates that the AVM copy only the current record from each
* temp-table at each level in the source {@code DataSet} object to the target
* {@code DataSet} object. The default value is {@code FALSE}.
*
* @return {@code true} on success.
*/
@Override
public logical copyDataset(handle srcDS,
logical append,
logical replace,
logical looseCopy,
Text pairList,
logical currentOnly)
{
boolean isAppend = append != null && !append.isUnknown() && append.getValue();
boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
boolean isLoose = looseCopy != null && !looseCopy.isUnknown() && looseCopy.getValue();
boolean isCOnly = currentOnly != null && !currentOnly.isUnknown() && currentOnly.getValue();
return copyDatasetImpl(
srcDS, isAppend, isReplace, isLoose, pairList.toJavaType(), isCOnly, "cpy");
}
/**
* Copies a source {@code DataSet} object into this {@code DataSet} object.
*
* @param srcDS
* The source {@code DataSet}.
* @param append
* When {@code true} the source temp-tables are appended to current temp-tables.
* @param replace
* When {@code true} the records in this temp-tables are replaced with records from
* the temp-tables of source {@code DataSet}.
* @param looseCopy
* When {@code TRUE}, each temp-table in the source DataSet object is copied to
* this {@code DataSet} object based on a field mapping between the source and target
* temp-table buffers.
* @param pairList
* A list of the target and source temp-table PAIRS to be copied, comma-delimited.
* @param currentOnly
* Use {@code TRUE} indicates that the AVM copy only the current record from each
* temp-table at each level in the source {@code DataSet} object to the target
* {@code DataSet} object. The default value is {@code FALSE}.
* @param prefix
* An expression used as the prefix for (re-)naming the target {@code DataSet}.
*
* @return {@code true} on success.
*/
@Override
public logical copyDataset(handle srcDS,
logical append,
logical replace,
logical looseCopy,
Text pairList,
logical currentOnly,
Text prefix)
{
boolean isAppend = append != null && !append.isUnknown() && append.getValue();
boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
boolean isLoose = looseCopy != null && !looseCopy.isUnknown() && looseCopy.getValue();
boolean isCOnly = currentOnly != null && !currentOnly.isUnknown() && currentOnly.getValue();
return copyDatasetImpl(
srcDS, isAppend, isReplace, isLoose, pairList.toJavaType(), isCOnly,
prefix == null || prefix.isUnknown() ? "cpy" : prefix.getValue());
}
/**
* Binds all buffers for a dynamic {@code DataSet} object at the same time. Any buffers
* previously added to this {@code DataSet} object are removed.
*
* @param hBuffers
* Variable argument list of buffer handles to be set. In 4GL, the maximum number of
* buffers per query is 18.
*
* @return {@code true} on success.
*/
@Override
public logical setBuffers(handle... hBuffers)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11858);
// Cannot add/set buffers on a static dataset.
return logical.of(false);
}
// the list of buffers is cleared even if errors are encountered and method is aborted
this.buffers.clear();
this.proxies.clear();
List<BufferImpl> localBuffers = new ArrayList<>(hBuffers.length);
for (int i = 0; i < hBuffers.length; i++)
{
// references must be valid
if (hBuffers[i].isUnknown())
{
ErrorManager.recordOrShowError(7319, Integer.toString(i + 1));
// ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
return logical.of(false);
}
if (hBuffers[i].isType(LegacyResource.TEMP_TABLE))
{
hBuffers[i] = hBuffers[i].unwrapTempTable().defaultBufferHandle();
}
// references must be BUFFER handles (the error number is rather strange!)
if (!hBuffers[i].isType(LegacyResource.BUFFER))
{
ErrorManager.recordOrShowError(11860, Integer.toString(i + 1));
// Buffer <argnumber> may not appear more than once in any dataset
return logical.of(false);
}
BufferImpl proxy = (BufferImpl) hBuffers[i].getResource();
BufferImpl buffer = proxy.ref();
if (!(buffer instanceof Temporary))
{
ErrorManager.recordOrShowError(11884, buffer.doGetName());
// Dataset buffer <name> must be for a temp-table.
return logical.of(false);
}
if (buffer.isBeforeBuffer())
{
ErrorManager.recordOrShowError(15008, buffer.doGetName());
// You may not add before-table buffer <buffer> to a DATASET.
return logical.of(false);
}
// check for duplications:
DataSet ds = buffer._dataSet();
if (localBuffers.contains(buffer) || (ds != null) && ds.valid() && ds != this)
{
ErrorManager.recordOrShowError(11860, buffer.doGetName());
// Buffer <name> may not appear more than once in any dataset.
return logical.of(false);
}
localBuffers.add(buffer);
proxies.put(buffer, proxy);
}
// when everything is fine:
this.buffers.addAll(localBuffers);
this.buffers.forEach(b -> b.setDataSet(this));
return logical.of(true);
}
/**
* Binds all buffers for a dynamic {@code DataSet} object at the same time. Any buffers
* previously added to this {@code DataSet} object are removed.
*
* @param bufs
* Variable argument list of buffer handles to be set. In 4GL, the maximum number of
* buffers per query is 18.
*
* @return {@code true} on success.
*/
@Override
public logical setBuffers(Buffer... bufs)
{
return logical.of(setBuffersImpl(bufs));
}
/**
* Binds all buffers for a dynamic {@code DataSet} object at the same time. Any buffers
* previously added to this {@code DataSet} object are removed.
*
* @param bufs
* Variable argument list of Buffer to be set, a mixed list of {@link Buffer},
* {@link handle} or {@link character} instances.
*
* @return {@code true} on success
*/
@Override
public logical setBuffers(Object... bufs)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11858);
// Cannot add/set buffers on a static dataset.
return logical.of(false);
}
// the list of buffers is cleared even if errors are encountered and method is aborted
this.buffers.clear();
this.proxies.clear();
BufferManager bm = null;
List<BufferImpl> localBuffers = new ArrayList<>(bufs.length);
for (int i = 0; i < bufs.length; i++)
{
BufferImpl proxy = null;
if (bufs[i] instanceof Buffer)
{
proxy = (BufferImpl) bufs[i];
}
else if (bufs[i] instanceof handle)
{
handle asHandle = (handle) bufs[i];
// references must be valid:
if (asHandle.isUnknown() || !asHandle.isType(LegacyResource.BUFFER))
{
ErrorManager.recordOrShowError(7319, Integer.toString(i + 1));
// ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
return logical.of(false);
}
proxy = (BufferImpl) asHandle.getResource();
}
else if (bufs[i] instanceof character || bufs[i] instanceof String)
{
String name = (bufs[i] instanceof String)
? (String) bufs[i] : ((character) bufs[i]).toStringMessage();
name = TextOps.rightTrimNative(name);
if (bm == null)
{
bm = BufferManager.get();
}
RecordBuffer rb = bm.lookupByName(name);
if (rb != null)
{
proxy = (BufferImpl) rb.getDMOProxy();
}
else
{
ErrorManager.recordOrShowError(7319, Integer.toString(i + 1));
// ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
return logical.of(false);
}
}
else
{
// unknown/invalid data type
ErrorManager.recordOrShowError(7319, Integer.toString(i + 1));
// ADD/SET-BUFFERS argument <arg-number> was invalid or not found.
return logical.of(false);
}
// check for duplications:
BufferImpl crtBuff = proxy.ref();
if (crtBuff.isBeforeBuffer())
{
ErrorManager.recordOrShowError(15008, crtBuff.doGetName());
// You may not add before-table buffer <buffer> to a DATASET.
return logical.of(false);
}
DataSet ds = crtBuff._dataSet();
if (localBuffers.contains(crtBuff) || (ds != null) && ds.valid() && ds != this)
{
ErrorManager.recordOrShowError(11860, crtBuff.doGetName());
// Buffer <name> may not appear more than once in any dataset.
return logical.of(false);
}
localBuffers.add(crtBuff);
proxies.put(crtBuff, proxy);
}
// when everything is fine:
this.buffers.addAll(localBuffers);
this.buffers.forEach(b -> b.setDataSet(this));
return logical.of(true);
}
/**
* Adds one new buffer to a query object or dynamic {@code DataSet} object, without affecting
* the other buffers, if any. Use the SET-BUFFERS( ) method to remove all prior buffers and set
* all buffers for the object at the same time.
*
* @param bufHandle
* A buffer handle to add.
*
* @return true on success
*/
@Override
public logical addBuffer(handle bufHandle)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11858);
// Cannot add/set buffers on a static dataset.
return logical.of(false);
}
if (bufHandle.isUnknown())
{
ErrorManager.recordOrShowError(7319, "1");
// ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
return logical.of(false);
}
WrappedResource res = bufHandle.getResource();
if (res instanceof TempTable)
{
res = ((TempTable) res).defaultBufferHandleNative();
}
if (!(res instanceof Buffer))
{
// NOTE: trying to add an invalid handle - this is probably OE bug, should throw other error
ErrorManager.recordOrShowError(11860, bufHandle.unwrap().name().toStringMessage());
// Buffer <name> may not appear more than once in any dataset. (11860)
return logical.of(false);
}
return addBuffer((Buffer) res);
}
/**
* Adds one new buffer to a query object or dynamic {@code DataSet} object, without affecting
* the other buffers, if any. Use the SET-BUFFERS( ) method to remove all prior buffers and set
* all buffers for the object at the same time.
*
* @param buf
* A buffer to add.
*
* @return true on success
*/
@Override
public logical addBuffer(Buffer buf)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11858);
// Cannot add/set buffers on a static dataset.
return logical.of(false);
}
if (!(buf instanceof Temporary))
{
ErrorManager.recordOrShowError(11884, buf.name().toStringMessage());
// Dataset buffer <name> must be for a temp-table.
return logical.of(false);
}
BufferImpl proxy = (BufferImpl) buf;
BufferImpl bufImpl = proxy.ref();
if (bufImpl.isBeforeBuffer())
{
ErrorManager.recordOrShowError(15008, bufImpl.doGetName());
// You may not add before-table buffer <buffer> to a DATASET.
return logical.of(false);
}
DataSet ds = bufImpl._dataSet();
if (buffers.contains(bufImpl) || (ds != null) && ds.valid() && ds != this)
{
ErrorManager.recordOrShowError(11860, bufImpl.doGetName());
// Buffer <name> may not appear more than once in any dataset.
return logical.of(false);
}
buffers.add(bufImpl);
proxies.put(bufImpl, proxy);
bufImpl.setDataSet(this);
return logical.of(true);
}
/**
* Adds one new buffer to a query object or dynamic {@code DataSet} object, without affecting
* the other buffers, if any. Use the SET-BUFFERS( ) method to remove all prior buffers and set
* all buffers for the object at the same time.
*
* @param bufName
* Legacy buffer name to add.
*
* @return true on success
*/
@Override
public logical addBuffer(String bufName)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11858);
// Cannot add/set buffers on a static dataset.
return logical.of(false);
}
bufName = TextOps.rightTrimNative(bufName);
BufferManager bm = BufferManager.get();
RecordBuffer rb = bm.lookupByName(bufName);
if (rb == null)
{
ErrorManager.recordOrShowError(7319, Integer.toString(1));
// ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
return logical.of(false);
}
return addBuffer((Buffer) rb.buffer());
}
/**
* Adds one new buffer to a query object or dynamic {@code DataSet} object, without affecting
* the other buffers, if any. Use the SET-BUFFERS( ) method to remove all prior buffers and set
* all buffers for the object at the same time.
*
* @param bufName
* Legacy buffer name to add.
*
* @return true on success
*/
@Override
public logical addBuffer(character bufName)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11858);
// Cannot add/set buffers on a static dataset.
return logical.of(false);
}
if (bufName.isUnknown())
{
ErrorManager.recordOrShowError(7319, "1");
// ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
return logical.of(false);
}
return addBuffer(bufName.getValue());
}
/**
* Obtain the number of buffers in this {@code DataSet}.
*
* @return the number of buffers in this {@code DataSet}.
*/
@Override
public integer numBuffers()
{
return integer.of(buffers.size());
}
/**
* Obtain the handle to a particular buffer of this {@code DataSet}.
*
* @return a handle to a particular buffer of this {@code DataSet}.
*/
@Override
public handle bufferHandle()
{
return bufferHandle(1);
}
/**
* Obtain the handle to a particular buffer of this {@code DataSet}.
*
* @param bufIndex
* The sequence number of the desired buffer.
*
* @return a handle to a particular buffer of this {@code DataSet}.
*/
@Override
public handle bufferHandle(int bufIndex)
{
return bufferHandle(integer.of(bufIndex));
}
/**
* Obtain the handle to a particular buffer of this {@code DataSet}.
*
* @param bufIndex
* The sequence number of the desired buffer.
*
* @return a handle to a particular buffer of this {@code DataSet}.
*/
@Override
public handle bufferHandle(int64 bufIndex)
{
int idx = bufIndex.isUnknown() ? 0 : bufIndex.intValue();
return new handle(getBufferByIndex(idx));
}
/**
* Obtain the handle to a particular buffer of this {@code DataSet}.
*
* @param buffName
* The name of the desired buffer.
*
* @return a handle to a particular buffer of this {@code DataSet}.
*/
@Override
public handle bufferHandle(String buffName)
{
return new handle(getBufferByName(buffName));
}
/**
* Obtain the handle to a particular buffer of this {@code DataSet}.
*
* @param buffName
* The name of the desired buffer.
*
* @return a handle to a particular buffer of this {@code DataSet}.
*/
@Override
public handle bufferHandle(character buffName)
{
if (buffName == null || buffName.isUnknown())
{
return new handle();
}
return new handle(getBufferByName(buffName.getValue()));
}
/**
* 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()
{
return logical.of(serializeHidden);
}
/**
* 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 (val.isUnknown())
{
unableToAssignUnknown("SERIALIZE-HIDDEN", "DATA-RELATION widget");
return;
}
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)
{
serializeHidden = val;
}
/**
* Returns the value of the the NAMESPACE-PREFIX attribute. Only for ELEMENT or ATTRIBUTE,
* for other attributes return unknown value.
*
* @return See above.
*/
@Override
public character namespacePrefix()
{
return new character(nsPrefix);
}
/**
* Setter for NAMESPACE-PREFIX attribute.
*
* @param prefix
* The new value of the NAMESPACE-PREFIX attribute.
*/
@Override
public void namespacePrefix(String prefix)
{
if (prefix == null)
{
unableToAssignUnknown("NAMESPACE-PREFIX", "DATASET widget");
return;
}
this.nsPrefix = 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());
}
/**
* Implementation of the READ-JSON method. Reads JSON data from the specified data source into
* this object.
*
* @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)
{
// normalize parameters
JsonImport jsonImport = new JsonImport(source, readMode.toJavaType());
try
{
// perform import
return logical.of(jsonImport.readDataset(this));
}
catch (PersistenceException exc)
{
// TODO: log
return logical.of(false);
}
finally
{
// release and flush all buffers
for (int i = 0; i < buffers.size(); i++)
{
buffers.get(i).release();
}
}
}
/**
* Implementation of the WRITE-JSON method. Writes JSON data from this object into the
* specified data target.
*
* @param target
* Data target.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeJson(TargetData target)
{
return writeJson(target, null, null, null, null, null);
}
/**
* 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}.
*
* @return {@code True} if the operation completed successfully, else {@code false}.
*/
@Override
public logical writeJson(TargetData target, logical formatted)
{
return writeJson(target, formatted, null, null, null, null);
}
/**
* 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.
*
* @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, null, null, null);
}
/**
* 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 omitInitials
* {@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 omitInitials)
{
return writeJson(target, formatted, encoding, omitInitials, null, null);
}
/**
* 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 omitInitials
* {@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 omitInitials,
logical omitOuterObject)
{
return writeJson(target, formatted, encoding, omitInitials, omitOuterObject, null);
}
/**
* 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)
{
boolean pretty = formatted != null && !formatted.isUnknown() && formatted.getValue();
boolean noInit = omitInitialValues != null &&
!omitInitialValues.isUnknown() &&
omitInitialValues.getValue();
boolean noOuter = omitOuterObject != null &&
!omitOuterObject.isUnknown() &&
omitOuterObject.getValue();
boolean beforeImage = writeBeforeImage != null &&
!writeBeforeImage.isUnknown() &&
writeBeforeImage.getValue();
boolean ret;
try
{
com.fasterxml.jackson.core.JsonEncoding jsonEncoding = JsonExport.parseEncoding(encoding);
if (jsonEncoding == null)
{
ErrorManager.recordOrShowError(15342, encoding.toStringMessage(), "WRITE-JSON");
// Invalid encoding '<encoding>' argument for <method>.
return logical.of(false);
}
if (!flushAllBuffers())
{
return logical.FALSE;
}
JsonExport export = new JsonExport();
ret = export.exportDataSet(this, target, pretty, jsonEncoding, noInit, noOuter, beforeImage);
}
catch (PersistenceException exc)
{
// TODO: log ?
ret = false;
}
finally
{
try
{
target.close();
}
catch (IOException e)
{
// TODO: log ?
ret = false;
}
}
return logical.of(ret);
}
/**
* 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.DATASET);
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.DATASET);
}
/**
* 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.DATASET);
}
/**
* 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()
{
return new character(serializeHidden ? XmlNode.HIDDEN : XmlNode.ELEMENT);
}
/**
* Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
* node types are "ELEMENT" and "HIDDEN". It overlaps the{@code SERIALIZE-HIDDEN} attribute.
* This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
*
* @param newType
* The new type.
*/
@Override
public void setXmlNodeType(Text newType)
{
setXmlNodeType(newType.toJavaType());
}
/**
* Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
* node types are "ELEMENT" and "HIDDEN". It overlaps the{@code }SERIALIZE-HIDDEN} attribute.
* This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
*
* @param newType
* The new type.
*/
@Override
public void setXmlNodeType(String newType)
{
if (newType == null)
{
unableToAssignUnknown("XML-NODE-TYPE", "DATASET widget");
return;
}
String newTypeUp = newType.toUpperCase();
if (!newTypeUp.equals(XmlNode.HIDDEN) && !newTypeUp.equals(XmlNode.ELEMENT))
{
ErrorManager.recordOrShowError(16031, newType, name().toStringMessage());
// Invalid XML-NODE-TYPE <nodetype> for Dataset <dataset>.
ErrorManager.recordOrShowError(4058, "XML-NODE-TYPE", "DATASET widget", newType);
// **Attribute <attribute> for the <widget id> has an invalid value of <string>.
return;
}
serializeHidden = newTypeUp.equals(XmlNode.HIDDEN);
}
/**
* 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 strMapping
* 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 strMapping,
character verifySchemaMode)
{
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
}
}
}
// normalize parameters
XmlImport xmlImport = new XmlImport(
source,
readMode.toJavaType(),
schemaLocation.toJavaType(),
!overrideDefaultMapping.isUnknown() && overrideDefaultMapping.booleanValue(),
mapping,
verifySchemaMode.toJavaType());
// perform import
try
{
return logical.of(xmlImport.importDataset(this, RecordBuffer::loadRecord));
}
finally
{
// release and flush all buffers
for (int i = 0; i < buffers.size(); i++)
{
buffers.get(i).release();
}
}
}
/**
* Implementation of the WRITE-XML method. Writes XML data from this object into the specified
* data target.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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)
{
XmlExport export = new XmlExport(
target,
formatted != null && !formatted.isUnknown() && formatted.getValue(),
encoding == null ? null : encoding.toJavaType());
if (!flushAllBuffers())
{
return logical.FALSE;
}
boolean ret;
try
{
ret = export.writeDataset(
this,
schemaLocation == null ? null : schemaLocation.toJavaType(),
writeXmlSchema != null && !writeXmlSchema.isUnknown() && writeXmlSchema.getValue(),
minXmlSchema != null && !minXmlSchema.isUnknown() && minXmlSchema.getValue(),
beforeImage != null && !beforeImage.isUnknown() && beforeImage.getValue(),
noInitialVals != null && !noInitialVals.isUnknown() && noInitialVals.getValue());
}
catch(XmlExport.Fatal e) // Emergency exit - for the 4GL compatibility
{
// Error was already reported, just stop;
ret = false;
}
finally
{
try
{
target.close();
}
catch (IOException e)
{
// TODO: log
ret = false;
}
}
return logical.of(ret);
}
/**
* Writes an XML representation of the definition of this {@code DataSet}.
*
* @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 DataSet}.
*
* @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 DataSet}.
*
* @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 DataSet}.
*
* @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 DataSet}.
*
* @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)
{
XmlExport export = new XmlExport(
target,
formatted != null && !formatted.isUnknown() && formatted.getValue(),
encoding == null ? null : encoding.toJavaType());
boolean ret;
try
{
ret = export.writeDatasetSchema(
this,
minXmlSchema != null && !minXmlSchema.isUnknown() && minXmlSchema.getValue(),
omitInitVal != null && !omitInitVal.isUnknown() && omitInitVal.getValue());
}
finally
{
try
{
target.close();
}
catch (IOException e)
{
// TODO: log
ret = false;
}
}
return logical.of(ret);
}
/**
* Reads XML Schema from an XML document and uses that schema to either create a schema for a
* {@code DataSet} or {@code TempTable} or verify existing schema in a {@code DataSet},
* {@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, null, null);
}
/**
* Reads XML Schema from an XML document and uses that schema to either create a schema for a
* {@code DataSet} or {@code TempTable} or verify existing schema in a {@code DataSet},
* {@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, null);
}
/**
* Reads XML Schema from an XML document and uses that schema to either create a schema for a
* {@code DataSet} or {@code TempTable} or verify existing schema in a {@code DataSet},
* {@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.importDatasetSchema(this));
}
/**
* Obtain an integer values that uniques identifies this resource.
*
* @return an integer values that uniques identifies this resource.
*/
@Override
public integer getUniqueID()
{
if (uuid == null)
{
uuid = UniqueIdGenerator.getUniqueId(UniqueIdGenerator.IdKind.DATASET);
}
return new integer(this.uuid);
}
/**
* Set the {@link #postponedDelete} flag.
*
* @param postponed
* Flag indicating that delete is postponed.
*/
public void setPostponedDelete(boolean postponed)
{
this.postponedDelete = postponed;
}
/**
* Searches whether a buffer is part of the dataset. The before buffers are not taken into
* consideration so for these arguments the method returns -1.
*
* @param buffer
* The {@code BufferImpl} to be search.
*
* @return The 0-base index of the buffer if it is part of the {@code DataSet} and -1
* otherwise.
*/
public int getIndexOf(BufferImpl buffer)
{
for (int i = 0; i < buffers.size(); i++)
{
if (buffer == buffers.get(i))
{
return i;
}
}
return -1; // buffer not found
}
/**
* Check if the resource can veto the delete or not, before attempting the {@link #delete()} call.
*
* @param ref
* The exact handle holding the reference, on which the delete is attempted.
* @param explicit
* Flag indicating this is called via <code>DELETE OBJECT</code> from the application.
*
* @return <code>false</code> if the dataset is registered as a OUTPUT DATASET-HANDLE parameter
* or a BY-REFERENCE parameter.
*/
@Override
public boolean allowDelete(handle ref, boolean explicit)
{
DataSetManager dmgr = DataSetManager.instance();
if (dmgr.isOutputDataSetHandle(this))
{
setPostponedDelete(true);
return false;
}
if (dmgr.isByReferenceParameter(this))
{
if (_dynamic())
{
// 12327 error is thrown only for dynamic datasets
ErrorManager.recordOrThrowError(12327,
"Cannot delete a BY-REFERENCE PARAMETER " +
"dataset or table in the called procedure",
false, false);
}
return false;
}
return true;
}
/**
* Delete the resource.
*
* @return {@code true} if the resource was deleted.
*/
@Override
protected boolean resourceDelete()
{
if (!_dynamic() && !implicitDeletion())
{
// TODO: show error ?
return false;
}
clearImpl(true, true);
deleted = true;
ControlFlowOps.invalidateCallSiteCache(this);
if (deleteQueries != null)
{
for (QueryWrapper query : deleteQueries)
{
if (query.valid())
{
query.delete();
}
}
}
return true;
}
/**
* Allows each resource to declare whether it has some naming constraints.
*
* @return This method always returns {@code false}, requiring that its name should be
* validated before assignment.
*
* @see #validateName
*/
@Override
protected boolean hasNameConstraints()
{
return true;
}
/**
* Checks if this {@code REFERENCE-ONLY} {@code DataSet} was bound to a real {@code DataSet}.
*
* @return This object is dynamically built so by convention this method always returns
* {@code true} (it is bound to itself).
*/
@SuppressWarnings("static-method")
protected boolean isBound()
{
return true;
}
/**
* Check if this dataset was attempted to be deleted, but was postponed because of the handle
* being a DATASET-HANDLE BY-REFERENCE.
*
* @return The {@link #postponedDelete}.
*/
protected boolean isPostponedDelete()
{
return postponedDelete;
}
/**
* Test the compatibility of two datasets. The number of buffers and their field number is
* checked first. The datatype of each one is not (yet).
*
* @param other
* The {@code DataSet} to compare with this instance.
*
* @return {@code true} if the two dataset are compatible (ie one can receive data from
* another).
*/
protected boolean isCompatibleWith(DataSet other)
{
if (other == null || !other.valid())
{
// The use of ErrorManager.recordOrThrowError() is not very effective here because in FWD
// the bind is done after the called procedure has already started. The ErrorManager will
// stop only the execution of the called procedure/method. In 4GL the associate / bind
// operation is performed in caller and, in case of errors the caller is stopped before
// the invocation of the called method
TransactionManager.triggerErrorInCaller(12314, ErrorManager.replaceTokens(12314));
return false;
}
int bufSize = buffers.size();
if (bufSize != other.getBuffers().size())
{
TransactionManager.triggerErrorInCaller(12310, ErrorManager.replaceTokens(12310));
return false;
}
RecordBuffer buf1;
RecordBuffer buf2;
for (int i = 0; i < bufSize; i++)
{
buf1 = buffers.get(i).buffer();
buf2 = other.getBuffers().get(i).buffer();
if (buf1.getDMOBufInterface() == buf2.getDMOBufInterface())
{
// same DMO, no longer needed to check individual fields.
continue;
}
if (!buf1.getDmoInfo().getSignature().getQuickSignature().equals
(buf2.getDmoInfo().getSignature().getQuickSignature()))
{
TransactionManager.triggerErrorInCaller(12311,
ErrorManager.replaceTokens(12311,
buf1.getLegacyName(),
buf2.getLegacyName()));
return false;
}
}
// if all validations passed:
return true;
}
/**
* Obtains the original {@code DataSet} definition. This is for internal usage, in cases when
* that information is needed and the proxy interface will mask and make the information unavailable.
*
* @return the original {@code DataSet} definition.
*/
public DataSet ref()
{
return this;
}
/**
* Get the list of {@link #buffers} for the runtime instance of this resource.
*
* @return The list of buffers.
*/
public List<BufferImpl> getBuffers()
{
return ref().buffers; // NOTE: breaking encapsulation !
}
/**
* Getter for the internal variable {@link #serializedBySOAP}.
*
* @return <code>true</code> if this instance is currently serialized due to a SOAP request,
* <code>false</code> otherwise
*/
public boolean getSerializedBySOAP()
{
return serializedBySOAP;
}
/**
* Lookup a buffer by its index.
*
* @param bufIndex
* The index to search among the list of buffers.
*
* @return The buffer whose index was specified or {@code null} when there is no match.
*/
public BufferImpl getBufferByIndex(int bufIndex)
{
if (bufIndex < 1 || bufIndex > buffers.size())
{
return null;
}
BufferImpl src = buffers.get(bufIndex - 1);
BufferImpl srcProxy = proxies.get(src);
return (srcProxy != null) ? srcProxy.ref() : src;
}
/**
* Copy output data for the linked {@code DataSet} parameter, if any.
*
* @param outputParam
* The {@code DataSet} parameter where to copy the data.
*/
protected void performOutputCopy(DataSetParameter outputParam)
{
if (!outputParams.contains(outputParam))
{
// was already processed (by i.e. scope close)
return;
}
boolean res = true;
if (outputParam.isRemoteParameter())
{
handle pds = outputParam.getDatasetHandle();
DatasetWrapper oldWrapper = outputParam.getDatasetWrapper();
if (oldWrapper.isAsXml())
{
String dsXml = writeToXml(new handle(this),
oldWrapper.isUseBeforeImage(),
oldWrapper.isDatasetHandle());
oldWrapper.setXmlDataset(dsXml);
return;
}
// fill result set(s)
DataSetContainer rs = new DataSetContainer(this,
oldWrapper.isInput(),
oldWrapper.isOutput(),
oldWrapper.isAppend());
DatasetWrapper wrapper = new DatasetWrapper(rs);
wrapper.setDatasetName(this.name().getValue());
outputParam.setDatasetWrapper(wrapper);
if (pds._isValid())
{
// delete it, nothing else to do with it. done last as the 'pds' can be the same as 'this bound'.
((DataSet) pds.getResource()).delete();
}
}
else
{
handle hds = outputParam.getDatasetHandle();
if (hds.isUnknown())
{
handle thisHandle = new handle(this);
DataSet.create(hds);
DataSet callingDataset = outputParam.getDataset();
callingDataset.createLike(thisHandle);
callingDataset.copyDataset(thisHandle);
}
else
{
DataSet callingDataset = outputParam.getDataset();
// copies all the temp-tables in the order they were defined or added
// extra temp-tables from both source & destination are ignored
int bufCopyCount = Math.min(callingDataset.getBuffers().size(), this.buffers.size());
for (int i = 0; i < bufCopyCount; i++)
{
BufferImpl src = buffers.get(i);
BufferImpl srcProxy = proxies.get(src);
src = (srcProxy != null) ? srcProxy.ref() : src; // dynamically compute the .ref()
BufferImpl dst = callingDataset.ref().buffers.get(i);
BufferImpl dstProxy = callingDataset.ref().proxies.get(dst);
dst = (dstProxy != null) ? dstProxy.ref() : dst; // dynamically compute the .ref()
rowid srcRec = (referenceOnly || outputParam.isByReference()) && src._available()
? src.rowID()
: null;
Temporary srcDMO = (Temporary) src;
Temporary dstDMO = (Temporary) dst;
res = TemporaryBuffer.copyAllRows(srcRec,
srcDMO,
dstDMO,
outputParam.isAppend(),
CopyTableMode.OUTPUT_TABLE_PARAM_MODE);
if (!res)
{
break;
}
}
}
}
if (!res)
{
TemporaryBuffer.deferCopyError();
}
outputParams.remove(outputParam);
}
/**
* Obtain the number of top-level buffers in this {@code DataSet} object.
*
* @return the number of top-level buffers in this {@code DataSet} object.
*/
public int _getNumTopBuffers()
{
if (buffers.isEmpty())
{
return 0;
}
else if (relations.isEmpty())
{
return buffers.size();
}
// initially all buffers are TOP-LEVEL. For each active relation, one buffer becomes a child
// so it is removed from TOP-LEVEL list. Since a buffer can be a child in not more than a
// relation, each relation will have unique child buffers. As result, the number of
// TOP-LEVEL buffer list is the difference between the buffer number and the number of
// active relations.
int topBuffs = buffers.size();
for (int i = 0; i < relations.size(); i++)
{
DataRelation relation = relations.get(i);
if (relation._isActive() && !relation._isReposition() && !relation.isRecursive().booleanValue())
{
topBuffs--;
}
}
// NOTE: because of the #11979 issue described in addRelationImpl() the [topBuffs] may be negative !!!
return topBuffs;
}
/**
* Checks whether a buffer can be at this moment the child side in a relation of this {@code DataSet}. When
* an existing active relation is detected having the buffer as child the error 11979 is displayed before
* returning {@code false}.
*
* @param childBuffer
* The child buffer to test.
*
* @return {@code null} if the requested buffer can be a child buffer in a relation in this
* {@code DataSet} in current configuration or the {@code DataRelation} that caused the conflict.
*/
protected DataRelation allowsActiveChild(Buffer childBuffer)
{
int childUid = childBuffer.getUniqueID().intValue();
for (int i = 0; i < relations.size(); i++)
{
DataRelation relation = relations.get(i);
if (relation._isActive() &&
!relation._isReposition() &&
relation.getChildBufferNative().getUniqueID().intValue() == childUid)
{
return relation;
}
}
return null;
}
/**
* Queries the list of {@code DataRelation} s for a buffer. Depending on the requests, only the
* relations where the buffer is parent, child or any of them is returned.
*
* @param buffer
* The buffer to be queried on.
* @param parent
* If {@code true}, all the relations where the buffer is parent are included.
* @param child
* If {@code true}, all the relations where the buffer is child are included.
* @param active
* If {@code true}, ONLY the active relations are included. Otherwise, all relations
* that match above constraints will be returned.
*
* @return The set of relations where the requested buffer is part of, depending on the value
* of the flags.
*/
public ArrayList<DataRelation> getRelations(Buffer buffer, boolean parent, boolean child, boolean active)
{
return getRelations(buffer, parent, child, active, false);
}
/**
* Queries the list of {@code DataRelation} s for a buffer. Depending on the requests, only the
* relations where the buffer is parent, child or any of them is returned.
*
* @param buffer
* The buffer to be queried on.
* @param parent
* If {@code true}, all the relations where the buffer is parent are included.
* @param child
* If {@code true}, all the relations where the buffer is child are included.
* @param active
* If {@code true}, ONLY the active relations are included. Otherwise, all relations
* that match above constraints will be returned.
* @param forFill
* A flag indicating that the relations are requested by a fill operation.
*
* @return The set of relations where the requested buffer is part of, depending on the value
* of the flags.
*/
public ArrayList<DataRelation> getRelations(Buffer buffer,
boolean parent,
boolean child,
boolean active,
boolean forFill)
{
buffer = ((BufferImpl) buffer).ref();
ArrayList<DataRelation> ret = new ArrayList<>(relations.size());
for (int i = 0; i < relations.size(); i++)
{
DataRelation rel = relations.get(i);
if (active && !rel._isActive())
{
continue;
}
// If the relation is reposition and is used for a fill,
// ignore the relation and make the child a top-level buffer.
if (child && rel.getChildBufferNative() == buffer && !(rel._isReposition() && forFill))
{
ret.add(rel);
}
if (parent && rel.getParentBufferNative() == buffer)
{
ret.add(rel);
}
}
return ret;
}
/**
* Query the relation by its buffer.
*
* @param buffer
* The buffer to be queried on.
*
* @return The optional relation for the buffer
*/
public Optional<DataRelation> getRelation(Buffer buffer)
{
return relations.stream().filter(rel -> rel.getChildBuffer().getResource() == buffer).
findFirst();
}
/**
* Invokes a callback procedure or a callback method. The callback procedure/method must have
* an {@code INPUT} parameter of type {@code DATASET} or {@code DATASET-HANDLE}. The passed
* parameter is this {@code DataSet} object.
*
* @param cbd
* A {@code CallbackData} object holding the data necessary for invocation. May be
* {@code null}, in which case this method will silently return {@code yes} even
* though nothing is performed.
* @param self
* The object on which the event is applied (buffer or dataset).
*
* @return {@code true} if no errors were encountered during execution.
*/
protected boolean invokeCallback(handle self, CallbackData cbd)
{
if (cbd == null)
{
return true; // yep, nothing to do here
}
ErrorManager.ErrorHelper errHlp = ErrorManager.getErrorHelper();
boolean wasSilent = errHlp.isSilent();
try
{
if (!wasSilent)
{
errHlp.setSilent(true);
}
SelfManager.pushSelf(self);
// reference already assumes input-output, do not emit append
DataSetParameter dsp = new DataSetParameter(this, ParameterOption.BY_REFERENCE);
if (cbd.getObject() != null)
{
// TODO:
// Method '<methodname>' for callback must be valid public method. (13452)
ObjectOps.invokeStandalone(cbd.getObject(), cbd.getTarget(), "I", dsp);
}
else if (cbd.getProcedure() != null && !cbd.getProcedure().isUnknown())
{
InvokeConfig callSite = cbd.getCallSite();
InvokeConfig cfg = callSite == null ? new InvokeConfig() : callSite.clone();
cfg.setTarget(cbd.getTarget())
.setInHandle(cbd.getProcedure())
.setModes("I")
.setArguments(dsp)
.executeForResource(this);
}
// NOTE: sometimes the method return *NO* even if the callback was successfully called
return !errHlp.isPending(); // !ErrorManager.isError()
}
catch (ErrorConditionException e)
{
if (e.getProgressErrorCode() == 14457)
{
ErrorManager.recordOrShowError(13452, cbd.getTarget());
}
return false;
}
finally
{
SelfManager.popSelf();
if (!wasSilent)
{
errHlp.setSilent(false);
}
}
}
/**
* Register an output dataset parameter.
*
* @param dsParam
* The dataset parameter.
*/
protected void addOutputParam(DataSetParameter dsParam)
{
if (outputParams == null)
{
outputParams = new HashSet<>();
}
outputParams.add(dsParam);
}
/**
* The implementation of {@code COPY-DATASET} method. Copies all records from all tables of a
* source {@code DataSet} object into this {@code DataSet} object.
*
* @param hSrcDS
* The source {@code DataSet}.
* @param append
* When {@code true} the source temp-tables are appended to current temp-tables.
* @param replace
* When {@code true} the records in this temp-tables are replaced with records from
* the temp-tables of source {@code DataSet}.
* @param looseCopy
* When {@code TRUE}, each temp-table in the source DataSet object is copied to
* this {@code DataSet} object based on a field mapping between the source and target
* temp-table buffers.
* @param pairList
* A list of the target and source temp-table PAIRS to be copied, comma-delimited.
* @param currentOnly
* Use {@code TRUE} indicates that the AVM copy only the current record from each
* temp-table at each level in the source {@code DataSet} object to the target
* {@code DataSet} object. The default value is {@code FALSE}.
* @param prefix
* An expression used as the prefix for (re-)naming the target {@code DataSet}.
*
* @return {@code true} on success.
*/
protected logical copyDatasetImpl(handle hSrcDS,
boolean append,
boolean replace,
boolean looseCopy,
String pairList,
boolean currentOnly,
String prefix)
{
if (hSrcDS == null || hSrcDS.isUnknown() || !hSrcDS.isType(LegacyResource.DATASET))
{
ErrorManager.recordOrShowError(12362);
// COPY-DATASET must have a valid dataset handle as first argument.
return logical.of(false);
}
String[] pairs = null;
if (pairList != null && !pairList.isEmpty())
{
pairs = pairList.split(",");
if (pairs.length % 2 == 1)
{
ErrorManager.recordOrShowError(12783);
// Odd number of tables in pairs list parameter in COPY-DATASET.
return logical.of(false);
}
}
DataSet srcDS = (DataSet) hSrcDS.getResource();
// if this is a freshly created DATASET, we copy the structure from the source DS
if (this.buffers.isEmpty())
{
createLikeImpl(srcDS, prefix);
}
BiFunction<BufferImpl,BufferImpl, Boolean> checkDestB4Buffer = (src, dst) ->
{
boolean isEmptyBefore = src.peerBuffer.buffer().isTableDefinitelyEmpty();
if (!isEmptyBefore)
{
TempTableBuilder ttb = ((TempTableBuilder) dst.tableHandleNative());
if (ttb._dynamic())
{
// automatically create implicit before table for this dynamic temp-table
String tname = name().toJavaType();
ttb.createBeforeTable("BI" + tname, tname);
}
else
{
ErrorManager.recordOrShowError(12753, dst.doGetName());
// In COPY-TEMP-TABLE operation the static target table <name> must have an empty BEFORE-TABLE if the source table has any BEFORE-TABLE records.
return false;
}
}
return true;
};
logical res = new logical();
if (pairs == null)
{
// copies all the temp-tables in the order they were defined or added
// extra temp-tables from both source & destination are ignored
int bufCopyCount = Math.min(buffers.size(), srcDS.getBuffers().size());
for (int i = 0; i < bufCopyCount; i++)
{
BufferImpl src = srcDS.getBuffers().get(i);
BufferImpl dst = buffers.get(i);
if (src.isAfterBuffer() && !dst.isAfterBuffer() && !checkDestB4Buffer.apply(src, dst))
{
res = logical.of(false);
break; // i-for
}
if (currentOnly)
{
if (!append)
{
dst.deleteAll();
}
if (!dst._available())
{
// if destination buffer is empty create a new placeholder for the record to be copied
dst.create();
}
// copy only the current record from the specified temp-table buffers
res = dst.bufferCopy(src);
if (!res.getValue())
{
break; // i-for
}
}
else
{
// copy the entire AFTER temp-table
res = dst.copyTempTable(new handle(src), append, replace, looseCopy, prefix);
if (!res.getValue())
{
break; // i-for
}
}
}
}
else
{
// copy only the specified/mapped tables
for (int i = 0; i < pairs.length; i+=2)
{
BufferImpl dst = getBufferByName(pairs[i]);
BufferImpl src = srcDS.getBufferByName(pairs[i + 1]);
if (dst == null && src == null)
{
dst = getBufferByName(pairs[i + 1]);
src = srcDS.getBufferByName(pairs[i]);
}
if (dst == null || src == null)
{
ErrorManager.recordOrShowError(12784, pairs[i], pairs[i + 1]);
// COPY-DATASET pair <name> and <name> must have a table in each dataset.
return logical.of(false);
}
if (src.isAfterBuffer() && !dst.isAfterBuffer() && !checkDestB4Buffer.apply(src, dst))
{
res = logical.of(false);
break; // i-for
}
if (currentOnly)
{
// copy only the current record from the specified temp-table buffers
res = src.bufferCopy(dst);
if (!res.getValue())
{
break; // i-for
}
}
else
{
// copy the entire temp-table
res = dst.copyTempTable(new handle(src), append, replace, looseCopy, prefix);
if (!res.getValue())
{
break; // i-for
}
}
}
}
return res;
}
/**
* Adds a new reference to this dataset. The {@code NUM-REFERENCES} is updated if needed.
*
* @param staticDataSet
* The new {@code DataSet} that references this object.
*/
final void addReference(StaticDataSet staticDataSet)
{
if (refs == null)
{
refs = Collections.newSetFromMap(new IdentityHashMap<>());
}
refs.add(staticDataSet);
}
/**
* Removes a reference to this dataset. The {@code NUM-REFERENCES} is updated if needed.
*
* @param staticDataSet
* The new {@code DataSet} that does not references this object any more.
*/
final void removeReference(StaticDataSet staticDataSet)
{
if (refs != null)
{
refs.remove(staticDataSet);
}
}
/**
* Write the specified DATASET handle to a XML.
* <p>
* This is used when the remote call sent the DATASET as XML and it must receive it in the same format
* (used by SOAP callers).
*
* @param dsHandle
* The handle referencing the dataset.
* @param useBeforeImage
* Flag indicating if the before table needs to be serialized.
* @param isHandle
* Flag indicating that this is used for a DATASET-HANDLE parameter.
*
* @return The XML representation of this dataset.
*/
@SuppressWarnings("resource")
static String writeToXml(handle dsHandle, boolean useBeforeImage, boolean isHandle)
{
if (!dsHandle._isValid())
{
return null;
}
longchar dsXml = new longchar("");
DataSet ds = (DataSet) dsHandle.getResource();
ds.serializedBySOAP = true;
try
{
ds.writeXml(new TargetData(Serializator.TYPE_LONGCHAR, dsXml),
logical.of(false), // this must be false
null,
null,
logical.of(isHandle), // schema is sent only for DATASET-HANDLE
null,
logical.of(useBeforeImage),
null);
}
finally
{
ds.serializedBySOAP = false;
}
return dsXml.getValue();
}
/**
* Binds all buffers for a dynamic {@code DataSet} object at the same time. Any buffers
* previously added to this {@code DataSet} object are removed.
*
* @param bufs
* Variable argument list of buffer handles to be set. In 4GL, the maximum number of
* buffers per query is 18.
*
* @return {@code true} on success.
*/
boolean setBuffersImpl(Buffer... bufs)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11858);
// Cannot add/set buffers on a static dataset.
return false;
}
// the list of buffers is cleared even if errors are encountered and method is aborted
this.buffers.clear();
BufferManager bm = null;
Class<?> initClass = pm.getInstantiatingExternalProgramClass();
List<BufferImpl> localBuffers = new ArrayList<>(bufs.length);
for (Buffer buf : bufs)
{
if (buf == null)
{
continue;
}
BufferImpl proxy = (BufferImpl) buf;
BufferImpl bufRef = proxy.ref();
if (initClass == null)
{
// when initiating a legacy OE class, we don't know the source code where this is
// happening - but the buffer has the definition class emitted; so force that
if (bm == null)
{
bm = bufRef.buffer().bufferManager;
}
Class<?> def = bm.getPendingBufferClass(bufRef.buffer());
pm.setInstantingExternalProgramClass(def);
}
if (bufRef.isBeforeBuffer())
{
ErrorManager.recordOrShowError(15008, bufRef.doGetName());
// You may not add before-table buffer <buffer> to a DATASET.
return false;
}
// check for duplications:
DataSet ds = bufRef._dataSet();
if (localBuffers.contains(bufRef) || (ds != null) && ds.valid() && ds != this)
{
ErrorManager.recordOrShowError(11860, bufRef.doGetName());
// Buffer <name> may not appear more than once in any dataset.
return false;
}
localBuffers.add(bufRef);
proxies.put(bufRef, proxy);
}
pm.setInstantingExternalProgramClass(initClass);
// when everything is fine:
this.buffers.addAll(localBuffers);
localBuffers.forEach(b -> b.setDataSet(this));
return true;
}
/**
* Read the DATASET from XML.
* <p>
* This is used when the remote call sent the DATASET as XML and it must receive it in the same format
* (used by SOAP callers).
*
* @param xmlDataset
* The XML serialized dataset.
* @param append
* Flag indicating if the read mode is 'append'. Otherwise, is 'empty.
* @param ds
* The handle where to read the dataset.
*/
@SuppressWarnings("resource")
private static void readFromXml(String xmlDataset, boolean append, handle ds)
{
character readMode = new character(append ? "APPEND" : "EMPTY");
if (!ds._isValid())
{
DataSet.create(ds);
}
DataSet dsTarget = (DataSet) ds.getResource();
longchar lc = new longchar();
longchar.fixCodePage(lc, "utf-8");
lc.assign(xmlDataset);
dsTarget.readXml(new SourceData("longchar", lc),
readMode,
new character(),
logical.of(false));
}
/**
* Lookup a buffer by its name.
*
* @param name
* The name to search among the list of buffers.
*
* @return The buffer whose name was specified or {@code null} when there is no match.
*/
public BufferImpl getBufferByName(String name)
{
List<BufferImpl> proxyBuffers = proxies.values().stream().collect(Collectors.toList());
for (int i = 0; i < proxyBuffers.size(); i++)
{
BufferImpl buf = proxyBuffers.get(i);
if (buf._name().equalsIgnoreCase(name))
{
return buf;
}
}
return null;
}
/**
* Lookup a buffer by its serialize-name.
*
* @param serializeName
* The serialize-name to search among the list of buffers.
*
* @return The buffer whose serialize-name was specified or {@code null} when there is no match.
*/
public BufferImpl getBufferBySerializeName(String serializeName)
{
if (serializeName == null || serializeName.isEmpty())
{
return null; //quick out
}
List<BufferImpl> proxyBuffers = proxies.values().stream().collect(Collectors.toList());
for (int i = 0; i < proxyBuffers.size(); i++)
{
BufferImpl buf = proxyBuffers.get(i);
if (serializeName.equalsIgnoreCase(buf.getSerializeName().toJavaType()))
{
return buf;
}
}
return null;
}
/**
* Flush all buffers (including secondary buffers and associated before-buffers) used in this dataset.
*
* @return {@code true} if all buffers were flushed.
*/
private boolean flushAllBuffers()
{
for (Buffer buf : buffers)
{
RecordBuffer buffer = ((BufferImpl) buf).buffer();
if (!buffer.getParentTable().flushBuffers())
{
return false;
}
}
return true;
}
/**
* Obtain the current {@code TOP-NAV-QUERY} for a specified buffer. Uses lazy initialization:
* if not exist one is created (and prepared, but not open) now.
*
* @param buffer
* The buffer whose {@code TOP-NAV-QUERY} is to be returned.
*
* @return A {@code handle} to {@code TOP-NAV-QUERY} for specified buffer.
*/
private handle getTopNavQuery(BufferImpl buffer)
{
buffer = buffer.ref();
QueryWrapper query = topNavQueries == null ? null : topNavQueries.get(buffer);
if (query == null || !query.valid())
{
query = QueryWrapper.createQueryUnnamedPool();
query.setBuffers(buffer);
query.prepare("FOR EACH " + buffer._name());
if (topNavQueries == null)
{
topNavQueries = new HashMap<>();
}
topNavQueries.put(buffer, query);
if (deleteQueries == null)
{
deleteQueries = Collections.newSetFromMap(new IdentityHashMap<>());
}
deleteQueries.add(query);
}
return new handle(query);
}
/**
* Notifies the user about an attempt to set an invalid value for {@code TOP-NAV-QUERY } attribute. Two
* error messages will be issued, the former being 14293 and the latter the one specified as parameter.
*
* @param errCode
* The message code (4056 or 4061).
* @param bufName
* The buffer's name.
*/
private static void invalidTopNavQuery(int errCode, String bufName)
{
ErrorManager.recordOrShowError(14293, bufName);
// TOP-NAV-QUERY requires a valid query on top dataset buffer <name>
ErrorManager.recordOrShowError(errCode, "TOP-NAV-QUERY", "DATASET widget");
// **Attribute <attribute> for the <widget id> has an invalid value of UNKNOWN. (4056)
// or
// **Attribute for the <widget id> has an invalid value. (4061)
}
/**
* Low level implementation of {@code TOP-NAV-QUERY} method. Does the final parameter checking before
* mapping the query to the buffer.
*
* @param queryHandle
* The query to be set.
* @param topBuffer
* The top level buffer.
*/
private void _setTopBufferQuery(handle queryHandle, BufferImpl topBuffer)
{
if (queryHandle == null || queryHandle.isUnknown())
{
invalidTopNavQuery(4056, topBuffer.name().toStringMessage());
return;
}
if (!queryHandle.isType(LegacyResource.QUERY))
{
invalidTopNavQuery(4061, topBuffer.name().toStringMessage());
return;
}
QueryWrapper qv = (QueryWrapper) queryHandle.getResource();
if (qv.bufferHandleNative() != topBuffer)
{
invalidTopNavQuery(4061, topBuffer.name().toStringMessage());
return;
}
if (topNavQueries == null)
{
topNavQueries = new HashMap<>();
}
topNavQueries.put(topBuffer, qv);
}
/**
* Checks whether <strong>AT LEAST</strong> one data-relation objects in this {@code DataSet}
* object is active. All data-relation objects in a {@code DataSet} object are active by default.
*
* @return {@code true} when all data-relation objects are active.
*/
boolean _areRelationsActive()
{
if (relations.isEmpty() /*|| !dynamic*/)
{
// initial testcases suggested that, in case of relations definined in static dataset the [active] is
// still true although all relations were individually deactivated. It seems that this behaviour is
// caused by other (combination of) factors
return defaultActive;
}
for (int i = 0; i < relations.size(); i++)
{
DataRelation rel = relations.get(i);
if (rel._isActive())
{
return true;
}
}
// could not find any active relation
return false;
}
/**
* Adds a data-relation object for a pair of parent and child buffers to this {@code DataSet} object.
*
* @param relName
* The name of the relation.
* @param parent
* The handle to the parent buffer in the data-relation object.
* @param child
* The handle to the child buffer in the data-relation object.
* @param fields
* An array of (parent-field, child-field) pairs describing the relationship between
* parent and child buffers in the new data-relation object.
* @param strPairs
* The pair of fields, as string as they appear in the legacy statement.
* @param reposition
* The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
* Otherwise the relation mode is {@code SELECTION}. The default value is
* {@code SELECTION}.
* @param nested
* Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
* rows when writing the XML representation of data. When {@code FALSE} is passed, all
* child rows are written after all parent rows are written. The default value is {@code FALSE}.
* @param active
* Use {@code true} to make the newly created relation active.
* @param recursive
* Using {@code TRUE} will cause the data-relation to be filled recursively.
* @param fkeyHidden
* When set to {@code TRUE} the foreign key fields in nested data-relations will be
* omitted when writing the XML representation of data. If this option is used, the
* {@code NESTED} argument must be {@code TRUE}.
* @param dynamic
* Use {@code true} for dynamically created relations.
*
* @return A {@code handle} to the newly created {@code DataRelation}.
*/
private handle addRelationWorker(String relName,
Buffer parent,
Buffer child,
String[] fields,
String strPairs,
boolean reposition,
boolean nested,
boolean active,
boolean recursive,
boolean fkeyHidden,
boolean dynamic)
{
DataRelation dR = new DataRelation(this, parent, child, fields, strPairs, recursive);
dR.name(relName != null ? relName : "RELATION" + (relations.size() + 1));
dR.setReposition(reposition);
dR.setNested(nested);
dR.setActive(active);
dR.setForeignKeyHidden(fkeyHidden);
if (dynamic)
{
dR.confirmDynamic();
}
relations.add(dR);
return new handle(dR);
}
/**
* Checks whether this object can be altered (added buffers and relations). Normally, the
* static defined {@code DataSet} s are not alterable, but they can 'altered' by their
* {@code Builder} while the object is being constructed.
*
* @return {@code true} if new buffers or relations can be added.
*/
private boolean isAlterable()
{
return _dynamic() || buildInProgress;
}
/**
* Test whether a string is a valid CALLBACK name for {@code DataSet}s. This method is responsible of
* raising the 11950 error if the name specified is unknown or not a name of a known callback for
* {@code DataSet}.
*
* @param someName
* The name to test.
*
* @return {@code true} only if {@code someName} is equals to "BEFORE-FILL" or "AFTER-FILL",
* in ignore-case mode.
*/
private static boolean isCallbackName(String someName)
{
if (someName == null ||
!BEFORE_FILL_EVENT.equalsIgnoreCase(someName) &&
!AFTER_FILL_EVENT.equalsIgnoreCase(someName))
{
ErrorManager.recordOrShowError(11950, someName != null ? someName : "");
// Argument <arg> to CALLBACK method must be a valid buffer or dataset event name.
return false;
}
return true;
}
/**
* Get the {@link InvokeConfig callsite} for the specified event.
*
* @param eventName
* The event name.
*
* @return see above
*/
private static InvokeConfig getCallSite(String eventName)
{
return (BEFORE_FILL_EVENT.equals(eventName.toUpperCase())
? BEFORE_FILL_CALL_SITE
: AFTER_FILL_CALL_SITE);
}
/**
* Obtain the list of top-buffers. From the list of all buffers are removed those buffers that
* are found as child buffers in an active relation.
*
* @return the list of top-buffers.
*/
private List<Buffer> getTopBufferList()
{
List<Buffer> topBuffers = new LinkedList<>(buffers);
for (int i = 0; i < relations.size(); i++)
{
DataRelation relation = relations.get(i);
if (relation._isActive() &&
!relation._isReposition() &&
!relation.isRecursive().booleanValue())
{
topBuffers.remove(relation.getChildBufferNative());
}
}
return topBuffers;
}
/**
* Actual implementation of {@code CREATE-LIKE} method. The {@code sourceDS} structure,
* including {@code TempTable} and {@code DataRelation} definitions, is copied to the this
* {@code DataSet}; the source data, however, is not copied.
*
* @param sourceDS
* The source {@code DataSet}.
* @param prefix
* The name prefix for the newly created {@code DatSet}.
*
* @return {@code true} on success (and this object is a copy of the {@code sourceDS}), and
* {@code false} if the copy operation failed.
*/
private logical createLikeImpl(DataSet sourceDS, String prefix)
{
if (prefix == null)
{
prefix = "";
}
if (!validateName(prefix))
{
// no error thrown now, one was already issued by validateName()
return logical.of(false);
}
if (!_dynamic())
{
ErrorManager.recordOrShowError(11969);
// Unable to CLEAR target CREATE-LIKE dataset.
return logical.of(false);
}
// make sure the old content is gone:
clearImpl(false, false);
name(prefix + sourceDS.name().getValue());
BufferImpl[] srcBuffers = sourceDS.ref().buffers.toArray(new BufferImpl[sourceDS.getBuffers().size()]);
BufferImpl[] newBuffers = new BufferImpl[srcBuffers.length];
for (int i = 0; i < newBuffers.length; i++)
{
handle atth = TypeFactory.handle();
TempTableBuilder.create(atth);
BufferImpl srcAfterBuff = srcBuffers[i];
BufferImpl srcB4Buff = srcAfterBuff.beforeBufferNative();
String beforeName = srcB4Buff != null ? prefix + srcB4Buff._name() : null;
String tableName = prefix + srcAfterBuff.buffer().getParentTable()._name();
if (tableName.length() > 32)
{
// max length is 32
tableName = tableName.substring(0, 32);
}
TempTableBuilder newAtt = (TempTableBuilder) atth.getResource();
newAtt.createLike(new handle(srcAfterBuff));
newAtt.tempTablePrepareImpl(tableName, beforeName, false);
// TODO: anything to copy at the before-table?
copyTempTableOptions((AbstractTempTable) srcAfterBuff.tableHandleNative(), newAtt);
newBuffers[i] = (BufferImpl) newAtt.defaultBufferHandleNative();
}
setBuffersImpl(newBuffers);
// in OE 11.6 the [xmlNodeName] was observed to be copied in a different fashion
// if (xmlNodeName == null)
// {
// setXmlNodeName(prefix + sourceDS.getXmlNodeName().toStringMessage());
// }
// in OE the attributes are reportedly set as follows:
nsUri = null; //unknown
nsPrefix = null; //unknown
xmlNodeName = sourceDS.xmlNodeName; // copied
serializeName = sourceDS.serializeName; // copied
ArrayList<DataRelation> dataRelations = sourceDS.ref().relations;
for (int i = 0; i < dataRelations.size(); i++)
{
DataRelation srcRel = dataRelations.get(i);
BufferImpl srcChBuffer = srcRel.getChildBufferNative();
BufferImpl srcParBuffer = srcRel.getParentBufferNative();
BufferImpl bufParent = newBuffers[positionOf(srcBuffers, srcParBuffer)];
Buffer bufChild = newBuffers[positionOf(srcBuffers, srcChBuffer)];
if (srcRel.isParentIdRelation().booleanValue())
{
DataRelation dR = new DataRelation(this, bufParent, bufChild,
srcRel._getRelationFields(), new ArrayList<>(), new ArrayList<>(), true);
relations.add(dR);
// in OE 12+ it is reported that the name is not copied
// dR.name(srcRel.name());
// instead it is generated automatically:
dR.name("RELATION" + relations.size());
// TODO: test whether this happens for simple (non parent-id) relations
}
else
{
addRelationWorker(srcRel.name().toStringMessage(),
bufParent,
bufChild,
srcRel.getRelationFieldArray(),
srcRel._getRelationFields(),
srcRel._isReposition(),
srcRel.isNested().getValue(),
srcRel._isActive(),
srcRel.isRecursive().getValue(),
srcRel.getForeignKeyHidden().getValue(),
true);
}
}
// everything is fine:
return logical.of(true);
}
/**
* Copy the temp-table options from the source to the destination.
*
* @param src
* The source table.
* @param dst
* The destination table.
*/
private static void copyTempTableOptions(AbstractTempTable src, AbstractTempTable dst)
{
// TODO: any other options to copy?
// copy the following from temp-table:
// XML-NODE-NAME
// SERIALIZE-NAME
if (!src.getXmlNodeName().isUnknown())
{
dst.setXmlNodeName(src.getXmlNodeName());
}
if (!src.getSerializeName().isUnknown())
{
dst.setSerializeName(src.getSerializeName());
}
// copy the following from fields:
// SERIALIZE-NAME
// SERIALIZE-HIDDEN
// XML-DATA-TYPE
// XML-NODE-TYPE
// XML-NODE-NAME
BufferImpl bufSrc = (BufferImpl) src.defaultBuffer().getDMOProxy();
AbstractTempTable srcTT = (AbstractTempTable) src.defaultBuffer().tableHandleResource();
BufferImpl bufDst = (BufferImpl) dst.defaultBuffer().getDMOProxy();
int numFields = bufSrc._numFields();
Object[] fields = (Object[]) bufSrc.buffer().getDmoInfo().getExistingFields().toArray();
for (int i = 0; i < numFields; i++)
{
BufferFieldImpl dstField = bufDst.bufferFieldNative(i + 1);
// check if the sources has mutable info which has changed
TableMapper.MutableFieldInfo srcFieldInfo = srcTT.getMutableInfo(((P2JField) fields[i])
.getName().toLowerCase(),
false,
null);
if (srcFieldInfo != null)
{
// if this has been mutated in the source temp-table, then copy it; otherwise, let the defaults
SerializeOptions srcSerOpts = srcFieldInfo.getSerializeOptions();
if (srcSerOpts.getSerializeName() != null)
{
dstField.setSerializeName(srcSerOpts.getSerializeName());
}
dstField.setSerializeHidden(srcSerOpts.isSerializeHidden());
if (srcSerOpts.getXmlDataType() != null)
{
dstField.setXmlDataType(srcSerOpts.getXmlDataType());
}
if (srcSerOpts.getXmlNodeType() != null)
{
dstField.setXmlNodeType(srcSerOpts.getXmlNodeType());
}
if (srcSerOpts.getXmlNodeName() != null)
{
dstField.setXmlNodeName(srcSerOpts.getXmlNodeName());
}
if (srcFieldInfo.getColumnLabel() != null)
{
dstField.setColumnLabel(srcFieldInfo.getColumnLabel());
}
if (srcFieldInfo.getHelp() != null)
{
dstField.setHelp(srcFieldInfo.getHelp());
}
if (srcFieldInfo.getLabel() != null)
{
dstField.setLabel(srcFieldInfo.getLabel());
}
}
}
}
/**
* Locates a {@code BufferImpl} in an array of objects of same type.
*
* @param buffers
* A buffers of items.
* @param item
* The item to be located.
*
* @return the first occurrence of the {@code item} in the {@code buffers} or -1 if not found.
*/
private static int positionOf(BufferImpl[] buffers, BufferImpl item)
{
for (int i = 0; i < buffers.length; i++)
{
if (item.ref() == buffers[i].ref())
{
return i;
}
}
return -1;
}
/**
* Declares this static {@code DataSet} as build. This will prohibit some of the setters to be
* called.
*/
private void finishBuild()
{
buildInProgress = false;
}
/**
* Builder class that helps construct a {@code DataSet} object statically, that is, using the
* {@code DEFINE DATASET} statement. It exposes a set of chaining methods that take each
* option parameters and set them into the {@code DataSet} being constructed. The chaining must
* finish with {@link #generate()} to obtain the final {@code DataSet} object. Before
* returning, the {@code generate()} will do the final checks and register the object to be
* returned with the {@link DataSetManager}.
*/
public static class Builder
{
/** This is a builder for a shared {@code DataSet}. */
private final boolean isShared;
/** This is a builder for a shared new {@code DataSet}. */
private final boolean isNew;
/** The legacy name of the {@code DataSet}. */
private final String legacyName;
/** This is a reference of a {@code DataSet} defined in another procedure / class. */
private boolean referenceOnly = false;
/** The build actions to perform. */
private final ArrayList<Runnable> buildChain = new ArrayList<>(7);
/** The build dataset instance. */
private DataSet target = null;
/** The defining type where this dataset is constructed. */
private Class<?> definingType = null;
/**
* The constructor. Initialize the private final fields that will be used in the final step
* of the construction: to parametrize the registration of the {@code DataSet}.
*
* @param legacyName
* The legacy name of the {@code DataSet}.
* @param isShared
* This is a shared {@code DataSet}.
* @param isNew
* This is a new {@code DataSet}. Only in combination with {@code isShared}.
*/
public Builder(String legacyName, boolean isShared, boolean isNew)
{
this.legacyName = legacyName;
this.isShared = isShared;
this.isNew = isNew;
}
/**
* Set the defining type where this dataset is constructed.
*
* @param type
* The defining type.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setDefiningType(Class<?> type)
{
this.definingType = type;
return this;
}
/**
* Sets the namespace attribute. Corresponds to {@code NAMESPACE-URI} option from
* {@code DEFINE DATASET} statement.
*
* @param namespace
* The namespace for the {@code DataSet} to be constructed.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setNamespaceUri(String namespace)
{
buildChain.add(() -> target.namespaceURI(namespace));
return this;
}
/**
* Sets the ns prefix attribute. Corresponds to {@code NAMESPACE-PREFIX} option from
* {@code DEFINE DATASET} statement.
*
* @param prefix
* The ns prefix for the {@code DataSet} to be constructed.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setNamespacePrefix(String prefix)
{
buildChain.add(() -> target.namespacePrefix(prefix));
return this;
}
/**
* Sets the name of the xml node attribute. Corresponds to {@code XML-NODE-NAME} option from
* {@code DEFINE DATASET} statement.
*
* @param nodeName
* The xml node name for the {@code DataSet} to be constructed.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setXmlNodeName(String nodeName)
{
buildChain.add(() -> target.setXmlNodeName(nodeName));
return this;
}
/**
* Sets the serialize name attribute. Corresponds to {@code SERIALIZE-NAME} option from
* {@code DEFINE DATASET} statement.
*
* @param serName
* The serialize name for the {@code DataSet} to be constructed.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setSerializeName(String serName)
{
buildChain.add(() -> target.setSerializeName(serName));
return this;
}
/**
* Sets the xml node type attribute. Corresponds to {@code XML-NODE-TYPE} option from
* {@code DEFINE DATASET} statement.
*
* @param nodeType
* The node type for the {@code DataSet} to be constructed.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setXmlNodeType(String nodeType)
{
buildChain.add(() -> target.setXmlNodeType(nodeType));
return this;
}
/**
* Mark the DataSet's serialize-hidden attribute. Corresponds to {@code SERIALIZE-HIDDEN}
* option from {@code DEFINE DATASET} statement.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setSerializeHidden()
{
buildChain.add(() -> target.setSerializeHidden(true));
return this;
}
/**
* Mark the DataSet as reference only. Corresponds to {@code REFERENCE-ONLY} option from
* {@code DEFINE DATASET} statement.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setReferenceOnly()
{
this.referenceOnly = true;
return this;
}
/**
* Configures the set of buffers for the {@code DataSet}. Corresponds to {@code FOR} option
* from {@code DEFINE DATASET} statement.
*
* @param bufs
* The set buffers for the {@code DataSet} to be constructed.
*
* @return A reference of this object in order to help chaining.
*/
public Builder forBuffers(Buffer... bufs)
{
buildChain.add(() -> target.setBuffersImpl(bufs));
return this;
}
/**
* Configure a data relation. Corresponds to {@code DATA-RELATION} set of options from
* {@code DEFINE DATASET} statement.
*
* @param relName
* The relation name.
* @param reposition
* Specifies the relation mode as {@code REPOSITION} as opposed to the default
* {@code SELECTION} relation mode.
* @param nested
* If {@code true}, the child rows of a {@code DataSet} buffer are nested within
* their parent rows when writing a JSON representation of data or an XML
* representation of data or schema.
* @param fkHidden
* Specifies that foreign key fields in nested data relations are omitted when
* writing JSON or XML data, and that foreign key fields are optional in the XML
* Schema representation of the child table definition.
* If {@code NESTED} option is {@code false}, this option is ignored.
* @param inactive
* Setting this to {@code true} allows to have two relations between the same two
* DataSet temp-table buffers, but only have one relation active at a time.
* @param recursive
* Allows DataSet {@code FILL} method to load self-referencing elements.
* @param parent
* The parent buffer in this data-relation. This must be one of the buffers
* identified by the buffers added with {@code forBuffers()} above.
* @param child
* The child buffer in this data-relation. This must be one of the buffers
* identified by the buffers added with {@code forBuffers()} above.
* @param relationFieldPairs
* A set of field pairs (parent, child)*.
*
* @return A reference of this object in order to help chaining.
*/
public Builder addDataRelation(String relName,
boolean reposition,
boolean nested,
boolean fkHidden,
boolean inactive,
boolean recursive,
Buffer parent,
Buffer child,
FieldPair... relationFieldPairs)
{
logical ok = logical.of(true);
buildChain.add(() -> {
BufferImpl bufParent = ((BufferImpl) parent).ref();
BufferImpl bufChild = ((BufferImpl) child).ref();
if (!target.getBuffers().contains(bufParent) || !target.getBuffers().contains(bufChild))
{
ErrorManager.throwError(12384);
// DATA-RELATION parent and child buffers must also be in the dataset.
ok.assign(false);
return;
// this is a compile-time error, followed by "Could not understand line <number>. (196)
}
StringBuilder strPairs = new StringBuilder();
String[] fields = new String[relationFieldPairs.length * 2];
for (int i = 0; i < relationFieldPairs.length; i++)
{
if (strPairs.length() != 0)
{
strPairs.append(",");
}
String parentField = relationFieldPairs[i].parentField;
if (StringHelper.startsWithIgnoreCase(parentField, bufParent.doGetName() + "."))
{
parentField = parentField.substring(bufParent.doGetName().length() + 1);
}
// this will cause an NPE because the buffer scope is not yet open so the parent table is not
// configured
// if (!bufParent.hasField(parentField))
// {
// ErrorManager.throwError(12385);
// // Pairs in DATA-RELATION RELATION-FIELDS phrase must be in the form: parent-field, child-field, parent-field, child-field etc.
// return null;
// // this is a compile-time error, followed by "Could not understand line <number>. (196)
// }
String childField = relationFieldPairs[i].childField;
if (StringHelper.startsWithIgnoreCase(childField, bufChild.doGetName() + "."))
{
childField = childField.substring(bufChild.doGetName().length() + 1);
}
// this will cause an NPE because the buffer scope is not yet open so the parent table is not
// configured
// if (!bufChild.hasField(childField))
// {
// ErrorManager.throwError(12385);
// // Pairs in DATA-RELATION RELATION-FIELDS phrase must be in the form: parent-field, child-field, parent-field, child-field etc.
// return null;
// // this is a compile-time error, followed by "Could not understand line <number>. (196)
// }
fields[2 * i] = parentField;
fields[2 * i + 1] = childField;
strPairs.append(parentField).append(",").append(childField);
}
target.addRelationWorker(relName, parent, child, fields, strPairs.toString(),
reposition, nested, !inactive, recursive, fkHidden, false);
});
return ok.getValue() ? this : null;
}
/**
* This method chains a {@code PARENT-ID-RELATION} option to the {@code DataSet} builder.
*
* @param relName
* A name for the data relation.
* @param parentBuffer
* The parent buffer.
* @param childBuffer
* The child buffer.
* @param parentIdField
* The field from child buffer of {@code RECID} type that points to a record in
* parent buffer.
*
* @return A reference of this object in order to help chaining.
*/
public Builder addParentIdRelation(String relName,
Buffer parentBuffer,
Buffer childBuffer,
String parentIdField)
{
buildChain.add(() -> {
DataRelation dR = new DataRelation(target, parentBuffer, childBuffer,
parentIdField, new ArrayList<>(), new ArrayList<>(), false);
dR.name(relName);
// the REPOSITION attribute might be set, depending on the OE version
// dR.setReposition(true);
target.ref().relations.add(dR);
});
return this;
}
/**
* This method chains a {@code PARENT-ID-RELATION} option to the {@code DataSet} builder.
*
* @param relName
* A name for the data relation.
* @param parentBuffer
* The parent buffer.
* @param childBuffer
* The child buffer.
* @param parentIdField
* The field from child buffer of {@code RECID} type that points to a record in
* parent buffer.
* @param parentFieldsBefore
* The list of fields from the {@code parentBuffer} temp-table record that are to
* be written to XML before any {@code childBuffer} temp-table records.
* @param parentFieldsAfter
* The list of fields from the {@code parentBuffer} temp-table record that are to
* be written to XML after all {@code childBuffer} temp-table records.
*
* @return A reference of this object in order to help chaining.
*/
public Builder addParentIdRelation(String relName,
Buffer parentBuffer,
Buffer childBuffer,
String parentIdField,
String[] parentFieldsBefore,
String[] parentFieldsAfter)
{
buildChain.add(() -> {
DataRelation dR = new DataRelation(target,
parentBuffer,
childBuffer,
parentIdField,
Arrays.asList(parentFieldsBefore),
Arrays.asList(parentFieldsAfter),
false);
dR.name(relName);
// the REPOSITION attribute might be set, depending on the OE version
// dR.setReposition(true);
target.ref().relations.add(dR);
});
return this;
}
/**
* Validates and registers the {@code DataSet} to {@code DataSetManager}, making it valid.
*
* @return the parent {@code DataSet}.
*/
public DataSet generate()
{
target = new DataSet(legacyName, false, referenceOnly);
// the actual build of the dataset
for (int i = 0; i < buildChain.size(); i++)
{
Runnable step = buildChain.get(i);
step.run();
}
// prevent further options to be set
target.finishBuild();
target = StaticDataSet.create(target);
((StaticDataSet) target).setDefiningType(definingType);
// register with DataSetManager.
DataSetManager.instance().register(target, legacyName, isShared, isNew);
// end chain by returning the final object
return target;
}
}
/**
* Container immutable class that abstracts a field mapping between the parent and child
* tables/buffers in a data relation.
*/
public static class FieldPair
{
/** The parent field of the relation. */
private final String parentField;
/** The child field of the relation. */
private final String childField;
/**
* The constructor
* @param parentField
* The parent field of the relation.
* @param childField
* The child field of the relation.
*/
public FieldPair(String parentField, String childField)
{
this.parentField = parentField;
this.childField = childField;
}
}
/**
* Flag for debugging level. When {@code true} the full dataset is returned at the expense of CPU clocks.
* When {@code false} only the essential dataset structure is returned. Intentionally this is defined as
* non-final private here and has no accessors so that it can be r/w only at debug time.
*/
private static boolean fullDebugString = false;
/**
* Obtain a short description of the dataset used for debugging. If {@code fullDebugString} is {@code true}
* the full dataset (all rows of all buffers) is dumped as a JSON string. If only the dataset structure
* is needed, set {@code fullDebugString} to {@code false}.
*
* @return a short description of the dataset used for debugging.
*/
@Override
public String toString()
{
return fullDebugString ? getDataAsJson() : getSchema();
}
/**
* Obtain a short description of the dataset structure: buffers and relations between them.
*
* @return a string describing the important elements of the dataset structure.
*/
private String getSchema()
{
StringBuilder sb = new StringBuilder();
sb.append("{").append(name).append(": ");
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl buffer = buffers.get(i);
sb.append(buffer.doGetName()).append(" ");
}
sb.append("(");
for (int i = 0; i < relations.size(); i++)
{
DataRelation rel = relations.get(i);
sb.append(rel.name().getValue()).append("=");
sb.append(rel.getParentBufferNative().doGetName()).append("+");
sb.append(rel.getChildBufferNative().doGetName()).append(" ");
}
sb.append(") }");
return sb.toString();
}
/**
* Get the JSON representation of data of this DATASET. The returned value contains data from all tables of
* the dataset, but not the relations between them.
*
* @return See above.
*/
private String getDataAsJson()
{
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> fullResult = new HashMap<>();
Map<String, Object> datasetResult = new HashMap<>();
fullResult.put(name, datasetResult);
for (BufferImpl buffer : buffers)
{
String tableName = buffer.buffer().getDmoInfo().legacyTable;
RecordBufferSerializer recordBufferSerializer = new RecordBufferSerializer(buffer.buffer());
try
{
datasetResult.put(tableName, recordBufferSerializer.sqlTableContent());
}
catch (PersistenceException | SQLException e)
{
log.severe("Error getting data: " + e.getMessage());
datasetResult.put("error", "Error getting data: " + e.getMessage());
}
}
try
{
return objectMapper.writeValueAsString(fullResult);
}
catch (JsonProcessingException e)
{
log.severe("JSON processing error: " + e.getMessage());
return "{\"error\": \"Error generating JSON\"}";
}
}
/**
* Get the SQL representation of data of this DATASET. The returned value contains data from all tables of
* the dataset, but not the relations between them.
*
* @return See above.
*/
@SuppressWarnings("unused")
private String getSqlData()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl buffer = buffers.get(i);
// output the main/after buffer content
if (sb.length() != 0)
{
sb.append("\n");
}
try
{
sb.append(buffer.buffer().sqlTableContent(0));
}
catch (PersistenceException | SQLException e)
{
sb.append("Error getting data: ").append(e.getMessage()).append("\n");
}
// if exists, output the before buffer content, too
BufferImpl before = buffer.beforeBufferNative();
if (before != null && before.valid())
{
try
{
sb.append("\n").append(before.buffer().sqlTableContent(0));
}
catch (PersistenceException | SQLException e)
{
sb.append("Error getting data: ").append(e.getMessage()).append("\n");
}
}
}
return sb.toString();
}
}