logical.java
/*
** Module : logical.java
** Abstract : Progress 4GL compatible logical object
**
** Copyright (c) 2005-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description----------------------------------
** 001 GES 20050517 @21233 Created initial version with full support
** for all Progress logical processing including
** all operators (AND, OR and NOT are in this
** class and all others in the parent class)
** and the unknown value.
** 002 GES 20050607 @21444 Minor additions for function support.
** 003 GES 20050621 @21534 Added an assign() method to modify this
** instance's state based on the result of
** an expression of compatible type.
** 004 GES 20050721 @21765 Added a constructor that takes a string and
** merged cloning for SVG.
** 005 GES 20050815 @22120 Added Undoable interface support.
** 006 GES 20050830 @22385 Added error condition processing.
** 007 ECF 20051004 @22971 Implement hashCode() method. Required to be
** consistent with BaseDataType's implementation
** of equals() method.
** 008 GES 20051022 @23088 Handle the case of an OR operator with both
** operands as unknown.
** 009 GES 20060206 @24302 Made compareTo() safe to process any object
** type (avoids ClassCastException).
** 010 GES 20060208 @24391 Added a default format string getter and
** a new constructor that uses a format string
** to parse out a string value properly.
** 011 GES 20060312 @24987 Added variants of the OR, AND and NOT
** operators to allow primitives to be passed
** in all possible combinations.
** 012 GES 20060417 @25556 Added default value generation.
** 013 GES 20060518 @26347 "/" is an invalid format string too.
** 014 GES 20060522 @26387 Added forms of the OR and AND operators which
** defer the processing of the right by passing
** a LogicalExpression instance which is only
** evaluated conditionally. This is needed
** to ensure that right operand conditions that
** have side effects or can generate an error
** will not process if the left operand is
** enough to make a decision.
** 015 GES 20060616 @27357 OR and AND operators had unknown value
** processing reversed from what it should have
** been (the logic was mis-documented originally
** and was implemented as mis-documented). This
** has been resolved.
** 016 GES 20060724 @28163 Added a forced assign() abstract method.
** 017 GES 20060807 @28475 Moved to externalizable interface to optimize
** network performance.
** 018 ECF 20060812 @28589 Added methods needed for database server-side
** function support.
** 019 NVS 20060908 @28589 compareTo now properly compares with String
** character type data.
** 020 GES 20061109 @31051 Added formatLength().
** 021 GES 20080624 @38913 Safer and more complete version of H019.
** 022 SIY 20080721 @39150 Fixed toString() behavior in order to match
** 4GL behavior in various situations.
** 023 GES 20080911 @39791 Added assign(boolean) signature.
** 024 GES 20081001 @40035 Modified method names to shorten calling
** code for the logical operators.
** 025 ECF 20090212 @41301 Implemented a faster equals(). Handles most
** common cases without having to invoke the
** superclass' implementation.
** 026 GES 20090422 @41910 Converted to standard string formatting.
** 027 ECF 20090816 @43664 Removed final modifier from class.
** 028 GES 20090828 @43803 Make sure that ignore mode is disabled even if
** and exception occurs.
** 029 GES 20110303 Moved maximum/minimum to varargs.
** 030 CA 20130123 Added c'tor which builds a new instance from a
** BaseDataType value, if possible. This should be
** used only when constructing an instance using the
** value returned by a DYNAMIC-FUNCTION call; 4GL
** does some special casting in this case, which is
** not implemented yet.
** 031 GES 20130312 Implemented the BDT constructor. We hope it is suitable
** for any _POLY that returns a BDT which is supposed to
** be a logical.
** 032 GES 20130322 Fixed the BDT c'tor and modified the assign(BDT,boolean) to be the
** common location for the _POLY morphing. This is safe because all
** current usage would have generated a ClassCastException if that
** method was ever used with an invalid type. Now, instead of the CCE,
** the code will morph the value as is possible. The morphing code
** still needs to be checked.
** 033 MAG 20140602 Fixed the BDT c'tor and modified the assign(BDT, boolean) on _POLY
** morphing. Add BDT types on compareTo(Object).
** Improve compareTo(Object) by comparing unknown values.
** 034 CA 20150310 Fixed initialization of logical values from a string.
** 035 OM 20150630 Documented char-to-logical conversion quirk.
** 036 CA 20151222 Added instantiateDefaultExtent.
** 037 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 038 GES 20160309 Fixed fromString() to match new undocumented 4GL cases.
** 039 OM 20160302 Fixed logical(boolean) constructor.
** 040 GES 20160518 Switched from LogicalExpression anon instances to LogicalOp lambdas
** for AND and OR operator deferred evaluation.
** 041 GES 20160617 Added unknown literal processing for the polymorphic assign case.
** 042 CA 20160627 Reworked undoable support - the undoables register themselves with
** all blocks up the stack, until either 1. the tx block which created
** it is reached or 2. the last tx block where it was saved is reached.
** 043 GES 20171206 Changes in ErrorManager make silent error mode support unnecessary.
** 044 CA 20180514 Added logical(character, character) c'tor.
** 045 OM 20190206 Added implementation of field size needed by record-length.
** 046 CA 20190927 Added support for direct Java access from 4GL code.
** 047 RFB 20200804 _and() was evaluating the 2nd operator, instead of letting it flow to the and()
** method. This was causing issues if the 2nd operator was unnecessary, and would
** have side-effect results, like subscript out-of-range.
** GES 20200806 Reduced ErrorManager context-local usage.
** ME 20200810 Improvements to string parsing.
** EVL 20200818 Fix for value creation issue from empty string for custom format case that
** considers empty string as valid false logical.
** GES 20200820 Fix for fromString() "trash matching" mode and slight performance improvement.
** 048 IAS 20201007 Added Type enum
** 049 VVT 20210403 splitFormat() fixed: only one slash character is allowed. See #4880-67.
** VVT 20210322 Fixed after the review. See #4880-107.
** 050 ME 20210504 Added isIncompatibleTypesOnConversion override for invalid POLY conversion.
** CA 20210923 Fixed logical(BDT, fmt) builtin function.
** OM 20220303 Renamed defaultFormat to DEFAULT_FORMAT and granted public access.
** General code maintenance.
** AL2 20220319 Added value proxy check in assign.
** 051 CA 20230215 'duplicate()' method returns the real type instead of BDT.
** 052 CA 20231129 Avoid additional BDT creation for some operators which receive Java arguments.
** 053 CA 20240313 Converting a string to logical must not raise an error or set the
** ERROR-STATUS:FLAG if the value does not match the format.
** 054 CA 20240320 Avoid BDTs within internal FWD runtime. Replaced iterators with Java 'for',
** where it applies.
** 055 CA 20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** CA 20240331 logical type checks must include sub-classes, too.
** 056 SP 20240415 Fixed assign(boolean): added missing checkUndoable().
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
import java.math.*;
import java.util.*;
import java.util.function.*;
/**
* A class that represents a Progress 4GL compatible logical object whose
* data is mutable. All Progress language features related to logical
* types are supported including all function that can be accessed via the
* logical operators. In particular, the <code>AND</code>, <code>OR</code>
* and <code>NOT</code> operators are implemented in this class (they are
* unique to logicals) and all other logical operators are implemented in
* the {@link CompareOps} class.
* <p>
* The only reason this class exists is to provide an object that represents
* 3 possible states: <code>true</code>, <code>false</code> and the
* <code>unknown value</code>. This is the only difference between the
* Progress 4GL logical data type and the Java primitive <code>boolean</code>.
* While a <code>java.lang.Boolean</code> could be used and set to
* <code>null</code> to indicate the <code>unknown value</code>, this
* approach has the significant drawback of requiring unwrapping everywhere
* and in addition, <code>null</code> checks are required wherever the
* data is unwrapped. This would require a very nasty bit of refactoring for
* most expressions, which is made unnecessary by this class which represents
* the tri-state of a logical using only internal means. Since all operators
* have been implemented as method calls and these methods are aware of the
* <code>unknown value</code>, the resulting expressions are clean and
* as close to the original level of expressiveness as possible.
* <p>
* Format string related processing is handled in this class, see method
* {@link #toString(String)}.
* <p>
* The following is the mapping of Progress language features to the
* corresponding feature in this class:
* <p>
* <pre>
* not operator {@link #not} and {@link #_not}
* and operator {@link #and} and {@link #_and}
* or operator {@link #or} and {@link #_or}
* = or EQ operator {@link CompareOps#isEqual}
* <> or NE operator {@link CompareOps#isNotEqual}
* > or GT operator {@link CompareOps#isGreaterThan}
* >= or GE operator {@link CompareOps#isGreaterThanOrEqual}
* < or LT operator {@link CompareOps#isLessThan}
* <= or LE operator {@link CompareOps#isLessThanOrEqual}
* maximum function {@link CompareOps#maximum(BaseDataType[])}
* minimum function {@link CompareOps#minimum(BaseDataType[])}
* </pre>
* <p>
* <pre>
* OR Operator Truth Table
*
* Left Right Result
* --------- --------- ---------
* true true true
* false true true
* unknown true true
* true false true
* false false false
* unknown false unknown
* true unknown true
* false unknown unknown
* unknown unknown unknown
* </pre>
* <p>
* <pre>
* AND Operator Truth Table
*
* Left Right Result
* --------- --------- ---------
* true true true
* false true false
* unknown true unknown
* true false false
* false false false
* unknown false false
* true unknown unknown
* false unknown false
* unknown unknown unknown
* </pre>
* <p>
* <pre>
* NOT Operator Truth Table
*
* Operand Result
* --------- ---------
* true false
* false true
* unknown unknown
* </pre>
* <p>
* There are convenience versions of each operator that directly return
* <code>boolean</code> values such that unwrapping is not needed. These
* are named the same as the operator methods above except there is no
* 'Operator' appended. For example, the not operator is {@link #not}.
*
* @author GES
*/
public class logical
extends BaseDataType
{
/** Singleton and immutable for the logical TRUE value. */
public static final logical TRUE = new logicalConstant(true);
/** Singleton and immutable for the logical FALSE value. */
public static final logical FALSE = new logicalConstant(false);
/** Singleton and immutable for the logical UNKNOWN value. */
public static final logical UNKNOWN = new logicalConstant();
/** The default format string. */
public static final String DEFAULT_FORMAT = "yes/no";
/**
* Stores the state of this logical as <code>true</code> or
* <code>false</code>, independent of the state of whether or not this
* instance represents the <code>unknown value</code>.
*/
private boolean value = false;
/**
* Stores the state of whether or not this instance represents the
* <code>unknown value</code>, independent of the state of this logical's
* value as <code>true</code> or <code>false</code>.
*/
private boolean unknown = false;
/**
* Default constructor, creates an instance that represents the unknown
* value.
*/
public logical()
{
unknown = true;
}
/**
* This is a special c'tor which should be used only when converting the
* value returned by a function or method with polymorphic return type into the
* expected type (i.e. DYNAMIC-FUNCTION()). In such cases, the 4GL does some
* automatic type conversion (see {@link #assign(BaseDataType)}).
*
* @param value
* The value to be used for this instance.
*/
public logical(BaseDataType value)
{
if (value == null || value.isUnknown())
{
setUnknown();
}
else
{
assign(value);
}
}
/**
* This is a special c'tor which should be used only when converting the
* value returned by a function or method with polymorphic return type into the
* expected type (i.e. DYNAMIC-FUNCTION()). In such cases, the 4GL does some
* automatic type conversion (see {@link #assign(BaseDataType)}).
*
* @param value
* The value to be used for this instance.
* @param fmt
* The format string to use.
*/
public logical(BaseDataType value, character fmt)
{
if (value == null || value.isUnknown() || fmt.isUnknown())
{
setUnknown();
}
else if (value instanceof Text)
{
assign(new logical(((Text) value).getValue(), fmt.toStringMessage()));
}
else
{
assign(value);
}
}
/**
* This is a special c'tor which should be used only when converting the
* value returned by a function or method with polymorphic return type into the
* expected type (i.e. DYNAMIC-FUNCTION()). In such cases, the 4GL does some
* automatic type conversion (see {@link #assign(BaseDataType)}).
*
* @param value
* The value to be used for this instance.
* @param fmt
* The format string to use.
*/
public logical(BaseDataType value, String fmt)
{
if (value == null || value.isUnknown() || fmt == null)
{
setUnknown();
}
else if (value instanceof Text)
{
assign(new logical(((Text) value).getValue(), fmt));
}
else
{
assign(value);
}
}
/**
* Constructs an instance that has the <code>boolean</code> value passed
* in and which <b>does not</b> represent the <code>unknown value</code>.
*
* @param value
* The logical value of the resulting instance.
*/
public logical(boolean value)
{
this.value = value;
}
/**
* Constructs an instance that has the <code>boolean</code> value and
* <code>unknown value</code> that exactly matches that of the passed-in
* instance.
*
* @param value
* The logical and unknown value of the resulting instance.
*/
public logical(logical value)
{
assign(value);
}
/**
* Converts the given string into a logical value. A '?' is interpreted as
* the <code>unknown value</code> and the strings 'yes' and 'true' (and
* any abbreviations thereof) are considered <code>true</code> and
* any other input is considered <code>false</code>.
*
* @param value
* The text representation of the logical value.
*/
public logical(String value)
{
this(value, DEFAULT_FORMAT);
}
/**
* Converts the given string into a logical value. A '?' is interpreted as
* the <code>unknown value</code> and the strings 'yes' and 'true' (and
* any abbreviations thereof) are considered <code>true</code> and
* any other input is considered <code>false</code>.
*
* @param value
* The text representation of the logical value.
*/
public logical(character value)
{
this(value, DEFAULT_FORMAT);
}
/**
* Converts the given string into a logical value. A '?' is interpreted as
* the <code>unknown value</code> and the strings 'yes' and 'true' (and
* any abbreviations thereof) are considered <code>true</code> and
* any other input is considered <code>false</code>.
* <p>
* Parses the string using default formats 'yes/no' and 'true/false' and
* the passed in format string. Any any abbreviations of the above formats
* are also considered as a match.
*
* TODO: when a LOGICAL argument is passed as OUTPUT parameter to a procedure the implicit
* conversion will fails without reason. The following message is displayed:
* [** Input value: should be yes/no. (87)]
* When the argument is passes to a INPUT-OUTPUT parameter, everything is fine.
*
* @param value
* The text representation of the logical value.
* @param fmt
* The format string to use.
*/
public logical(character value, String fmt)
{
if (value.isUnknown())
{
setUnknown();
}
else
{
assign(new logical(value.getValue(), fmt));
}
}
/**
* Converts the given string into a logical value. A '?' is interpreted as
* the <code>unknown value</code> and the strings 'yes' and 'true' (and
* any abbreviations thereof) are considered <code>true</code> and
* any other input is considered <code>false</code>.
* <p>
* Parses the string using default formats 'yes/no' and 'true/false' and
* the passed in format string. Any any abbreviations of the above formats
* are also considered as a match.
*
* TODO: when a LOGICAL argument is passed as OUTPUT parameter to a procedure the implicit
* conversion will fails without reason. The following message is displayed:
* [** Input value: should be yes/no. (87)]
* When the argument is passes to a INPUT-OUTPUT parameter, everything is fine.
*
* @param value
* The text representation of the logical value.
* @param fmt
* The format string to use.
*/
public logical(character value, character fmt)
{
if (value.isUnknown() || fmt.isUnknown())
{
setUnknown();
}
else
{
assign(new logical(value.getValue(), fmt.toStringMessage()));
}
}
/**
* Converts the given string into a logical value. A '?' is interpreted as
* the <code>unknown value</code>. Parses the string using default
* formats 'yes/no' and 'true/false' and the passed in format string. Any
* any abbreviations of the above formats are also considered as a match.
*
* @param value
* The text representation of the logical value.
* @param fmt
* The format string to use.
*
* @throws ErrorConditionException
* If no match is found.
*/
public logical(String value, String fmt)
throws ErrorConditionException
{
if (value == null || value.equals("?"))
{
setUnknown();
}
else
{
Boolean res = fromString(value, fmt);
if (res == null)
{
String errmsg = "Input value: " + value + " should be " + fmt;
ErrorManager.recordOrShowError(87, errmsg, true, true, false, false);
setUnknown();
}
else
{
this.value = res;
}
}
}
/**
* Constructs an instance after copying the parameter's data into the
* internal representation of this class.
* <p>
* If the parameter is <code>null</code>, this instance will represent
* the <code>unknown value</code>.
*
* @param value
* The value to be used for this instance.
*/
public logical(Boolean value)
{
if (value == null)
{
setUnknown();
}
else
{
this.value = value;
}
}
/**
* Resolve the given boolean value to a logical TRUE/FALSE singleton.
* <p>
* WARNING: this must be used only from within the fwd runtime or as an instance returned to the converted
* code, to be assigned to a mutable instance. The return instance is immutable!
*
* @param value
* The value to resolve
*
* @return See above.
*/
public static logical of(boolean value)
{
return value ? logical.TRUE : logical.FALSE;
}
/**
* Check if the given value matches the received format. This method is called at runtime
* (from this class' constructor) or at conversion time (used for "boolean manhandling").
* <p>
* The default formats of "yes/no" and "true/false" are always considered and will match
* in preference to an explicitly provided format string. This means that any conflict with
* the explicitly provided format string will match one of the default formats first (in
* preference).
* <p>
* Matching is done on a case-insensitive basis.
* <p>
* Interestingly, any matching subset of valid match text will match. This means that an
* exact match or an unambiguous abbreviated match to a format string of the form 'XXX/YYY'
* will make x, X, xx, XX, xxx, XXX all resolve to <code>true</code> and y, Y, yy, YY, yyy,
* YYY all resolve to <code>false</code>. This optional abbreviation support works for
* matches to both the default formats AND to explicitly provided custom formats.
* <p>
* The default formats have an additional "trash matching" behavior. When specifying yes, no,
* true or false, the string can contain any trash so long as the trash is following a space
* character. This means a <code>yes . bogus text.</code> matches a <code>yes</code> in a
* default format. This same ridiculous trick does not work for explicit custom formats.
* <p>
* All of the above behavior is indeed how the 4GL does it. No, it doesn't make any sense.
* <p>
* At conversion time, all enclosing quote characters should already have been removed from
* both input parameters. It is expected that this is always already the case with runtime
* strings.
*
* @param val
* The value to be checked.
* @param fmt
* The format; if <code>null</code>, the {@link #DEFAULT_FORMAT} will be used.
*
* @return <code>null</code> if the value does not match the format. Otherwise, the value's
* <code>true</code> or <code>false</code> state.
*/
public static Boolean fromString(String val, String fmt)
{
// the val can be empty if the custom format case considers this option as valid boolean
if (val == null || (val.isEmpty() && (fmt == null || (!fmt.startsWith("/") && !fmt.endsWith("/")))))
{
return null;
}
List<String> list = new ArrayList<>();
// custom format is checked first (might be invalid, #errno 74)
if (fmt != null)
{
fmt = fmt.toLowerCase();
list.add(fmt);
}
if (fmt == null || !fmt.equals(DEFAULT_FORMAT))
{
list.add(DEFAULT_FORMAT);
}
if (fmt == null || !fmt.equals("true/false"))
{
list.add("true/false");
}
String[] fmts = list.toArray(new String[0]);
String target = val.toLowerCase();
for (int i = 0; i < fmts.length; i++)
{
String current = fmts[i];
String[] values = splitFormat(current);
String yesVal = values[0];
String noVal = values[1];
// check 1: exact or unambiguous subset match
if (target.isEmpty())
{
// in custom format case the target can be empty
// for this case one of the custom option should be empty too
if (yesVal.isEmpty())
{
return true;
}
else if (noVal.isEmpty())
{
return false;
}
}
else
{
if (yesVal.startsWith(target))
{
return true;
}
else if (noVal.startsWith(target))
{
return false;
}
}
// default formats only, try trash matching mode
if (fmt == null || fmts.length == 2 || i > 0)
{
// check 2: if not explicit format, first token in target (using space as separator)
// must be a yesVal or noVal. any other tokens after this one are discarded/ignored
if (target.startsWith(yesVal + " "))
{
return true;
}
else if (target.startsWith(noVal + " "))
{
return false;
}
}
}
return null;
}
/**
* Get the type
* @return type
*/
public Type getType()
{
return Type.LOGICAL;
}
/**
* Returns the largest instance (<code>false</code> is smaller than
* <code>true</code>) from the list. This list may not include
* <code>null</code> values.
*
* @param list
* The list of logicals to check.
*
* @return The largest instance or <code>unknown value</code> if any
* entries in the list are unknown or <code>null</code> if
* an empty list is passed.
*/
public static logical maximum(logical ... list)
{
return (logical) CompareOps.maximum(list);
}
/**
* Returns the smallest instance (<code>false</code> is smaller than
* <code>true</code>) from the list. This list may not include
* <code>null</code> values.
*
* @param list
* The list of decimals to check.
*
* @return The smallest instance or <code>unknown value</code> if any
* entries in the list are unknown or <code>null</code> if
* an empty list is passed.
*/
public static logical minimum(logical ... list)
{
return (logical) CompareOps.minimum(list);
}
/**
* Implements the <code>NOT</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
*
* @param op
* The operand.
*
* @return The result of the operator.
*/
public static boolean _not(logical op)
{
return op.unknown ? false : !op.value;
}
/**
* Implements the <code>NOT</code> logical operator using the Java logical
* NOT.
*
* @param op
* The operand.
*
* @return The result of the operator.
*/
public static logical not(boolean op)
{
return logical.of(!op);
}
/**
* Implements the <code>NOT</code> logical operator with proper processing
* of the <code>unknown value</code>. In particular, if the operand
* is unknown, then <code>unknown value</code> is returned.
* <p>
* If the operand is not <code>unknown</code>, the boolean value will
* be negated and returned using the Java logical NOT.
*
* @param op
* The operand.
*
* @return The result of the operator.
*/
public static logical not(logical op)
{
if (op.unknown)
return (logical) logical.UNKNOWN;
return logical.of(!(op.value));
}
/**
* Implements the <code>AND</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _and(boolean op1, boolean op2)
{
return op1 && op2;
}
/**
* Implements the <code>AND</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _and(logical op1, boolean op2)
{
return op2 && !op1.unknown && op1.value;
}
/**
* Implements the <code>AND</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _and(boolean op1, logical op2)
{
return op1 && !op2.unknown && op2.value;
}
/**
* Implements the <code>AND</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _and(logical op1, logical op2)
{
return !op1.unknown && !op2.unknown && op1.value && op2.value;
}
/**
* Implements the <code>AND</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* This form of the method allows the original semantic of the operator
* to be maintained. In particular, the expression defining the right
* operand will not be evaluated unless the left operand does not fail
* the logical test.
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _and(boolean op1, LogicalOp op2)
{
return op1 && _and(op1, op2.evaluate());
}
/**
* Implements the <code>AND</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* This form of the method allows the original semantic of the operator
* to be maintained. In particular, the expression defining the right
* operand will not be evaluated unless the left operand does not fail
* the logical test.
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _and(logical op1, LogicalOp op2)
{
return !op1.unknown && op1.value && _and(op1, op2.evaluate());
}
/**
* Implements the <code>AND</code> logical operator (just a convenience
* method for the Java logical AND).
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical and(boolean op1, boolean op2)
{
return logical.of(op1 && op2);
}
/**
* Implements the <code>AND</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>true</code> and
* <code>false</code> is returned if the other operand is
* <code>false</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical AND.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical and(boolean op1, logical op2)
{
return and(logical.of(op1), op2);
}
/**
* Implements the <code>AND</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>true</code> and
* <code>false</code> is returned if the other operand is
* <code>false</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical AND.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical and(logical op1, boolean op2)
{
return and(op1, logical.of(op2));
}
/**
* Implements the <code>AND</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>true</code> and
* <code>false</code> is returned if the other operand is
* <code>false</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical AND.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical and(logical op1, logical op2)
{
if (op1.unknown && op2.unknown)
{
return logical.UNKNOWN;
}
if (op1.unknown && !op2.unknown)
{
return op2.value ? logical.UNKNOWN : logical.of(false);
}
if (!op1.unknown && op2.unknown)
{
return op1.value ? logical.UNKNOWN : logical.of(false);
}
return logical.of(op1.value && op2.value);
}
/**
* Implements the <code>AND</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>true</code> and
* <code>false</code> is returned if the other operand is
* <code>false</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical AND.
* <p>
* This form of the method allows the original semantic of the operator
* to be maintained. In particular, the expression defining the right
* operand will not be evaluated unless the left operand does not fail
* the logical test.
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical and(boolean op1, LogicalOp op2)
{
// ensure that the right operand will not be evaluated unless the
// left operand is true
if (!op1)
{
// if the left operand is false, it doesn't matter if the right
// operand is unknown, we always return false
return logical.of(false);
}
return and(op1, op2.evaluate());
}
/**
* Implements the <code>AND</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>true</code> and
* <code>false</code> is returned if the other operand is
* <code>false</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical AND.
* <p>
* This form of the method allows the original semantic of the operator
* to be maintained. In particular, the expression defining the right
* operand will not be evaluated unless the left operand does not fail
* the logical test.
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical and(logical op1, LogicalOp op2)
{
// ensure that the right operand will not be evaluated unless the
// left operand is true or UNKNOWN
if (!op1.isUnknown() && !op1.value)
{
// if the left operand is false, it doesn't matter if the right
// operand is unknown, we always return false
return logical.of(false);
}
return and(op1, op2.evaluate());
}
/**
* Implements the <code>OR</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _or(boolean op1, boolean op2)
{
return op1 || op2;
}
/**
* Implements the <code>OR</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _or(logical op1, boolean op2)
{
return op2 || (!op1.unknown && op1.value);
}
/**
* Implements the <code>OR</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _or(boolean op1, logical op2)
{
return op1 || (!op2.unknown && op2.value);
}
/**
* Implements the <code>OR</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _or(logical op1, logical op2)
{
return (!op1.unknown && op1.value) || (!op2.unknown && op2.value);
}
/**
* Implements the <code>OR</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* This form of the method allows the original semantic of the operator
* to be maintained. In particular, the expression defining the right
* operand will not be evaluated unless the left operand does not fail
* the logical test.
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _or(boolean op1, LogicalOp op2)
{
return op1 || _or(op1, op2.evaluate());
}
/**
* Implements the <code>OR</code> logical operator with the convenience
* of converting the result to a primitive <code>boolean</code>. If the
* result is the <code>unknown value</code>, this will be converted to a
* <code>false</code>.
* <p>
* This form of the method allows the original semantic of the operator
* to be maintained. In particular, the expression defining the right
* operand will not be evaluated unless the left operand does not fail
* the logical test.
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static boolean _or(logical op1, LogicalOp op2)
{
return (!op1.unknown && op1.value) || _or(op1, op2.evaluate());
}
/**
* Implements the <code>OR</code> logical operator using the Java logical
* OR.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical or(boolean op1, boolean op2)
{
return logical.of(op1 || op2);
}
/**
* Implements the <code>OR</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>false</code> and
* <code>true</code> is returned if the other operand is
* <code>true</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical OR.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical or(boolean op1, logical op2)
{
return or(logical.of(op1), op2);
}
/**
* Implements the <code>OR</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>false</code> and
* <code>true</code> is returned if the other operand is
* <code>true</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical OR.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical or(logical op1, boolean op2)
{
return or(op1, logical.of(op2));
}
/**
* Implements the <code>OR</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>false</code> and
* <code>true</code> is returned if the other operand is
* <code>true</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical OR.
* <p>
* <b>Warning: this form requires that both operand expressions are
* evaluated in the context of the caller. It does not maintain the
* standard semantic of a logical operator where the right operand is not
* evaluated unless the left operand requires it.</b>
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical or(logical op1, logical op2)
{
if (op1.unknown && op2.unknown)
{
return logical.UNKNOWN;
}
if (op1.unknown && !op2.unknown)
{
return op2.value ? logical.of(true) : logical.UNKNOWN;
}
if (!op1.unknown && op2.unknown)
{
return op1.value ? logical.of(true) : logical.UNKNOWN;
}
return logical.of(op1.value || op2.value);
}
/**
* Implements the <code>OR</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>false</code> and
* <code>true</code> is returned if the other operand is
* <code>true</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical OR.
* <p>
* This form of the method allows the original semantic of the operator
* to be maintained. In particular, the expression defining the right
* operand will not be evaluated unless the left operand does not fail
* the logical test.
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical or(boolean op1, LogicalOp op2)
{
// ensure that the right operand will not be evaluated unless the
// left operand is false or unknown
if (op1)
{
return logical.of(true);
}
// unknown value processing is too complex to inline here, use the
// common method even if it is a bit slower
return or(logical.of(op1), op2.evaluate());
}
/**
* Implements the <code>OR</code> logical operator with proper processing
* of the <code>unknown value</code>.
* <p>
* If one operand is <code>unknown</code>, then <code>unknown</code>
* is returned if the other operand is <code>false</code> and
* <code>true</code> is returned if the other operand is
* <code>true</code>. If both operands are <code>unknown</code>, then
* <code>unknown</code> is returned.
* <p>
* If no operand is <code>unknown</code>, the boolean values of each
* logical are tested using the Java logical OR.
* <p>
* This form of the method allows the original semantic of the operator
* to be maintained. In particular, the expression defining the right
* operand will not be evaluated unless the left operand does not fail
* the logical test.
*
* @param op1
* The left operand.
* @param op2
* The right operand.
*
* @return The result of the operator.
*/
public static logical or(logical op1, LogicalOp op2)
{
// ensure that the right operand will not be evaluated unless the
// left operand is false or unknown
if (!op1.isUnknown() && op1.value)
{
return logical.of(true);
}
return or(op1, op2.evaluate());
}
/**
* Split a valid logical format string in a safe way that handles all
* possible edge cases. Examples:
* <p>
* <ul>
* <li> an empty or <code>null</code> format string is invalid
* <li> a format that starts with '/' has an empty string for the
* <code>true</code> value
* <li> a format that ends with '/' or has no '/' has an empty string
* for the <code>false</code>
* <li> otherwise the format string is split into 2 pieces at the '/'
* character and the left side is used as the <code>true</code>
* representation and the right side is the <code>false</code>
* </ul>
*
* @param fmt
* Format to split.
*
* @return The split format string as a string array with 2 elements,
* where the first element is the <code>true</code> value
* and the second element is the <code>false</code> value.
*
* Return {@code null} if the provided format is invalid, and error was not thrown.
*
* @throws ErrorConditionException
* if the provided format is invalid (null, empty or a single slash character).
* The error code is 74.
*/
public static String[] splitFormat(String fmt)
throws ErrorConditionException
{
// interestingly, only the empty string or the "/" are problems
if (fmt == null || fmt.length() == 0 || fmt.equals("/"))
{
ErrorManager.recordOrThrowError(74,
String.format("Value %s cannot be displayed using %s", "", fmt));
return null;
}
// usually, format strings must have 2 non-empty parts separated by a '/' so let's look for that char
final int split = fmt.indexOf('/');
if (split < 0)
{
// edge case where there is no '/' at all, use the whole ftm as the true value
return new String[] { fmt, "" };
}
// It is an error in OE if format has more than one slash character.
final int secondSlashIndex = fmt.indexOf('/', split + 1);
if (secondSlashIndex > 0)
{
ErrorManager.genInvalidCharError(secondSlashIndex, fmt);
return null;
}
return new String[] { fmt.substring(0, split), fmt.substring(split + 1) };
}
/**
* Determines the largest possible string length that can match either of
* the specified values in a valid format string. For example, if the
* format string was specified as "Yep/Nope" then the largest size would
* be returned as 4. The format string will be split and the length of
* the larger of the 2 values will be returned.
*
* @param fmt
* A valid format string for the <code>logical</code> type.
*
* @return The length of the longer of the 2 values in the format
* string.
*/
public static int maximumStringSize(String fmt)
{
if (fmt == null)
{
fmt = "";
}
String[] result = splitFormat(fmt);
if (result == null)
{
result = new String[] { "", "" };
}
return Math.max(result[0].length(), result[1].length());
}
/**
* Calculate the length of a formatted value (the text form of the value)
* using this format string.
*
* @param fmt
* The format string.
*
* @return The length in characters of the any resulting formatted
* value.
*/
public int formatLength(String fmt)
{
if (fmt == null)
{
fmt = defaultFormatString();
}
return maximumStringSize(fmt);
}
/**
* An equality test which handles the most common cases with the least
* amount of overhead. The superclass implementation is invoked otherwise.
* <p>
* This implementation is consistent with {@link #hashCode()}.
*
* @see com.goldencode.p2j.util.BaseDataType#equals(java.lang.Object)
*/
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o instanceof logical)
{
logical that = (logical) o;
boolean u1 = this.unknown;
boolean u2 = that.unknown;
if (u1 && u2)
{
return true;
}
if (u1 || u2)
{
return false;
}
return (this.value == that.value);
}
return super.equals(o);
}
/**
* Hash code implementation which is consistent with {@link
* BaseDataType#equals}.
*
* @return Hash code value for this object instance.
*/
public int hashCode()
{
int result = 17;
if (isUnknown())
{
return result;
}
result = 37 * result + (value ? 0 : 1);
return result;
}
/**
* Creates a new instance of the same type that represents the
* <code>unknown value</code>.
*
* @return An instance that represents the <code>unknown value</code>.
*/
public BaseDataType instantiateUnknown()
{
return new logical();
}
/**
* Creates a new instance of the same type that represents the
* default initialized value.
*
* @return An instance that represents the default value.
*/
public BaseDataType instantiateDefault()
{
return new logical(false);
}
/**
* Does the same as standard <code>clone()</code> method but returns an
* instance of <code>BaseDataType</code> and doesn't throw the
* <code>CloneNotSupportedException</code>.
*
* @return A clone of this instance.
*/
public logical duplicate()
{
return new logical(this);
}
/**
* Accesses the state of this instance's <code>boolean</code> value. If
* this instance represents the <code>unknown value</code>, then this
* method will return <code>false</code> even if the internal state is
* <code>true</code>!
*
* @return The state of this instance's <code>boolean</code> value or
* <code>false</code> if this instance represents the
* <code>unknown value</code>.
*/
public boolean booleanValue()
{
if (unknown)
return false;
return value;
}
/**
* Determines if this instance represents the <code>unknown value</code>.
*
* @return <code>true</code> if this instance is set to the
* <code>unknown value</code>.
*/
public boolean isUnknown()
{
return unknown;
}
/**
* Sets the state of this instance's <code>unknown value</code> flag to
* <code>true</code>.
* <p>
* <b>Warning: a separate call is needed to ensure that the data of this
* instance is set to the correct value.</b>
*/
public void setUnknown()
{
if (!unknown)
{
checkUndoable(true);
}
unknown = true;
}
/**
* Accesses the state of this instance's <code>boolean</code> value
* independently of the state of whether this instance represents the
* <code>unknown value</code>. If this instance represents the
* <code>unknown value</code>, the returned <code>boolean</code> is
* <b>not valid</b>!
*
* @return The state of this instance's <code>boolean</code> value.
*/
public boolean getValue()
{
return value;
}
/**
* Sets the state of this instance's <code>boolean</code> value
* independently of the state of whether this instance represents the
* <code>unknown value</code>. This also sets the
* <code>unknown value</code> state for this instance to
* <code>false</code>.
*
* @param value
* The state to set this instance's <code>boolean</code> value.
*/
public void setValue(boolean value)
{
if (unknown || value != this.value)
{
checkUndoable(true);
}
this.value = value;
unknown = false;
}
/**
* Returns {@code true} if dynamic data type conversion should not be attempted, it can also throw an
* {@code ErrorConditionException} in case of incompatible types for a POLY conversion.
* (e.g. DYNAMIC-FUNCTION()).
*
* @throws ErrorConditionException
* If dynamic conversion of input value is not supported.
*/
@Override
protected boolean isIncompatibleTypesOnConversion(BaseDataType value)
{
// invalid conversion is being thrown even if unknown
if (value instanceof longchar /*|| value instanceof clob*/ || value instanceof blob)
{
incompatibleTypesOnConversion();
}
return super.isIncompatibleTypesOnConversion(value);
}
/**
* Sets the state (data and unknown value) of this instance based on the
* state of the passed instance.
* <p>
* If the value is not of type <code>logical</code>, the following automatic type
* conversions will occur:
* <p>
* <ol>
* <li> if the value is a logical value, the current value is returned</li>
* <li> if the value is unknown (including unknown literal), sets the instance to
* unknown</li>
* <li> if the value is a non-unknown rowid or BinaryData (raw or memptr) value the string
* representation is converted to a logical value</li>
* <li> if the value is a non-unknown numeric value, returns a logical value set to true
* when the numeric value is not zero, false if the numeric value is 0</li>
* <li> if the value is a non-unknown character or longchar value, it attempts to convert
* the character value to a logical value (if the character is "yes", it is set to
* true, if is "no", it is set to false</li>
* <li> if the value is a non-unknown date or datetime it returns a logical false for a
* value of 31/12/-4714 and true for other values. Basically the number of days from
* 1/1/4713 B.C. to that date are converted to logical value. If the number of days
* are 0 a false value is returned otherwise a true value is returned.</li>
* <li> if the value is a non-unknown handle or comhandle value, it returns a logical
* false if the resource id is 0 and true if the resource id is not 0.</li>
* <li> else, it attempts to convert the value's string representation to logical</li>
* </ol>
* <p>
* This variant is meant to handle the cases of built-in functions and methods in the 4GL
* which have polymorphic return types (e.g. DYNAMIC-FUNCTION()). This should NOT be used for
* non-polymorphic assignments.
*
* @param value
* The instance from which to copy state.
*/
public void assign(BaseDataType value)
{
if (value == null)
{
setUnknown();
return;
}
// the value may be a proxy; make sure to unwrap and work with the real value
value = value.val();
if (value.isUnknown())
{
setUnknown();
}
else if (value instanceof logical)
{
assign((logical) value);
}
else if (value instanceof jobject)
{
assign((jobject<?>) value);
}
else
{
// this path is only used when type morphing is needed
if (value instanceof date && !(value instanceof datetime))
{
// date values only
long day = ((date) value).longValue();
assign(day != 0);
}
else if (value instanceof handle)
{
Long id = ((handle) value).getResourceId();
if (id != null)
{
assign(id != 0);
}
else
{
setUnknown();
}
}
else if (value instanceof NumberType)
{
// decimal, integer, int64, recid
BigDecimal decimalValue = ((NumberType) value).toBigDecimal();
if (decimalValue == null)
{
setUnknown();
}
else
{
assign(decimalValue.signum() != 0);
}
}
else if (value instanceof Text)
{
// yes/no character or longchar values can be converted
assign(new logical(value.toStringMessage()));
}
else if (value instanceof raw)
{
setUnknown();
}
else
{
incompatibleTypesOnConversion();
}
}
}
/**
* Sets the state (data and unknown value) of this instance based on the
* state of the passed instance.
*
* @param value
* The instance from which to copy state.
*/
public void assign(logical value)
{
// the value may be a proxy; make sure to unwrap and work with the real value
if (BaseDataType.isProxy(value))
{
assign(value.val());
return;
}
checkUndoable(value);
this.value = value.value;
unknown = value.unknown;
}
/**
* Sets the state (data) of this instance based on the given value.
*
* @param value
* The data from which to copy state.
*/
public void assign(boolean value)
{
if (unknown || this.value != value)
{
checkUndoable(true);
}
this.value = value;
unknown = false;
}
/**
* Sets the state (data and unknown value) of this instance based on the
* state of the passed instance.
*
* @param value
* The instance from which to copy state.
*/
public void assign(Undoable value)
{
assign((logical) value);
}
/**
* Compares this instance with the specified instance and returns a -1
* if this instance is less than the specified, 0 if the two instances
* are equal and 1 if this instance is greater than the specified
* instance. This is the implementation of the <code>Comparable</code>
* interface.
* <p>
* The algorithm will fail to give meaningful results in the case where
* one tries to sort against other objects that do not represent compatible
* values.
*
* @param obj
* The instance to compare against.
*
* @return -1, 0 or 1 depending on if this instance is less than,
* equal to or greater (respectively) than the specified
* instance <code>obj</code>.
*/
public int compareTo(Object obj)
{
logical l = null;
if (obj instanceof logical)
{
l = (logical) obj;
}
else if (obj instanceof BaseDataType)
{
BaseDataType bdt = (BaseDataType) obj;
if (isUnknown() && bdt.isUnknown())
{
return 0;
}
else if (isUnknown())
{
return -1;
}
else if (bdt.isUnknown())
{
return 1;
}
else
{
l = new logical(bdt);
// An unknown value should be always converted to a unknown logical value.
// A non-unknown value should be always converted to a non-unknown logical value.
// If an unknown value is converted to a non-unknown value the conversion fails.
// If a non-unknown value is converted to an unknown value the conversion fails.
if (l.isUnknown() != bdt.isUnknown())
{
incompatibleTypesOnConversion();
}
}
}
else if (obj instanceof Boolean)
{
l = logical.of(((Boolean) obj).booleanValue());
}
else if (obj instanceof String)
{
l = convertString((String)obj);
// if an error occurred, we detect it here and return
if (l == null)
{
return -1;
}
}
else
{
return -1;
}
int result = 0;
if (!unknown && !l.unknown)
{
if (value == l.value)
{
result = 0;
}
else if (!value && l.value)
{
result = -1;
}
else
{
result = 1;
}
}
else
{
if (unknown == l.unknown)
{
result = 0;
}
else if (unknown)
{
result = -1;
}
else
{
result = 1;
}
}
return result;
}
/**
* Returns the larger (<code>false</code> is smaller than
* <code>true</code>) of the current or passed-in instance.
*
* @param l
* The instance to compare against.
*
* @return The largest instance.
*/
public logical maximum(logical l)
{
return (logical) super.maximum(l);
}
/**
* Returns the smaller (<code>false</code> is smaller than
* <code>true</code>) of the current or passed-in instance.
*
* @param l
* The instance to compare against.
*
* @return The smallest instance.
*/
public logical minimum(logical l)
{
return (logical) super.minimum(l);
}
/**
* Return the current value as a Java wrapper.
*
* @return Current value or {@code null} if unknown.
*/
public Boolean toJavaType()
{
return isUnknown() ? null : Boolean.valueOf(value);
}
/**
* Creates a string representation of the instance data using the default Progress 4GL format of
* {@code 'yes/no'}. If the instance represents the {@code unknown value}, a {@code '?'} will be returned.
*
* @return A short string representation of the object.
*/
public String toString()
{
return toString(null);
}
/**
* Creates a string representation of the instance data in a form that is
* compatible with the <code>MESSAGE</code> language statement. If the
* instance represents the <code>unknown value</code>, a '?' will be
* returned.
*
* @return The 'message' formatted string.
*/
public String toStringMessage()
{
return toString(null);
}
/**
* Creates a string representation of the instance data using the 'export'
* format. For the logical data type this is the same as the default
* format string of 'yes/no'. If the instance represents the
* <code>unknown value</code>, a '?' will be returned.
*
* @return The 'export' formatted string.
*/
public String toStringExport()
{
return toString(null);
}
/**
* Creates a string representation of the instance data using the user-
* specified Progress 4GL format string. If the passed-in format is
* <code>null</code>, then a default format string of 'yes/no' will be
* used. Please note that due to the implementation, it is faster
* to use a <code>null</code> format string than to explicitly encode
* 'yes/no'. If the instance represents the <code>unknown value</code>,
* a '?' will be returned.
* <p>
* The format string must be in the form of 'xxx/yyy' where xxx is the
* text for a <code>true</code> and yyy is the text for a
* <code>false</code> value. For example, one might want a 'true/false'
* to be used instead of the default. The user cannot override the '?'
* returned for the <code>unknown value</code>.
*
* @param fmt
* The Progress 4GL format string for a logical data type.
*
* @return The formatted string.
*/
public String toString(String fmt)
throws ErrorConditionException
{
// quick-out when unknown (no user override for that case)
if (unknown)
{
return "?";
}
// implement 'default' behavior (as if the format string was "yes/no")
if (fmt == null)
{
return value ? "yes" : "no";
}
String[] result = splitFormat(fmt);
if (result == null)
{
return "";
}
StringBuilder res = new StringBuilder(value ? result[0] : result[1]);
int fmtLen = formatLength(fmt);
while(res.length() < fmtLen)
{
res.append(' ');
}
// normal case
return res.toString();
}
/**
* Return the default display format string for this type.
*
* @return The default format string.
*/
public String defaultFormatString()
{
return DEFAULT_FORMAT;
}
/**
* Obtain the length (in bytes) of this BDT will use when serialized.
*
* @return the length of this BDT will use when serialized.
*/
@Override
public int getSize()
{
return (isUnknown() || !value) ? 1 : 2;
}
/**
* Replacement for the default object reading method. The latest
* state is read from the input source.
*
* @param in
* The input source from which fields will be restored.
*
* @throws IOException
* In case of I/O errors.
* @throws ClassNotFoundException
* If payload can't be instantiated.
*/
public void readExternal(ObjectInput in)
throws IOException,
ClassNotFoundException
{
value = in.readBoolean();
unknown = in.readBoolean();
}
/**
* Replacement for the default object writing method. The latest
* state is written to the output destination.
*
* @param out
* The output destination to which fields will be saved.
*
* @throws IOException
* In case of I/O errors.
*/
public void writeExternal(ObjectOutput out)
throws IOException
{
out.writeBoolean(value);
out.writeBoolean(unknown);
}
/**
* Get the default initialization for an extent variable of this type.
*
* @return See above.
*/
@Override
protected BaseDataType instantiateDefaultExtent()
{
return logical.of(false);
}
/**
* Helper to convert a text value into a logical value with ERROR
* processing SUPPRESSED. The only known instance where we use this is
* in a validation expression, so the suppression of error processing
* should be OK when it normally might be dangerous.
*
* @param val
* The value to attempt to convert. If it does not match the
* string "yes" or "no", then an error would normally occur.
* This error will be suppressed, but the resulting failure
* will be detected.
*
* @return The converted instance or <code>null</code> on any failure.
*/
private logical convertString(String val)
{
logical[] l = new logical[1];
try
{
ErrorManager.ignore(() -> l[0] = new logical(val));
}
catch (ErrorConditionException ece)
{
// should not happen but we put this here for safety
return null;
}
// if an error silently occurred inside the logical constructor, we
// detect it here and return
if (l[0] != null && l[0].isUnknown())
{
l[0] = null;
}
return l[0];
}
/**
* An immutable logical type, to be used for TRUE/FALSE/UNKNOWN singletons required within FWD runtime.
* <p>
* Instances of this type are serializable.
*/
public static class logicalConstant
extends logical
implements BaseDataTypeConstant
{
/**
* Default constructor, creates an instance that represents the unknown value.
*/
public logicalConstant()
{
// unknown
}
/**
* Constructs an instance that has the <code>boolean</code> value passed in and which <b>does not</b>
* represent the <code>unknown value</code>.
*
* @param value
* The logical value of the resulting instance.
*/
public logicalConstant(boolean value)
{
super(value);
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
public void setUnknown()
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
public void setValue(boolean value)
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
public void assign(BaseDataType value)
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
public void assign(boolean value)
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
public void assign(jobject<?> value)
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
public void assign(logical value)
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
public void assign(Object value)
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
public void assign(Undoable value)
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable logical instance.
*/
@Override
Supplier<AbstractParameter> getAssigner()
{
throw new UnsupportedOperationException("This is an immutable logical instance.");
}
}
}