Text.java
/*
** Module : Text.java
** Abstract : Base class for P2J's longchar and character implementations.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description---------------------------------
** 001 CA 20130130 Created initial version by extracting the internals from the character
** class.
** 002 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 finished.
** 003 CA 20131013 Added no-op deleted() method, required by the changes in Finalizable interface.
** 004 ECF 20140417 Implemented quick fix for missing _POLY support.
** 005 OM 20141014 Improved _POLY support for assign(Undoable).
** 006 GES 20141003 Fixed embedded null character behavior. Construction of a new instance or
** assignment from strings that have embedded nulls is disallowed. Construction
** or assignment from existing instances that have null characters is allowed
** if the subclass allows it. A special setter is provided for explicit use
** in forcing assignment of string data that has embedded nulls. That is the
** only way null data can get into an instance. Some helper methods were moved
** here from a subclass.
** 007 ECF 20150715 Replace StringBuffer with StringBuilder.
** 008 OM 20150924 Added missing replace() overloads.
** 009 ECF 20151111 Performance fix: in assign(Text, boolean), defer expensive null character scan
** until after we determine whether embedded nulls are allowed; common case
** allows them.
** 010 ECF 20160227 Fixed heading numbers.
** 011 ECF 20160430 Optimized compareTo and equalsCaseInsensitively to avoid costly string
** comparisons if the internal String objects are the same instance.
** 012 OM 20160603 Fixed rtrimming to space character only.
** 013 GES 20160617 Fixed unknown literal processing in assign() for processing polymorphic
** return values.
** 014 ECF 20160614 Implemented faster copy constructor.
** 015 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.
** 016 SBI 20160915 Fixed an extra delimiter that was added by replaceEntry.
** 017 IAS 20160919 Serialization optimization.
** 018 SBI 20160922 Fixed NPE if ENTRY is used for an unknown value.
** 019 CA 20161004 Fixed direct comparison with unknown values.
** 020 EVL 20171108 Changed read and write approach for String value from using (read|write)UTF()
** to (read|write)Object() to properly handle Windows Unicode strings.
** 021 GES 20171206 Removed explicit bypass on pending error (new silent error mode).
** 022 CA 20190508 Added support for CASE-SENSITIVE extent var option.
** 023 SVL 20190614 Added assign(clob) to prevent abend during dynamic tables compilation.
** 024 CA 20190927 Added support for direct Java access from 4GL code.
** 025 CA 20191203 Clear the value when setting to unknown.
** 026 CA 20200506 Fixed runtime for character vs longchar OO method calls.
** 027 ME 20200528 Add assign for "legacy" object - toLegacyString.
** 028 IAS 20200908 Rework (de)serialization.
** 029 EVL 20200922 Replace Java split() with our implementation for TextOps.splitInt().
** VVT 20201001 javaTruncateNull() optimized.
** ME 20210504 Added isIncompatibleTypesOnConversion override for invalid POLY conversion.
** CA 20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
** initialized at the method's execution, and not at the caller's arguments.
** AL2 20220319 Added value proxy check in assign.
** ME 20220404 Right trim string for hashCode value.
** 030 HC 20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
** for performance.
** 031 EVL 20230317 Adding method to get internal Text object length. In some conditions for longchar this
** can be different than simple length of the backend String variable.
** 032 CA 20240324 Short-circuit compareTo when one or both the operands are empty.
** 033 CA 20240423 Optimized 'replaceEntry' to create a minimal number of Strings.
** 034 SB 20240530 Use the string value of a BinaryData instance for assignment, therefore accounting
** for charset. Refs #8783.
** 035 CA 20241030 Avoid 'Text.javaSpacifyNull' if the string is known to already have been processed.
** 036 AS 20241205 Introduced valueEquals method.
** 037 FER 20250139 Modified compareTo() method to return '-1' if this value is null regardless of the type
** of the obj to be compared with. Otherwise it would lead to NPE.
*/
/*
** 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 static com.goldencode.util.NativeTypeSerializer.*;
import java.io.*;
import java.nio.charset.Charset;
import com.goldencode.util.*;
/**
* Base class for the {@link character} and {@link longchar} data types.
*/
public abstract class Text
extends BaseDataType
{
/** Charset name to use for encoding or <code>null</code> for default. */
protected static final String override = Utils.getCharsetOverride();
/**
* Stores the state of this character as an instance of class
* <code>String</code>. This is independent of the state of whether or
* not this instance represents the <code>unknown value</code>.
*/
protected String value = null;
/**
* Stores the state of whether or not this instance represents the
* <code>unknown value</code>, independent of the state of this
* character's data.
*/
protected boolean unknown = false;
/**
* Stores the state of whether or not this instance compares case
* sensitively.
*/
protected boolean caseSens = false;
/** Flag indicating if {@link #javaSpacifyNull(String)} should execute. */
protected boolean javaSpacifyNull = true;
/**
* Default constructor, creates an instance that represents the unknown
* value.
*/
public Text()
{
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 Text(BaseDataType value)
{
// the value may be a proxy; make sure to unwrap and work with the real value
value = value == null ? null : value.val();
if (value == null || value.isUnknown())
{
setUnknown();
}
else
{
assign(value);
}
}
/**
* Constructs an instance that exactly matches that of the passed-in instance.
*
* @param value
* The value of the resulting instance.
*/
public Text(Text value)
{
// the value may be a proxy; make sure to unwrap and work with the real value
if (BaseDataType.isProxy(value))
{
if (value.val() instanceof Text)
{
value = (Text) value.val();
}
else
{
assign(value.val());
return;
}
}
deepAssign(value);
}
/**
* Constructs an instance that has the <code>String</code> value passed in and which
* <b>does not</b> represent the <code>unknown value</code>, unless the <code>value</code>
* argument is <code>null</code>.
* <p>
* Strings (especially converted literals) can have embedded null characters, but in the 4GL
* the resulting variable instances are "spacified". The given value will be processed by
* {@link #javaSpacifyNull} before storing it in the new instance.
*
* @param value
* The character value of the resulting instance.
*/
public Text(String value)
{
if (value == null)
{
setUnknown();
}
else
{
// protected against embedded null characters
this.value = javaSpacifyNull(value);
this.javaSpacifyNull = false;
}
}
/**
* Constructs an instance that has the <code>String</code> value passed in and which
* <b>does not</b> represent the <code>unknown value</code>, unless the <code>value</code>
* argument is <code>null</code>.
* <p>
* Strings (especially converted literals) can have embedded null characters, but in the 4GL
* the resulting variable instances are "spacified". The given value will be processed by
* {@link #javaSpacifyNull} before storing it in the new instance.
*
* @param value
* The character value of the resulting instance.
* @param caseSens
* Specifies whether this instance should use case sensitive
* comparisons.
*/
public Text(String value, boolean caseSens)
{
this(value, caseSens, true);
}
/**
* Constructs an instance that has the <code>String</code> value passed in and which
* <b>does not</b> represent the <code>unknown value</code>, unless the <code>value</code>
* argument is <code>null</code>.
* <p>
* Strings (especially converted literals) can have embedded null characters, but in the 4GL
* the resulting variable instances are "spacified". The given value will be processed by
* {@link #javaSpacifyNull} before storing it in the new instance.
*
* @param value
* The character value of the resulting instance.
* @param caseSens
* Specifies whether this instance should use case sensitive
* comparisons.
* @param javaSpacifyNull
* Flag indicating if {@link #javaSpacifyNull(String)} needs to be executed on the value or not.
*/
public Text(String value, boolean caseSens, boolean javaSpacifyNull)
{
if (value == null)
{
setUnknown();
}
else
{
// protected against embedded null characters
this.value = javaSpacifyNull ? javaSpacifyNull(value) : value;
this.caseSens = caseSens;
this.javaSpacifyNull = javaSpacifyNull;
}
}
/**
* Constructs an instance that has a <code>char</code> value passed in and which
* <b>does not</b> represent the <code>unknown value</code>.
* <p>
* If the given value is the null character, an ASCII space character will be assigned.
*
* @param ch
* The character value of the resulting instance.
*/
public Text(char ch)
{
// protected against embedded null characters
this.value = (ch == '\0') ? " " : new StringBuilder().append(ch).toString();
javaSpacifyNull = false;
}
/**
* A method that checks the equality of two Text parameters.
* The comparison of the String values is low level, it ignores
* case sensitivity and trailing/leading spaces.
*
* @param op1
* The first operand of the comparison.
* @param op2
* The second operand of the comparison
*
* @return True if the operands have the same String value.
*/
public static boolean valueEquals(Text op1, Text op2)
{
if (op1.unknown == op2.unknown && op1.unknown)
{
return true;
}
if (op1.unknown != op2.unknown)
{
return false;
}
return op1.value.equals(op2.value);
}
/**
* Set the CASE-SENSITIVE option for all elements in this array.
*
* @param list
* The character or longchar array.
* @param caseSens
* Flag indicating if the CASE-SENSITIVE option is on or off.
*/
public static void setCaseSensitive(Text[] list, boolean caseSens)
{
for (Text item : list)
{
item.setCaseSensitive(caseSens);
}
// set the decimal precision, for lengths > 0 the precision is stored
// in the array components
if (list.length == 0)
{
ArrayAssigner.setCaseSensitive(list, caseSens);
}
}
/**
* Return a string which contains the contents of the given string up to
* (but not including) the first <code>null</code> character (ASCII 0x00)
* and for that <code>null</code> character and all following characters
* the resulting string will contain spaces.
* <p>
* The resulting length of the output string will be the same as the
* length of the input string.
* <p>
* This processing is needed by some operators and/or functionality that
* process <code>character</code> data.
* <p>
* This transformation is for runtime strings, NOT for Progress compatible
* string literals. If a source code conversion is needed, please use
* {@link #progressSpacifyNull}.
* <p>
* As an example, a string such as:
* <p>
* <pre>
* Input Output
* ---------------------------------- ----------------------------------
* "This has a \0 (null) char." "This has a "
* "\0" " "
* </pre>
*
* @param data
* The input string (not valid for encoding in a source file).
*
* @return The "space-ified" output string (still not valid for
* encoding in a source file).
*/
public static String javaSpacifyNull(String data)
{
if (data.isEmpty())
{
return data;
}
StringBuilder sb = null;
int idx = data.indexOf('\0');
if (idx != -1)
{
sb = new StringBuilder(data.substring(0, idx));
StringHelper.repeatChar(' ', (data.length() - idx), sb);
}
return sb == null ? data : sb.toString();
}
/**
* Return a string which contains the contents of the given string up to
* (but not including) the first <code>null</code> character (ASCII 0x00),
* the resulting string is truncated at the first <code>null</code>.
* <p>
* As an example, a string such as:
* <p>
* <pre>
* Input Output
* ---------------------------------- ----------------------------------
* "This has a \0 (null) char." "This has a "
* "\0" ""
* </pre>
*
* @param data
* The input string (not valid for encoding in a source file).
*
* @return The truncated output string (still not valid for
* encoding in a source file).
*/
public static String javaTruncateNull(String data)
{
int idx = data.indexOf('\0');
if (idx == -1)
{
return data;
}
return data.substring(0, idx);
}
/**
* Return a string which contains the contents of the given string up to
* (but not including) the first <code>null</code> character (encoded as a
* Progress escape sequence) and for that <code>null</code> character and
* all following characters the resulting string will contain spaces.
* <p>
* The resulting length of the string (in terms of the number of
* characters that the string will include at runtime) will NOT change,
* although the length of the string representation (as a source code
* representation stored in a Java string) may be reduced as the
* <code>null</code> character's escape sequence (which would require 4
* characters to represent as in <code>~000</code>) and any following
* escaped sequences will be converted to a space which only consumes
* a single character position.
* <p>
* The enclosing single or double quote characters are maintained (not
* overwritten).
* <p>
* This processing is needed by the Progress preprocessor and other
* conversion processing of string literals.
* <p>
* This transformation is for Progress compatible string literals, NOT for
* runtime strings. If a runtime string conversion is needed, please use
* {@link #javaSpacifyNull}. Both forms of the <code>null</code>
* character escape sequence are handled.
* <p>
* As an example, a Progress string such as:
* <p>
* <pre>
* Input Output
* ---------------------------------- ----------------------------------
* "This has a ~000 (null) char." "This has a "
* 'This has a ~000 (null) char.' 'This has a '
* "~000" " "
* '~000' ' '
* "\000" " "
* '\000' ' '
* </pre>
*
* @param data
* A Progress source code representation of a string literal
* which is the input string to be transformed.
* @param unixEsc
* <code>true</code> if the backslash character should be
* treated as an escape character.
*
* @return The "space-ified" output string (still valid as a Progress
* string literal that could be output into source code).
*/
public static String progressSpacifyNull(String data, boolean unixEsc)
{
StringBuilder sb = new StringBuilder();
// put the leading single or double quote char into the output buffer
char quote = data.charAt(0);
sb.append(quote);
// only scan up to the trailing quote char
int end = data.length() - 1;
boolean eat = false;
// loop through the contents
for (int i = 1; i < end; i++)
{
char current = data.charAt(i);
char next;
if (current == '~' || (current == '\\' && unixEsc))
{
if (!eat)
{
// flow through mode
// check for the null escape sequence
String esc = ((i + 4) <= end) ? data.substring(i + 1, i + 4) : null;
if (esc != null && esc.equals("000"))
{
// the first null, shift into space-ify mode
eat = true;
i += 3;
sb.append(' ');
}
else
{
// some other escape sequence, allow it to flow through
sb.append(current);
// there must always be a following character, if it is
// a doubled escape char, we must handle it here to avoid
// dropping back through this code since it is not the
// opening of a new escape sequence (since all following
// chars can be handled the same way we just go ahead
// no matter which one it is)
i++;
sb.append(readChar(data, i));
}
}
else
{
// space-ify mode (eat the full escape sequence here)
// output the replacement (always 1 char)
sb.append(' ');
// there must always be at least 1 following character
i++;
next = readChar(data, i);
if (next == 'b' || next == 't' || next == 'n' ||
next == 'f' || next == 'r' || next == '\"' ||
next == '\\' || next == '~' || next == 'E')
{
// these are only 1 char in size, so we are done
}
else if (next >= '0' && next <= '7')
{
// eat the next 2 chars too because this is an octal
// escape sequence
i += 2;
}
}
}
else
{
sb.append(eat ? ' ' : current);
}
}
// add the trailing (close) quote char
sb.append(data.charAt(end));
return sb.toString();
}
/**
* Test for certain failure conditions and if OK return the indexed character, otherwise throw
* an exception with useful debugging information.
*
* @param input
* The string to read from.
* @param index
* The 0-based index into the string, from which to read the character.
*
* @return The character at the specified index.
*
* @throws IllegalArgumentException
* If the index is out of bounds.
*/
public static char readChar(String input, int index)
throws IllegalArgumentException
{
char next;
if (index == input.length())
{
throw new IllegalArgumentException("Unexpected end of string.");
}
try
{
next = input.charAt(index);
}
catch (IndexOutOfBoundsException iob)
{
throw new IllegalArgumentException("Invalid index (" + index +
" in string \"" + input + "\").");
}
return next;
}
/**
* Report if existing instances that have embedded null characters are allowed to be
* assigned to this instance.
*
* @return Always <code>true</code>. Sub-classes can override this to change the
* behavior.
*/
public boolean isEmbeddedNullAllowed()
{
return true;
}
/**
* Returns the larger (lexicographically) of the current or passed-in
* instance.
*
* @param c
* The instance to compare against.
*
* @return The largest instance.
*/
public Text maximum(Text c)
{
return (Text) super.maximum(c);
}
/**
* Returns the smaller (lexicographically) of the current or passed-in
* instance.
*
* @param c
* The instance to compare against.
*
* @return The smallest instance.
*/
public Text minimum(Text c)
{
return (Text) super.minimum(c);
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, double pos)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), new integer(-1), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, double pos, double len)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), new integer(len), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
* @param type
* Description of how to interpret the position and length values.
* At this time only "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, double pos, double len, String type)
throws ErrorConditionException
{
checkOverlayType(type);
replaceWorker(text, new integer(pos), new integer(len), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, double pos, NumberType len)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), len, true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
* @param type
* Description of how to interpret the position and length values.
* At this time only "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, double pos, NumberType len, String type)
throws ErrorConditionException
{
checkOverlayType(type);
replaceWorker(text, new integer(pos), len, true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, NumberType pos)
throws ErrorConditionException
{
replaceWorker(text, pos, new integer(-1), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, NumberType pos, double len)
throws ErrorConditionException
{
replaceWorker(text, pos, new integer(len), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
* @param type
* Description of how to interpret the position and length values.
* At this time only "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, NumberType pos, double len, String type)
throws ErrorConditionException
{
checkOverlayType(type);
replaceWorker(text, pos, new integer(len), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, NumberType pos, NumberType len)
throws ErrorConditionException
{
replaceWorker(text, pos, len, true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
* @param type
* Description of how to interpret the position and length values.
* At this time only "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(Text text, NumberType pos, NumberType len, String type)
throws ErrorConditionException
{
checkOverlayType(type);
replaceWorker(text, pos, len, true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, double pos)
throws ErrorConditionException
{
replaceWorker(new character(text),
new integer(pos),
new integer(-1),
true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, double pos, double len)
throws ErrorConditionException
{
replaceWorker(new character(text),
new integer(pos),
new integer(len),
true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
* @param type
* Description of how to interpret the position and length values.
* At this time only "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, double pos, double len, String type)
throws ErrorConditionException
{
checkOverlayType(type);
replaceWorker(new character(text),
new integer(pos),
new integer(len),
true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, double pos, NumberType len)
throws ErrorConditionException
{
replaceWorker(new character(text), new integer(pos), len, true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
* @param type
* Description of how to interpret the position and length values.
* At this time only "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, double pos, NumberType len, String type)
throws ErrorConditionException
{
checkOverlayType(type);
replaceWorker(new character(text), new integer(pos), len, true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, NumberType pos)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, new integer(-1), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, NumberType pos, double len)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, new integer(len), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
* @param type
* Description of how to interpret the position and length values.
* At this time only "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, NumberType pos, double len, String type)
throws ErrorConditionException
{
checkOverlayType(type);
replaceWorker(new character(text), pos, new integer(len), true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text, NumberType pos, NumberType len)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, len, true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace
* and the number of characters from the replacement text to
* include in the replacement.
* @param type
* Description of how to interpret the position and length values.
* At this time only "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text overlay(String text,
NumberType pos,
NumberType len,
String type)
throws ErrorConditionException
{
checkOverlayType(type);
replaceWorker(new character(text), pos, len, true);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, double pos)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), new integer(-1), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, double pos, double len)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), new integer(len), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, double pos, NumberType len)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), len, false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, NumberType pos)
throws ErrorConditionException
{
replaceWorker(text, pos, new integer(-1), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, NumberType pos, double len)
throws ErrorConditionException
{
replaceWorker(text, pos, new integer(len), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, NumberType pos, NumberType len)
throws ErrorConditionException
{
replaceWorker(text, pos, len, false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, double pos)
throws ErrorConditionException
{
replaceWorker(new character(text),
new integer(pos),
new integer(-1),
false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, double pos, double len)
throws ErrorConditionException
{
replaceWorker(new character(text),
new integer(pos),
new integer(len),
false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, double pos, NumberType len)
throws ErrorConditionException
{
replaceWorker(new character(text), new integer(pos), len, false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, NumberType pos)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, new integer(-1), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, NumberType pos, double len)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, new integer(len), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, NumberType pos, NumberType len)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, len, false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the passed text at the
* 1-based index into the string. The entire replacement text will be placed into the
* resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, double pos, String type)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), new integer(-1), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the passed text at the
* 1-based index into the string. The entire replacement text will be placed into the
* resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param len
* The number of characters in the source string to replace.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, double pos, double len, String type)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), new integer(len), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the passed text at the
* 1-based index into the string. The entire replacement text will be placed into the
* resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param len
* The number of characters in the source string to replace.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, double pos, NumberType len, String type)
throws ErrorConditionException
{
replaceWorker(text, new integer(pos), len, false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, NumberType pos, String type)
throws ErrorConditionException
{
replaceWorker(text, pos, new integer(-1), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param len
* The number of characters in the source string to replace.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, NumberType pos, double len, String type)
throws ErrorConditionException
{
replaceWorker(text, pos, new integer(len), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param len
* The number of characters in the source string to replace.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(Text text, NumberType pos, NumberType len, String type)
throws ErrorConditionException
{
replaceWorker(text, pos, len, false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, double pos, String type)
throws ErrorConditionException
{
replaceWorker(new character(text),
new integer(pos),
new integer(-1),
false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param len
* The number of characters in the source string to replace.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, double pos, double len, String type)
throws ErrorConditionException
{
replaceWorker(new character(text),
new integer(pos),
new integer(len),
false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param len
* The number of characters in the source string to replace.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, double pos, NumberType len, String type)
throws ErrorConditionException
{
replaceWorker(new character(text), new integer(pos), len, false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, NumberType pos, String type)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, new integer(-1), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param len
* The number of characters in the source string to replace.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, NumberType pos, double len, String type)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, new integer(len), false);
return this;
}
/**
* Overwrites characters in the current value of this instance with the
* passed text at the 1-based index into the string. The entire
* replacement text will be placed into the resulting value.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement is to occur.
* @param len
* The number of characters in the source string to replace.
* @param type
* Specifies whether this substring operates on "CHARACTER" (default), "RAW" or
* "COLUMN". At this time "character" is supported.
*
* @return The current instance is returned.
* @throws ErrorConditionException
*/
public Text replace(String text, NumberType pos, NumberType len, String type)
throws ErrorConditionException
{
replaceWorker(new character(text), pos, len, false);
return this;
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param ii
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance.
* @throws ErrorConditionException
*/
public Text replaceEntry(double ii, char delimit, String replace)
throws ErrorConditionException
{
if (isUnknown())
{
return this;
}
// convert 1 based index to 0 based
int i = (int) ii;
int idx = i - 1;
// split up the list (make sure all possible entries are included,
// even empty trailing entries --> use -1 as the limit to force this)
// using our own implementation of the split, which returns a 3-element array, with:
// - prefix (everything before the found element, will end with the delimiter)
// - the element at the given position
// - suffix (everything after the found element, will not start with the delimiter)
String[] array = TextOps.splitInt(value, delimit, idx, false);
if (array == null)
{
TextOps.genOutsideRangeError(i, value);
return this;
}
StringBuilder sb = new StringBuilder();
if (array[0] != null)
{
// this already has the delimiter
sb.append(array[0]);
}
sb.append(replace);
if (array[2] != null)
{
sb.append(delimit).append(array[2]);
}
String newVal = sb.toString();
checkUndoable(() -> new character(newVal));
// save the result; does not need null character protection since the data is already
// coming from an existing text instance of the same type
value = newVal;
return this;
}
/**
* Replaces the specified 1-based indexed comma-delimited entry in the
* current instance which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(double i, Text replace)
throws ErrorConditionException
{
if (replace.unknown)
{
setUnknown();
return this;
}
return replaceEntry((int) i, ',', replace.value);
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(double i, character delimit, Text replace)
throws ErrorConditionException
{
if (replace.unknown || delimit.unknown)
{
setUnknown();
return this;
}
return replaceEntry((int) i,
TextOps.safeDelimiter(delimit.value),
replace.value);
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(double i, character delimit, String replace)
throws ErrorConditionException
{
if (delimit.unknown)
{
setUnknown();
return this;
}
return replaceEntry((int) i,
TextOps.safeDelimiter(delimit.value),
replace);
}
/**
* Replaces the specified 1-based indexed comma-delimited entry in the
* current instance which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param replace
* The replacment text.
*
* @return The current instance.
* @throws ErrorConditionException
*/
public Text replaceEntry(double i, String replace)
throws ErrorConditionException
{
return replaceEntry((int) i, ',', replace);
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(double i, String delimit, Text replace)
throws ErrorConditionException
{
if (replace.unknown)
{
setUnknown();
return this;
}
return replaceEntry((int) i,
TextOps.safeDelimiter(delimit),
replace.value);
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance.
* @throws ErrorConditionException
*/
public Text replaceEntry(double i, String delimit, String replace)
throws ErrorConditionException
{
return replaceEntry((int) i, TextOps.safeDelimiter(delimit), replace);
}
/**
* Replaces the specified 1-based indexed comma-delimited entry in the
* current instance which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(NumberType i, character replace)
throws ErrorConditionException
{
if (i.isUnknown() || replace.unknown)
{
setUnknown();
return this;
}
return replaceEntry(i.intValue(), ',', replace.value);
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(NumberType i, character delimit, Text replace)
throws ErrorConditionException
{
if (i.isUnknown() || replace.unknown || delimit.unknown)
{
setUnknown();
return this;
}
return replaceEntry(i.intValue(),
TextOps.safeDelimiter(delimit.value),
replace.value);
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(NumberType i, character delimit, String replace)
throws ErrorConditionException
{
if (i.isUnknown() || delimit.unknown)
{
setUnknown();
return this;
}
return replaceEntry(i.intValue(),
TextOps.safeDelimiter(delimit.value),
replace);
}
/**
* Replaces the specified 1-based indexed comma-delimited entry in the
* current instance which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(NumberType i, String replace)
throws ErrorConditionException
{
if (i.isUnknown())
{
setUnknown();
return this;
}
return replaceEntry(i.intValue(), ',', replace);
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(NumberType i, String delimit, Text replace)
throws ErrorConditionException
{
if (i.isUnknown() || replace.unknown)
{
setUnknown();
return this;
}
return replaceEntry(i.intValue(),
TextOps.safeDelimiter(delimit),
replace.value);
}
/**
* Replaces the specified 1-based indexed entry in the current instance
* which must be a delimited list.
*
* @param i
* The 1-based index into the list.
* @param delimit
* The delimiter character.
* @param replace
* The replacment text.
*
* @return The current instance. It may have been set to the
* <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
* @throws ErrorConditionException
*/
public Text replaceEntry(NumberType i, String delimit, String replace)
throws ErrorConditionException
{
if (i.isUnknown())
{
setUnknown();
return this;
}
return replaceEntry(i.intValue(),
TextOps.safeDelimiter(delimit),
replace);
}
/**
* 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 character();
}
/**
* Sets the state (data and unknown value) of this instance based on the
* state of the passed instance. The configuration (case-sensitivity) of
* this instance is not set. Use {@link #deepAssign} if both state and
* configuration must be set.
*
* TODO this function can be removed after runtime conversion of dynamic
* tables containing a clob field is fixed
*
* @param value
* The instance from which to copy state. If <code>null</code>
* is passed, the instance will be set to the unknown value.
*/
public void assign(clob value)
{
assign((BaseDataType) value);
}
@Override
protected boolean isIncompatibleTypesOnConversion(BaseDataType value)
{
// invalid conversion is being thrown even if unknown
if (value instanceof memptr || value instanceof blob || (value instanceof raw && !value.isUnknown()))
{
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>Text</code>, the following automatic type conversions
* will occur:
* <p>
* <pre>
* unknown - sets the instance to unknown value
* Text - assigns normally
* all other cases - the result of toStringMessage() is assigned
* </pre>
* <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.
* <p>
* It is possible that string data could have embedded null characters , but in the 4GL
* the resulting variable instances are "spacified". The given value will be processed by
* {@link #javaSpacifyNull} before storing it in the new instance.
*
* @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 Text)
{
assign((Text) value);
}
else if (value instanceof object)
{
assign((object<?>) value);
}
else if (value instanceof jobject)
{
assign((jobject<?>) value);
}
else if (value instanceof rowid)
{
incompatibleTypesOnConversion();
}
else if (value instanceof BinaryData)
{
byte[] bytes = ((BinaryData) value).getByteArray();
String strVal = new String(bytes, Charset.forName(I18nOps.getJavaCPInternal()));
assign(javaSpacifyNull(strVal));
javaSpacifyNull = false;
}
else
{
String newVal = javaSpacifyNull(value.toStringMessage());
checkUndoable(() -> new character(newVal));
// protected against embedded null characters
this.value = newVal;
unknown = false;
javaSpacifyNull = false;
}
}
/**
* Convert the specified object instance to number using toString,
* and assign it to this instance.
*
* @param value
* The instance holding a object type.
*/
public void assign(object<?> 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;
}
if (value.isUnknown())
{
setUnknown();
}
else
{
assign(value.ref().toLegacyString());
}
}
/**
* Sets the state (data and unknown value) of this instance based on the state of the passed
* instance.
* <p>
* This form of assignment allows embedded null characters to be assigned from one already
* existing instance to another if the child class allows it.
*
* @param value
* The instance from which to copy state.
*/
public void assign(Text 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;
}
String val = value.value;
// allow values with embedded null characters to be assigned only if the subclass allows
if (val != null && !isEmbeddedNullAllowed() && val.indexOf('\0') >= 0)
{
ErrorManager.recordOrThrowError(142, "Unable to update Field");
return;
}
checkUndoable(value);
this.value = val;
unknown = value.unknown;
javaSpacifyNull = value.javaSpacifyNull;
}
/**
* Sets the state (data and unknown value) of this instance based on the
* state of the passed instance. The configuration (case-sensitivity) of
* this instance is not set. Use {@link #deepAssign} if both state and
* configuration must be set.
*
* @param value
* The instance from which to copy state. If <code>null</code>
* is passed, the instance will be set to the unknown value.
*/
public void assign(String value)
{
assign(new character(value));
}
/**
* 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)
{
if (value instanceof Text)
{
assign((Text) value);
}
else if (value instanceof BaseDataType)
{
assign(character.valueOf((BaseDataType) value));
}
else
{
// should never reach this point, anyway
assign(value.toString());
}
}
/**
* Sets the state (data and unknown value) and configuration
* (case-sensitivity) of this instance based on the state and configuration
* of the passed instance.
*
* @param value
* The instance from which to copy state and configuration.
*/
public void deepAssign(Text value)
{
assign(value);
caseSens = value.caseSens;
}
/**
* Accesses the state of this instance's <code>String</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>String</code> is
* <b>not valid</b>!
*
* @return The state of this instance's <code>String</code> value.
*/
public String getValue()
{
return value;
}
/**
* Sets the state of this instance's <code>String</code> value and sets the
* <code>unknown</code> state for this instance to <code>false</code>.
* <p>
* Strings data can have embedded null characters, but in the 4GL the resulting variable
* instances are "spacified". The given value will be processed by {@link #javaSpacifyNull}
* before storing it in the new instance.
*
* @param value
* The state to set this instance's <code>String</code> value.
*/
public void setValue(String value)
{
String val = javaSpacifyNull(value);
checkUndoable(() -> new character(val));
// protected against embedded null characters
this.value = val;
unknown = false;
javaSpacifyNull = false;
}
/**
* Determines if this instance compares case sensitively.
*
* @return <code>true</code> if this instance is set to case sensitive.
*/
public boolean isCaseSensitive()
{
return caseSens;
}
/**
* Sets the state of this instance's compares case sensitive flag.
* <p>
* This method is used to set the current value in a manner that does not
* schedule an automated reset to the prior value later. Thus, this method
* is suitable for the initial configuration of a new instance. If a
* temporary case-sensitivity change is needed, use
* {@link #setTemporaryCaseSensitive}.
*
* @param caseSens
* <code>true</code> if this instance must be set to case
* sensitive.
*/
public void setCaseSensitive(boolean caseSens)
{
if (caseSens != this.caseSens)
{
checkUndoable(true);
}
this.caseSens = caseSens;
}
/**
* Sets the state of this instance's compares case sensitive flag for the
* duration of the caller's current scope. At the end of the caller's
* scope a notification will trigger a reset of the case sensitive flag
* to the current value.
* <p>
* This method should only be used when importing references via the
* {@link SharedVariableManager} as a local instance or a new shared
* instance would not need to be reset, since they would have no prior
* value to which to return. To set the initial case-sensitivity, please
* se {@link #setCaseSensitive}.
* <p>
* The {@link TransactionManager#registerFinalizable} facility is used to
* obtain the callback necessary to reset the sensitivity at end of the
* caller's scope.
*
* @param caseSens
* <code>true</code> if this instance must be set to case
* sensitive.
*/
public void setTemporaryCaseSensitive(boolean caseSens)
{
// do nothing if the request precision is the same as the current
// precision
if (this.caseSens != caseSens)
{
// remember the old value
boolean old = this.caseSens;
// reset
setCaseSensitive(caseSens);
// schedule the reset
SensitivityResetter sr = new SensitivityResetter(old);
TransactionManager.registerFinalizable(sr, false);
}
}
/**
* Test equality between the internal values of this object and the given parameter on a case
* sensitive basis. Before the internal values are checked, the unknown states of both operands
* are compared. If both are unknown, {@code true} is returned. If only one is unknown,
* {@code false} is returned. Otherwise, the case sensitive test is performed.
*
* @param c
* Another character instance to test for case sensitive equality with this object.
*
* @return {@code true} if this object and the given object test as equal, else {@code false}.
*/
public boolean equalsCaseSensitively(Text c)
{
boolean u1 = isUnknown();
boolean u2 = c.isUnknown();
if (u1 && u2)
{
return true;
}
if (u1 || u2)
{
return false;
}
// quick out if the values are the same object instance
if (value == c.value)
{
return true;
}
// prepare the data
String s1 = StringHelper.safeTrimTrailingSpaces(value);
String s2 = StringHelper.safeTrimTrailingSpaces(c.value);
return s1.equals(s2);
}
/**
* Hash code implementation which is consistent with {@link #equalsCaseSensitively}.
*
* @return Hash code value for this object instance.
*/
public int caseSensitiveHashCode()
{
int result = 17;
if (isUnknown())
{
return result;
}
result = 37 * result + value.hashCode();
return result;
}
/**
* 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;
value = null;
javaSpacifyNull = true;
}
/**
* 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
{
byte flags = in.readByte();
unknown = (flags & 1) != 0;
caseSens = (flags & 2) != 0;
if ((flags & 4) != 0)
{
value = readString(in);
}
}
/**
* 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
{
int flags = unknown ? 1 : 0;
if (caseSens)
{
flags |= 2;
}
// the next object can be null so we write an indicator var first to
// notify reader if there is following data to be read
boolean there = (value != null);
if (there)
{
flags |= 4;
}
out.writeByte(flags);
if (there)
{
writeString(out, (String)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} interface.
* <p>
* Core worker that handles comparisons between 2 string operands after removing trailing
* spaces and honoring the case sensitivity operands.
* <p>
* Case-insensitive comparisons are obtained by manually uppercasing both operands which is how
* Progress does it. Lowercase comparisons cannot be used because the results for
* {@link CompareOps#isGreaterThan}, {@link CompareOps#isLessThan},
* {@link CompareOps#isGreaterThanOrEqual} and {@link CompareOps#isLessThanOrEqual} would be
* different in Java than in Progress when the following characters are included in the string:
* <pre>
* [
* \
* ^
* _
* '
* </pre>
* <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}.
*/
public int compareTo(Object obj)
{
if (unknown)
{
return -1;
}
Text c = null;
if (obj instanceof Text)
{
c = (Text) obj;
// quick out if the String objects are the same instance
if ((unknown && c.unknown) || value == c.value)
{
return 0;
}
else if (c.unknown)
{
return -1;
}
}
else if (obj instanceof String)
{
// quick out if the String objects are the same instance
if (value == obj)
{
return 0;
}
c = new character((String) obj);
}
else
{
return -1;
}
// prepare the data
String s1 = StringHelper.safeTrimTrailingSpaces(value);
String s2 = StringHelper.safeTrimTrailingSpaces(c.value);
// if either instance requires case sensitivity, force a case sensitive comparison
if (caseSens || c.caseSens)
{
return s1.compareTo(s2);
}
// DO NOT use String.compareToIgnoreCase() since this lowercases and yields different
// results for >, <, >= and <= forms when [ \ ^ _ ' characters are included in the operands
if (s1.isEmpty() && s2.isEmpty())
{
return 0;
}
else if (s1.isEmpty())
{
return -1;
}
else if (s2.isEmpty())
{
return 1;
}
return TextOps.getUpper(s1).compareTo(TextOps.getUpper(s2));
}
/**
* 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;
}
String s = StringHelper.safeTrimTrailing(value);
result = 37 * result + (caseSens ? s.hashCode() : StringHelper.hashCodeCaseInsensitive(s));
return result;
}
/**
* Return the current value as a Java wrapper.
*
* @return Current value or <code>null</code> if unknown.
*/
public String toJavaType()
{
return (isUnknown() ? null : getValue());
}
/**
* Get the state of the {@link #javaSpacifyNull} flag.
*
* @return See above.
*/
public boolean isJavaSpacifyNull()
{
return javaSpacifyNull;
}
/**
* Returns the length of the current Text object instance in chars. Internal version.
*
* @return Current text variable char length.
*/
protected int lengthInt()
{
return value.length();
}
/**
* Check the overlay type description and throw an exception if the type
* is not yet supported. The check is case-insensitive.
*
* @param type
* Must be "character" at this time.
*
* @throws IllegalArgumentException
* If the type doesn't match "character".
*/
private void checkOverlayType(String type)
{
if (type != null && !type.equalsIgnoreCase("character"))
{
String err = String.format("OVERLAY type %s is not supported!", type);
throw new IllegalArgumentException(err);
}
}
/**
* Core method to implement overlay and replace support. This method
* changes the current value of this instance, overwriting characters
* at the specified index given by <code>pos</code> with the text
* specified by <code>text</code>. The number of replaced characters
* is specified by <code>len</code>, with a negative number indicating
* that the length of characters in <code>text</code> is used. If
* <code>overlay</code> mode is requested, then the <code>len</code>
* parameter also limits the number of characters in the <code>text</code>
* parameter which will be used as the replacement. If any of the
* parameters are <code>unknown value</code>, the instance will be set
* to the <code>unknown value</code>.
*
* @param text
* Replacement text.
* @param pos
* The 1-based index into the string at which the replacement
* is to occur.
* @param len
* The number of characters in the source string to replace.
* @param overlay
* <code>true</code> if overlay mode is required.
*/
private void replaceWorker(Text text,
NumberType pos,
NumberType len,
boolean overlay)
throws ErrorConditionException
{
// quick out
if (this.isUnknown())
{
// nothing to do, you can't replace chars in the unknown value
return;
}
// are any of our inputs unknown?
if (text.isUnknown() || pos.isUnknown() || len.isUnknown())
{
// set our result to unknown and return
setUnknown();
return;
}
String rtxt = text.getValue();
int rpos = pos.intValue();
int rlen = len.intValue();
// argument check
if (rpos < 1)
{
TextOps.genNegativeIndexError();
return;
}
// convert from 1-based to 0-based index
rpos--;
// -1 is equivalent of the length of the replacement text
if (rlen == -1)
{
rlen = rtxt.length();
}
// replacement text is exactly as passed-in if not in overlay mode
if (overlay)
{
// overlay mode means that the len parameter also changes the
// replacement text
if (rtxt.length() < rlen)
{
// pad to the requested length
rtxt = StringHelper.leftAlignText(rtxt, rlen);
}
else
{
// truncate the replacement text
rtxt = rtxt.substring(0, rlen);
}
}
StringBuilder sb = new StringBuilder();
// start with the beginning of the string
if (rpos >= value.length())
{
// pad to the requested position
sb.append(StringHelper.leftAlignText(value, rpos));
}
else
{
// just substring the first portion
sb.append(value.substring(0, rpos));
}
// insert our replacement text
sb.append(rtxt);
// index of the end of the string
int elen = rpos + rlen;
// if there is any text at the end of the string, add that
if (value.length() > elen)
{
sb.append(value.substring(elen));
}
String newVal = sb.toString();
checkUndoable(() -> new character(newVal));
// save the results into our instance data; does not need null character protection
// since the data is already coming from two existing text instances of the same type
value = newVal;
}
/**
* Helper to reset the associated instance's case sensitivity to a prior
* value based on a notification that a particular scope has exited. This
* scope notification support is provided by
* {@link TransactionManager#registerFinalizable} and the {@link
* Finalizable} interface.
*/
private class SensitivityResetter
implements Finalizable
{
/** The sensitivity to which the containing instance will be reset. */
boolean oldCaseSens;
/**
* Construct an instance which can reset the containing instance's
* case sensitivity when a notification is received. See
* {@link character#setTemporaryCaseSensitive}.
*
* @param oldCaseSens
* The case sensitivity to which the containing instance must
* be reset.
*/
SensitivityResetter(boolean oldCaseSens)
{
this.oldCaseSens = oldCaseSens;
}
/**
* Processes the end of scope notification and resets the associated
* instance's case sensitivity. This notification is provided by the
* {@link TransactionManager}.
*/
public void finished()
{
Text.this.caseSens = oldCaseSens;
}
/**
* Provides a notification that the external program scope in which the object is registered is
* is being deleted and the object's reference will be lost after this method is called.
* <p>
* This is a no-op in this case.
*/
@Override
public void deleted()
{
// no-op
}
/**
* Processes the iterate notification provided by the {@link
* TransactionManager}. This implementation does nothing.
*/
public void iterate()
{
}
/**
* Processes the retry notification provided by the {@link
* TransactionManager}. This implementation does nothing.
*/
public void retry()
{
}
}
}