DataRelation.java
/*
** Module : DataRelation.java
** Abstract : A data relation object.
**
** Copyright (c) 2019-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM 20190403 Created initial version.
** 002 OM 20190611 Added runtime implementation.
** 003 OM 20190716 Calling the right super c'tor based on the parent dataset's dynamic attribute.
** OM 20190721 Validate NAME attribute.
** CA 20190812 Changes to allow for mutable buffers; any API which receives a Buffer instance
** and is invoked from converted code must resolve the runtime instance before
** saving the instance.
** 004 OM 20201030 Invalid attribute API support for getters/setters.
** OM 20201202 Improved compatibility of attributes with P4GL.
** OM 20210930 Only the name of dynamic relations have name constraints.
** OM 20220120 Added getRelationFields(boolean) method.
** CA 20220923 Internal var definitions must be done via TypeFactory and not UndoableFactory.
** CA 20220608 Made getRelationFieldArray public.
** CA 20220918 Automatically delete the created dynamic query when the relation gets deleted.
** CA 20221129 Reset FILL-WHERE-STRING when relation is deactivated.
** 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.
** 005 IAS 20230312 Added 'getParentTable()/getChildTable()' methods.
** 006 OM 20230315 get***BufferNative() methods return BufferImpl values.
** 007 CA 20230523 Added _isRecursive, which returns Java boolean.
** 008 CA 20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** 009 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.
** 010 RNC 20241030 Created getQuery(boolean) method.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.persist;
import com.goldencode.p2j.util.*;
import com.goldencode.util.*;
import java.util.*;
/**
* A {@code DataRelation} defines one relation between a pair of parent and child buffers in a
* {@code DataSet} that have a one-to-one or one-to-many parent-child relationship. A
* {@code DataRelation} identifies the parent and child buffers, and the fields in each buffer
* that define the primary and foreign key fields of the relation.
*/
@LegacyResource(resource = LegacyResource.DATA_RELATION)
public class DataRelation
extends HandleResource
implements ADMData,
CommonHandleChain,
DataRelationInterface,
DynamicResource,
QueryAssociable
{
/** The parent {@code DataSet} where this {@code DataRelation} is defined. */
private final DataSet dataSet;
/** The parent buffer. */
private final BufferImpl parentBuffer;
/** The child buffer. */
private final BufferImpl childBuffer;
/** {@code true} to flag this as a RECURSIVE relation. */
private final boolean recursive;
/** The relation fields for this object. */
private final String[] relationFields;
/** The set of AFTER fields. */
private final List<String> afterFields;
/** The set of AFTER fields. */
private final List<String> beforeFields;
/** For PARENT-ID relations, this specifies the field that points to parent 'foreign' key. */
private final String parentLink;
/** The ACTIVE status of this relation. */
private boolean active = true;
/** The NESTED status of this relation. */
private boolean nested = false;
/** The REPOSITION status of this relation. */
private boolean reposition = false;
/** The FOREIGN-KEY-HIDDEN status of this relation. */
private boolean fkHidden = false;
/** The MAXIMUM-LEVEL status of this relation. */
private int maximumLevel = 0;
/** The current value of {@code WHERE-STRING} attribute. */
private String whereString = null;
/** Current value of PRIVATE-DATA. */
private String privateData = null;
/** Current value of PRIVATE-DATA. */
private String admData = null;
/** The value of {@code NAME} attribute. */
private String name = null;
/**
* Flags for valid property. The {@code DataRelation}s are valid from the moment they are
* created until the object is dropped/deleted. Once it is deleted and made invalid, the
* object cannot be made valid again.
*/
private boolean isValid = true;
/** The current value of {@code QUERY} attribute. Uses lazy initialization. */
private QueryWrapper query = null;
/**
* Reference counter. Only used to create incremental names for unnamed (static)
* {@code DataRelations}.
*/
private static final RefCount refCount = new RefCount();
/**
* Creates a new simple {@code DataRelation} based on join between pairs of fields from parent
* and child buffer. This is the constructor called when a {@code ADD-RELATION} method is
* successful.
*
* @param dataSet
* The {@code DataSet}.
* @param parent
* The parent buffer.
* @param child
* The child buffer.
* @param relationFields
* The set of fields. The list has an even number of values, the values on odd indexes
* being the parent fields and the even ones are the child fields.
* @param strPairs
* The pair of fields, as string as they appear in the legacy statement.
* @param recursive
* {@code true} if this is a recursive relation.
*/
public DataRelation(DataSet dataSet,
Buffer parent,
Buffer child,
String[] relationFields,
String strPairs,
boolean recursive)
{
super(dataSet._dynamic());
this.dataSet = dataSet.ref();
this.parentBuffer = ((BufferImpl) parent).ref();
this.childBuffer = ((BufferImpl) child).ref();
this.relationFields = relationFields;
this.recursive = recursive;
// create the default WHERE-STRING
String pbn = ((BufferImpl) parent)._name();
String cbn = ((BufferImpl) child)._name();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < relationFields.length; i += 2)
{
sb.append(sb.length() == 0 ? " WHERE " : " and ");
sb.append(cbn).append('.').append(relationFields[i + 1]).append('=');
sb.append(pbn).append('.').append(relationFields[i]);
}
// use lazy-initialization for [query]
this.whereString = sb.toString();
// mark unused fields for this kind of DataRelation:
this.afterFields = null;
this.beforeFields = null;
this.parentLink = null;
}
/**
* Creates a new simple {@code DataRelation} based on join between pairs of fields from parent and child
* buffer. This is the constructor called when a {@code ADD-PARENT-ID-RELATION} method is successful.
*
* @param parent
* The parent buffer.
* @param child
* The child buffer.
* @param parentLink
* The name of the {@code RECID} field of {@code child} buffer that is used to join
* with the {@code parent} buffer.
* @param beforeFields
* The list of fields to be serialized before.
* @param afterFields
* The list of fields to be serialized after.
* @param dynamic
* Use {@code true} for dynamically created relations.
*/
public DataRelation(DataSet dataSet,
Buffer parent,
Buffer child,
String parentLink,
List<String> beforeFields,
List<String> afterFields,
boolean dynamic)
{
super(dataSet._dynamic());
this.dataSet = dataSet.ref();
this.parentBuffer = ((BufferImpl) parent).ref();
this.childBuffer = ((BufferImpl) child).ref();
this.parentLink = parentLink;
this.beforeFields = beforeFields.isEmpty() ? null : beforeFields;
this.afterFields = afterFields.isEmpty() ? null : afterFields;
// create the default WHERE-STRING
String pbn = ((BufferImpl) parent)._name();
String cbn = ((BufferImpl) child)._name();
// use lazy-initialization for [query]
// I found different "styles" of essentially the same predicate, the only differences being the single
// space prefix and capitalization for WHERE and RECID keywords
// this.whereString = "WHERE " + cbn + '.' + parentLink + "=RECID(" + pbn + ')';
this.whereString = " where " + cbn + '.' + parentLink + "=recid(" + pbn + ')';
// mark unused fields for this kind of DataRelation:
this.recursive = false;
this.relationFields = null;
if (dynamic)
{
confirmDynamic();
}
}
/**
* Confirms that this object is dynamic.<p>
* This method finishes setting this object parent-procedure and makes sure it is deleted at the
* appropriate moment (when the parent dataset is deleted). This is only needed for dynamically created
* relations.
*/
void confirmDynamic()
{
pm.processResource(this, true);
}
/**
* Corresponds to {@code ACTIVE} ABL attribute. Deactivate an individual data-relation in a
* {@code DataSet} object.
*
* @return the current state of {@code ACTIVE} attribute.
*/
@Override
public logical isActive()
{
return logical.of(active);
}
/**
* Corresponds to {@code ACTIVE} ABL attribute. Deactivate an individual data-relation in a
* {@code DataSet} object.
*
* @param active
* The new state of {@code ACTIVE} attribute.
*/
@Override
public void setActive(boolean active)
{
if (this.active == active)
{
return;
}
if (_isParentIdRelation() || !active)
{
this.active = active;
DataSource childDSsrc = (DataSource) this.childBuffer.dataSourceNative();
if (!active && childDSsrc != null)
{
childDSsrc.setFillWhereString((String) null); // reset
}
}
else
{
if (dataSet.allowsActiveChild(childBuffer) == null)
{
this.active = true;
}
else
{
ErrorManager.recordOrShowError(11979, childBuffer.name().toStringMessage());
// Buffer <name> can have only 1 active parent relation.
}
}
}
/**
* Corresponds to {@code ACTIVE} ABL attribute. Deactivate an individual data-relation in a
* {@code DataSet} object.
*
* @param active
* The new state of {@code ACTIVE} attribute.
*/
@Override
public void setActive(logical active)
{
if (active == null || active.isUnknown())
{
unableToAssignUnknown("ACTIVE", "DATA-RELATION widget");
return;
}
setActive(active.getValue());
}
/**
* Obtain the buffer handle of the child member of this data-relation object. This method
* corresponds to the real-only {@code CHILD-BUFFER} attribute.
*
* @return the buffer handle of the child member.
*/
@Override
public handle getChildBuffer()
{
return new handle(childBuffer);
}
/**
* Obtain the buffer of the child member of this data-relation object. This method
* corresponds to the real-only {@code CHILD-BUFFER} attribute.
*
* @return the buffer of the child member.
*/
public BufferImpl getChildBufferNative()
{
return childBuffer;
}
/**
* Obtain the name of the child table.
*
* @return the name of the child table.
*/
public String getChildTable()
{
return childBuffer.getTable().toJavaType();
}
/**
* Obtain the handle to the {@code DataSet} query that contains the currently selected row in
* the {@code BindingSource}. Corresponds to {@code CURRENT-QUERY} ABL method.
*
* @return the handle to the {@code DataSet} query that contains the currently selected row
*/
@Override
public handle getCurrentQuery()
{
UnimplementedFeature.missing("Attribute CURRENT-QUERY not implemented");
return new handle();
}
/**
* Obtain the handle to the {@code DataSet} query that contains the currently selected row in
* the {@code BindingSource}. Corresponds to {@code CURRENT-QUERY} ABL method.
*
* @param bandIndex
* 0-based integer value that specifies the band index in a hierarchical grid
*
* @return the handle to the {@code DataSet} query that contains the currently selected row
*/
@Override
public handle getCurrentQuery(long bandIndex)
{
UnimplementedFeature.missing("Attribute CURRENT-QUERY not implemented");
return new handle();
}
/**
* Obtain the handle to the {@code DataSet} query that contains the currently selected row in
* the {@code BindingSource}. Corresponds to {@code CURRENT-QUERY} ABL method.
*
* @param bandIndex
* 0-based integer value that specifies the band index in a hierarchical grid
*
* @return the handle to the {@code DataSet} query that contains the currently selected row
*/
@Override
public handle getCurrentQuery(NumberType bandIndex)
{
UnimplementedFeature.missing("Attribute CURRENT-QUERY not implemented");
return new handle();
}
/**
* Obtains a comma-separated list of parent-field, child-field pairs describing the
* relationship between parent and child buffers as specified in the data-relation object
* definition. Corresponds to {@code RELATION-FIELDS} readonly ABL attribute.
*
* @return the set of field pairs that describes the data-relation.
*/
@Override
public character getRelationFields()
{
return new character(_getRelationFields());
}
/**
* Obtains the current WHERE expression from the query generated that links the child table to
* its parent. Corresponds to {@code WHERE-STRING} attribute.
*
* @return the current WHERE expression that links the child table to its parent.
*/
@Override
public character getWhereString()
{
return new character(whereString);
}
/**
* Modifies the current WHERE expression that links the child table to its parent. This is the
* setter implementation for {@code WHERE-STRING} ABL attribute.
*
* @param where
* the new predicate.
*/
@Override
public void setWhereString(String where)
{
if (createQuery(where))
{
whereString = where;
}
else
{
ErrorManager.recordOrShowError(11981);
// Failed to auto-prepare an automatic fill query.
ErrorManager.recordOrShowError(3131, "WHERE-STRING", "", LegacyResource.DATA_RELATION);
// Unable to set attribute %1 in widget %2 of type %3.
}
}
/**
* Modifies the current WHERE expression that links the child table to its parent. This is the
* setter implementation for {@code WHERE-STRING} ABL attribute.
* <p>
* Unlike other attributes, this attribute can be set tu unknown value!
*
* @param where
* the new predicate.
*/
@Override
public void setWhereString(Text where)
{
setWhereString(where.toJavaType());
}
/**
* Checks whether the foreign key fields in the child records of a nested data-relation are
* hidden. This is the getter implementation of the ABL {@code FOREIGN-KEY-HIDDEN} attribute.
*
* @return the current value of {@code FOREIGN-KEY-HIDDEN} attribute for this relation.
*/
@Override
public logical getForeignKeyHidden()
{
return logical.of(fkHidden);
}
/**
* Configures whether the foreign key fields in the child records of a nested data-relation are
* hidden. This is the setter implementation of the ABL {@code FOREIGN-KEY-HIDDEN} attribute.
*
* @param hide
* the new value of {@code FOREIGN-KEY-HIDDEN} attribute of this relation.
*/
@Override
public void setForeignKeyHidden(boolean hide)
{
this.fkHidden = hide;
}
/**
* Configures whether the foreign key fields in the child records of a nested data-relation are
* hidden. This is the setter implementation of the ABL {@code FOREIGN-KEY-HIDDEN} attribute.
*
* @param hide
* the new value of {@code FOREIGN-KEY-HIDDEN} attribute of this relation.
*/
@Override
public void setForeignKeyHidden(logical hide)
{
if (hide == null || hide.isUnknown())
{
unableToAssignUnknown("FOREIGN-KEY-HIDDEN", "DATA-RELATION widget");
return;
}
setForeignKeyHidden(hide.getValue());
}
/**
* Obtain the maximum level where a recursive data-relation stops. This is the getter part of
* the {@code MAXIMUM-LEVEL} ABL attribute.
*
* @return the number of iterations at which this recursion stops.
*/
@Override
public integer getMaximumLevel()
{
return new integer(maximumLevel);
}
/**
* Configures the maximum level where a recursive data-relation stops. This is the setter part
* of the {@code MAXIMUM-LEVEL} ABL attribute.
*
* @param max
* the new number of iterations at which the recursion stops.
*/
@Override
public void setMaximumLevel(NumberType max)
{
if (max == null || max.isUnknown())
{
unableToAssignUnknown("MAXIMUM-LEVEL", "DATA-RELATION widget");
return;
}
setMaximumLevel(max.intValue());
}
/**
* Configures the maximum level where a recursive data-relation stops. This is the setter part
* of the {@code MAXIMUM-LEVEL} ABL attribute.
*
* @param max
* the new number of iterations at which the recursion stops.
*/
@Override
public void setMaximumLevel(long max)
{
maximumLevel = (int) max;
}
/**
* Obtain the reposition mode of this data-relation object. When {@code true}, the relation
* mode is {@code REPOSITION}. Otherwise, the relation mode is {@code SELECTION}. The default
* value is {@code false}. This is the getter of the {@code REPOSITION} attribute.
*
* @return {@code true} when the reposition mode is {@code REPOSITION} and {@code false}
* when reposition mode is {@code SELECTION}.
*/
@Override
public logical getReposition()
{
return logical.of(reposition);
}
/**
* Sets the reposition mode of this data-relation object. Use {@code true} to set the relation
* mode to {@code REPOSITION} and {@code false} to set it to {@code SELECTION}. This is the
* setter of the {@code REPOSITION} attribute.
*
* @param reposition
* the new reposition mode as described above.
*/
@Override
public void setReposition(logical reposition)
{
if (reposition == null || reposition.isUnknown())
{
unableToAssignUnknown("REPOSITION", "DATA-RELATION widget");
return;
}
setReposition(reposition.getValue());
}
/**
* Sets the reposition mode of this data-relation object. Use {@code true} to set the relation
* mode to {@code REPOSITION} and {@code false} to set it to {@code SELECTION}. This is the
* setter of the {@code REPOSITION} attribute.
*
* @param reposition
* the new reposition mode as described above.
*/
@Override
public void setReposition(boolean reposition)
{
this.reposition = reposition;
}
/**
* Obtain a comma-separated list of fields from the parent record in the relation to be
* written to XML after all nested child records.
*
* @return a comma-separated list of fields from the parent record in the relation to be
* written to XML after all nested child records.
*/
@Override
public character getParentFieldsAfter()
{
if (!_isParentIdRelation() || afterFields == null)
{
return new character();
}
StringBuilder sb = new StringBuilder();
for (String field : afterFields)
{
if (sb.length() != 0)
{
sb.append(',');
}
sb.append(field);
}
return new character(sb.toString());
}
/**
* Obtain a comma-separated list of fields from the parent record in the relation to be
* written to XML before any nested child records.
*
* @return a comma-separated list of fields from the parent record in the relation to be
* written to XML after all nested child records.
*/
@Override
public character getParentFieldsBefore()
{
if (!_isParentIdRelation() || beforeFields == null)
{
return new character();
}
StringBuilder sb = new StringBuilder();
for (String field : beforeFields)
{
if (sb.length() != 0)
{
sb.append(',');
}
sb.append(field);
}
return new character(sb.toString());
}
/**
* Obtain the buffer handle of the parent member. This is the FWD implementation of
* {@code PARENT-BUFFER} ABL read-only attribute
*
* @return the buffer handle of the parent member.
*/
@Override
public handle getParentBuffer()
{
return new handle(parentBuffer);
}
/**
* Obtain the buffer of the parent member. This is the FWD implementation of
* {@code PARENT-BUFFER} ABL read-only attribute
*
* @return the buffer of the parent member.
*/
public BufferImpl getParentBufferNative()
{
return parentBuffer;
}
/**
* Obtain the name of the parent table.
*
* @return the name of the parent table.
*/
public String getParentTable()
{
return parentBuffer.getTable().toJavaType();
}
/**
* Checks whether child rows of a {@code DataSet} temp-table buffer are nested within their
* parent rows when writing the XML representation of {@code DataSet} object that contains
* data-relations.
*
* @return {@code yes} if the child rows are nested within their parent rows.
*/
@Override
public logical isNested()
{
return logical.of(nested);
}
/**
* Configures whether child rows of a {@code DataSet} temp-table buffer are nested within their
* parent rows when writing the XML representation of {@code DataSet} object that contains
* data-relations.
*
* @param nested
* The new value of the {@code NESTED} attribute.
*/
@Override
public void setNested(boolean nested)
{
this.nested = nested;
}
/**
* Configures whether child rows of a {@code DataSet} temp-table buffer are nested within their
* parent rows when writing the XML representation of {@code DataSet} object that contains
* data-relations.
*
* @param nested
* The new value of the {@code NESTED} attribute.
*/
@Override
public void setNested(logical nested)
{
if (nested == null || nested.isUnknown())
{
unableToAssignUnknown("NESTED", "DATA-RELATION widget");
return;
}
setNested(nested.getValue());
}
/**
* Checks whether this data-relation is recursive.
*
* @return {@code yes} when this data-relation is recursive.
*/
@Override
public logical isRecursive()
{
return logical.of(recursive);
}
/**
* Checks whether this data-relation is recursive.
*
* @return {@code true} when this data-relation is recursive.
*/
public boolean _isRecursive()
{
return recursive;
}
/**
* Checks whether the relationship between parent and child records is based on the
* {@code RECID} type of the parent record. In a {@code PARENT-ID-RELATION}, the value of
* {@code RELATION-FIELDS} attribute will be the name of the child buffer’s {@code RECID} field.
*
* @return {@code YES} if this is a {@code PARENT-ID-RELATION}
*/
@Override
public logical isParentIdRelation()
{
return logical.of(_isParentIdRelation());
}
/**
* Get the query associated with this relation.
*
* @return The associated query or {@code unknown} if none exists.
*/
@Override
public handle getQueryAsHandle()
{
return new handle(getQuery());
}
/**
* Get the query associated with this relation.
*
* @return The associated query or {@code null} if none exists.
*/
public QueryWrapper getQuery()
{
if (query == null)
{
// this will actually be called only when the default [whereString] was not accessed yet
createQuery(whereString);
}
return query;
}
/**
* Get the query associated with this relation, but try to recreate it first if
* <code>shouldRecreate</code> is true and at least one of the following conditions hold:
* <li>the <code>prepareString</code> associated with the {@link #query} is null</li>
* @param shouldRecreate
* <code>true</code> if the query should be recreated, <code>false</code> otherwise
*
* @return The associated query or {@code null} if none exists.
*/
public QueryWrapper getQuery(boolean shouldRecreate)
{
boolean delete = false;
if (shouldRecreate && query != null)
{
delete = query.prepareString().isUnknown();
}
if (delete && query.valid())
{
query.delete();
query = null;
}
return getQuery();
}
/**
* Set the query associated with the given resource.
*
* @param qry
* The query to associate.
*/
@Override
public void setQueryAsHandle(handle qry)
{
handle.invalidAttribute("QUERY", true, LegacyResource.DATA_RELATION);
}
/**
* Set the query associated with the given resource.
*
* @param qry
* The query to associate.
*/
@Override
public void setQueryAsHandle(QueryWrapper qry)
{
handle.invalidAttribute("QUERY", true, LegacyResource.DATA_RELATION);
}
/**
* 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)
{
admData = value;
}
/**
* Set the value of the ADM-DATA attribute.
*
* @param value The new value.
*/
@Override
public void setADMData(character value)
{
admData = (value == null || value.isUnknown()) ? null : value.getValue();
}
/**
* Get the {@code name} attribute of this object.
*
* @return See above.
*/
@Override
public character name()
{
if (name == null)
{
// TODO: is this implementation enough?
name = "RELATION" + (refCount.get());
refCount.increment();
}
return new character(name);
}
/**
* Set the {@code name} attribute of this object.
*
* @param name The name to be set.
*/
@Override
public void name(String name)
{
if (name == null)
{
unableToAssignUnknown("NAME", "DATA-RELATION widget");
return;
}
// this is strange, the static relation do not have naming constraints. The dynamic ones have.
// It must be some kind of code-injection protection
if (_dynamic() && !HandleChain.validateName(name))
{
ErrorManager.recordOrThrowError(3131, "NAME", "", LegacyResource.DATA_RELATION);
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
return;
}
this.name = name;
}
/**
* Set the {@code name} attribute of this object.
*
* @param name The name to be set.
*/
@Override
public void name(character name)
{
name(name.toJavaType());
}
/**
* Reports if this object is valid for use.
*
* @return {@code true} if we are valid (can be used).
*/
@Override
public boolean valid()
{
return isValid;
}
/**
* Get the {@code NEXT-SIBLING} attribute of this handle.
*
* @return See above.
*/
@Override
public handle getNextSibling()
{
handle.invalidAttribute("NEXT-SIBLING", false, LegacyResource.DATA_RELATION);
return new handle();
}
/**
* Get the {@code PREV-SIBLING} attribute of this handle.
*
* @return See above.
*/
@Override
public handle getPrevSibling()
{
handle.invalidAttribute("PREV-SIBLING", false, LegacyResource.DATA_RELATION);
return new handle();
}
/**
* Get the {@code PRIVATE-DATA} attribute of this handle.
*
* @return See above.
*/
@Override
public character getPrivateData()
{
return new character(privateData);
}
/**
* Set the {@code PRIVATE-DATA} attribute of this handle.
*
* @param data The new private-data value.
*/
@Override
public void setPrivateData(character data)
{
privateData = (data == null || data.isUnknown()) ? null : data.getValue();
}
/**
* Set the {@code PRIVATE-DATA} attribute of this handle.
*
* @param data The new private-data value.
*/
@Override
public void setPrivateData(String data)
{
privateData = data;
}
/**
* 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 false;
}
/**
* 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.FALSE;
}
/**
* Extracts the list of relation fields for only one of the tables, in form of an array. If this is a
* {@code PARENT-ID} relation only the parent link is returned.
*
* @param parent
* Pass {@code true} for fields from parent table and {@code false} for fields from child table.
*
* @return the list of relation fields.
*/
public String[] getRelationFields(boolean parent)
{
if (_isParentIdRelation())
{
return parent ? new String[0] : new String[] { parentLink };
}
String[] ret = new String[relationFields.length / 2];
for (int i = 0; i < relationFields.length; i += 2)
{
ret[i / 2] = relationFields[parent ? i : i + 1];
}
return ret;
}
/**
* 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 HandleChain#validateName
*/
protected boolean hasNameConstraints()
{
return true;
}
/**
* Worker to be implemented by each resource. Called by {@link #delete()}.
*
* @return Always {@code true}.
*/
@Override
protected boolean resourceDelete()
{
isValid = false;
if (query != null && query.valid())
{
query.delete();
}
return true;
}
/**
* The original set of field pairs. Only used for duplication of relations.
*
* @return original set of field pairs, if any.
*/
public String[] getRelationFieldArray()
{
return relationFields;
}
/**
* Extract the list of relation fields. If this is a {@code PARENT-ID} relation only the
* parent link is returned, otherwise the set of pairs that defines the RELATION-FIELDS
* attribute is returned.
*
* @return the list of relation fields.
*/
String _getRelationFields()
{
if (_isParentIdRelation())
{
return parentLink;
}
else
{
StringBuilder sb = new StringBuilder();
for (String field : relationFields)
{
if (sb.length() != 0)
{
sb.append(',');
}
sb.append(field);
}
return sb.toString();
}
}
/**
* Checks whether this relation is active.
*
* @return the current state of {@code ACTIVE} attribute.
*/
boolean _isActive()
{
return active;
}
/**
* Query the current value of {@code REPOSITION} attribute.
*
* @return the current value of {@code REPOSITION} attribute.
*/
boolean _isReposition()
{
return reposition;
}
/**
* Internal check whether this is a parent-id-relation.
*
* @return {@code true} when {@link #parentLink} is not {@code null}.
*/
private boolean _isParentIdRelation()
{
return parentLink != null;
}
/**
* Modifies the current WHERE expression that links the child table to its parent. This is the
* setter implementation for {@code WHERE-STRING} ABL attribute.
*
* @param where
* The new predicate.
* @return {@code true} if operation is successful.
*/
private boolean createQuery(String where)
{
// prepare the query
String pbn = parentBuffer._name();
String cbn = childBuffer._name();
StringBuilder sb = new StringBuilder("FOR EACH ");
sb.append(cbn);
if (where != null)
{
// the [localWhereString] must contain the 'WHERE' token
sb.append(' ').append(where);
}
if (query != null)
{
query.delete();
}
query = QueryWrapper.createQueryUnnamedPool();
query.setBuffers(childBuffer);
query.setSubstitutionBuffers(parentBuffer);
return query.prepare(sb.toString()).booleanValue();
}
/**
* Returns a string representation of this object.
*
* @return a string representation of this object.
*/
@Override
public String toString()
{
return name + "{" + whereString + "}";
}
}