TextOps.java
/*
** Module : TextOps.java
** Abstract : Progress 4GL compatible TextOps object
**
** Copyright (c) 2012-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 AIL 20121206 Created initial version by moving static methods from the character class as
** this methods will be used with character and also longchar arguments.
** 002 CA 20130226 Added LENGTH function support when the type is an expression.
** 003 CA 20130306 Added missing numEntries(String, String).
** 004 OM 20130322 Added missing length forms.
** 005 VIG 20131010 Added null processing for type argument in length-methods.
** 006 OM 20140924 Added extra parameter to matches methods so they can handle unix-escapes as
** configured/converted. Fixed case-sensitivity of matches methods.
** 007 GES 20141003 Embedded null character handling fixes.
** 008 GES 20141130 Added variants of substring().
** 009 GES 20150205 Added longchar operand support to concat().
** 010 ECF 20150801 Performance optimizations in string operations and reflection logic.
** 011 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 012 OM 20160603 Fixed rtrimming to space character only.
** 013 ECF 20171227 Cache regex patterns to avoid redundant, expensive compilation.
** 014 SVL 20180215 trim() was made public.
** 015 CA 20181208 LENGTH function can have "char" as shorthand for "character" mode.
** 016 OM 20190301 Fixed numEntries() for unknown parameters. Deprecated numEntriesOf() methods.
** 017 EVL 20190415 Adding runtime support for CODEPAGE-CONVERT function.
** 018 CA 20190723 Fixed MATCHES - the DOT must match anything, included \r\n chars.
** 019 CA 20200110 Fixed MATCHES - pattern must match the entire text, not partial.
** 020 VVT 20200203 The concat(Object ... list) method reworked (see #4433).
** 021 ME 20200401 Add `entries` helper methods to get all entries as an array instead of looping
** using entry.
** 022 OM 20200609 Small optimizations in static trim().
** 023 HC 20200726 Added lookup method overload.
** 024 ME 20200810 Added a fallback clause to handle non-String/non-BDT in substituteImpl().
** EVL 20200922 Rework for frequently used string operations. Reverted from 11575 to 11573 back due to
** slowing down.
** ME 20201009 Added helper method to check if text is empty (unknown or contains only spaces).
** ME 20201127 Use resource id for objects in substitute.
** OM 20201202 Fixed support for substitution of Java Boolean values.
** VVT 20210106 splitInt(): typo in javadoc fixed.
** ME 20210128 Avoid using java's trim to check if string is empty, 4GL only consider spaces to be empty
** (not tab, cr, lf).
** CA 20210322 Fixed SUBSTITUTE when a legacy enum instance is used.
** CA 20210328 Added CAPS operator.
** OM 20210414 codePageConvert() returns an object with expected CP.
** AIL 20210422 Added error to REPLACE function if used with empty second parameter.
** CA 20210423 Fixed I18nOps.convmap2Java access (key must be uppercased).
** ECF 20210511 Added rightTrimLower.
** CA 20210628 Fixed DATETIME and DATETIME-TZ toStringExport() usage.
** CA 20220117 Improved isEmpty performance, just check if the text has another char other than space.
** CA 20221021 Instantiate the result with the proper codepage for REPLACE, [LEFT-/RIGHT-]TRIM, SUBSTRING,
** UPPER and LOWER functions, when the source is a longchar.
** TJD 20220504 Java 11 compatibility minor changes
** CA 20220426 Added columnLength() stub.
** CA 20220514 Added stubs for IS-LEAD-BYTE() builtin function.
** OM 20220524 Added intermediary runtime support for LENGTH(..., COLUMN). Local optimization.
** 025 HC 20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
** for performance.
** 026 EVL 20230317 Changed getting Text object length to use internal lengthInt() method.
** 027 VVT 20230307 Fixed formatting unknown longchars with SUBSTITUTE. See #7175.
** 028 AL2 20230512 Right trimming with white-spaces doesn't require case insensitivity checks.
** RAA 20230525 Creating a new BaseDataType instance is now done using BaseDataTypeFactory.
** 029 OM 20231128 Skip checking for error 11679 when the source not specified in codePageConvert.
** 030 OM 20231219 Fixed regression in H029.
** 031 CA 20240214 Added UPPER(POLY) and LOWER(POLY) overloads.
** 032 AL2 20240221 Fixed r-index semantics to honor the starting point more accurately.
** 033 CA 20240323 Optimized 'trim' and 'replace' - if the trim list of from/to strings don't have any
** unicode alphabet codepoints, then assume case-sensitive, to avoid uppercase.
** 20240324 Added a cache for uppercase strings. Optimized 'instantiate'.
** 20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** 20240331 The 'upperCaseCache' cache must have different sizes for FWD server and client.
** 034 CA 20240401 Fixed regression in CA/033 - this class is used during conversion, so the static c'tor
** needs to be aware that 'there is no session', and create a small cache.
** 035 CA 20240402 'trim' can receive a null String when called from H2.
** 036 CA 20240423 Optimized entry, split and numEntries APIs, to create a minimal number of String instances.
** 037 CA 20240429 Fixed regression in 'splitInt' when there is a trailing empty element as in "`0`".
** 038 CA 20240809 Improved 'replaceAll' and 'substitute' functions.
** 039 CA 20240924 Implemented KMP string search for 'replaceAll' function.
** 040 CA 20241030 Avoid 'Text.javaSpacifyNull' if the string is known to already have been processed.
** Improved caching of match patterns.
** 041 AS 20241104 Removed the prefix from the 560 error message.
** 042 AB2 20241115 Changed the setCaseSensitive calls from replaceAllImpl to take the original
** flag value (See #9333).
** 043 AS 20241202 Added 11672 and 14434 errors in columnLength.
** 044 AS 20250130 Added missing implementation token support.
** 045 ICP 20250123 Used integer.of and int64.of to leverage caches instances.
** 046 ICP 20250123 Used integer.of to leverage caches instances.
** 047 ES 20250221 Switch to throwing the StopConditionException through ErrorManager.
*/
/*
** 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.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.goldencode.cache.LRUCache;
import com.goldencode.p2j.net.SessionManager;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.util.StringHelper;
/**
* A class that provides Progress 4GL compatible character and longchar
* functions and operators. This class provides all functionality as statics
* and has no instance data.
* <p>
* The following is the mapping of Progress language features to the
* corresponding feature in this class:
* <p>
* <pre>
* + operator (concatenation) {@link #concat}
* num-entries function {@link #numEntries}
* begins operator {@link #begins} and {@link #_begins}
* matches operator {@link #matches} and {@link #_matches}
* can-do function {@link #matchesList}
* caps function {@link #toUpperCase}
* compare function {@link #compare}
* entry function {@link #entry}
* index function {@link #indexOf}
* lc function {@link #toLowerCase}
* left-trim function {@link #leftTrim}
* length function {@link #length} and {@link #byteLength}
* lookup function {@link #lookup}
* maximum function {@link #maximum}
* minimum function {@link #minimum}
* r-index function {@link #lastIndexOf}
* replace function {@link #replaceAll}
* right-trim function {@link #rightTrim}
* substitute function {@link #substitute}
* substring function {@link #substring}
* trim function {@link #trim}
* entry language statement {@link Text#replaceEntry}
* overlay language statement {@link Text#overlay}
* substring language statement {@link Text#replace}
* </pre>
* <p>
* There are convenience versions of the begins and matches operators 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 'With' appended. For example, the begins operator is
* {@link #begins}.
* <p>
* The following is the list of Progress language features which optionally
* return a <code>character</code> instance that reflects the case-sensitivity
* of the operands or input values:
* <pre>
* + operator
* caps function {@link #toUpperCase}
* lc function {@link #toLowerCase}
* left-trim function {@link #leftTrim}
* maximum function {@link #maximum}
* minimum function {@link #minimum}
* replace function {@link #replaceAll}
* right-trim function {@link #rightTrim}
* substring function {@link #substring}
* trim function {@link #trim}
* </pre>
* <p>
* Strangely enough, the <code>chr, entry</code> and <code>substitute</code>
* functions DO NOT provide this support in Progress. Each of the
* corresponding backing methods properly duplicate this behavior.
* <p>
* This class implements unknown value logic for all operators and functions.
* Generally, if any parameter of the these methods is unknown, the result is
* unknown.
*/
public final class TextOps
{
/** Specifies the less than operator for {@link #compare}. */
private static final int LT = 0;
/** Specifies the less than or equal operator for {@link #compare}. */
private static final int LE = 1;
/** Specifies the greater than operator for {@link #compare}. */
private static final int GT = 2;
/** Specifies the greater than or equal operator {@link #compare}. */
private static final int GE = 3;
/** Specifies the equal operator for {@link #compare}. */
private static final int EQ = 4;
/** Specifies the not equal operator for {@link #compare}. */
private static final int NE = 5;
/** Specifies the matches operator for {@link #compare}. */
private static final int MATCHES = 6;
/** Specifies the begins operator for {@link #compare}. */
private static final int BEGINS = 7;
/** Specifies the RAW type for {@link #compare} or {@link #substring}. */
private static final int RAW = 6;
/** Specifies the COLUMN type for {@link #compare} or {@link #substring}. */
private static final int COLUMN = 7;
/** Specifies the FIXED type for {@link #compare} or {@link #substring}. */
private static final int FIXED = 8;
/** Specifies the CHARACTER type for {@link #compare} or {@link #substring}. */
private static final int CHARACTER = 9;
/** Specifies the CASE-SENSITIVE strength for {@link #compare}. */
private static final int CASE_SENSITIVE = 10;
/** Specifies the CASE-INSENSITIVE strength for {@link #compare}. */
private static final int CASE_INSENSITIVE = 11;
/** Specifies the CAPS type for {@link #compare} or {@link #substring}. */
private static final int CAPS = 12;
/** Maximum number of patterns to cache before clearing cache */
private static final int MAX_CACHED_PATTERNS = 32768;
/** Cache of regex patterns to avoid unnecessary compilation, used by {@link #matchesList}. */
private static final LRUCache<String, Pattern> listPatternCache = new LRUCache<>(MAX_CACHED_PATTERNS);
/** Cache of regex patterns to avoid unnecessary compilation, used by {@link #matchImpl}. */
private static final LRUCache<String, Pattern> matchPatternCache = new LRUCache<>(MAX_CACHED_PATTERNS);
/** Cache of uppercase {@link String} values. */
private static final LRUCache<String, String> upperCaseCache;
/** A token used for non repetitive logging of missing columnLength implementation.
* It is to be removed from the class attributes after the feature is implemented. */
private static volatile Object columnLengthToken = null;
static
{
// we are outside FWD server, assume a small cache
boolean isClient = SessionManager.get() == null || SessionManager.get().isLeaf();
// this cache is not configurable; FWD client's heap is usually small - so a use a smaller size.
int defaultSize = isClient ? 1024 : 1024 * 1024;
upperCaseCache = new LRUCache<>(defaultSize);
}
/**
* Get the uppercase version of this string (maybe from the {@link #upperCaseCache cache}.
* <p>
* Only strings with length less than 128 are cached.
*
* @return The uppercase instance of this string.
*/
public static String getUpper(String str)
{
if (str == null)
{
return null;
}
// TODO: use the correct locale for switching cases (see toUpperCaseImpl)
if (str.length() > 128)
{
// cache only small strings
return str.toUpperCase();
}
String upper = null;
synchronized (upperCaseCache)
{
// TODO: cache by codepage
upper = upperCaseCache.get(str);
if (upper == null)
{
upper = str.toUpperCase();
upperCaseCache.put(str, upper);
}
}
return upper;
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* basing case sensitivity on the left operand.
* <p>
* The Progress style matching pattern passed as a parameter is converted to a regular
* expression using {@link SQLHelper#convertToRegEx} during this method.
*
* @param op
* String to check for a match.
* @param pattern
* Progress style matching pattern.
* @param windows
* <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
* the backslash (\)is an escape character, like tilde (\).
*
* @return <code>true</code> if op matches the regular expression or <code>false</code> if
* there is no match or if either operand is <code>unknown</code>.
*/
public static boolean _matches(Text op, character pattern, boolean windows)
{
if (op.isUnknown() || pattern.isUnknown())
{
return false;
}
boolean caseSensitive = op.isCaseSensitive() || pattern.isCaseSensitive();
return _matches(op.getValue(), pattern.getValue(), caseSensitive, windows);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* basing case sensitivity on the left operand.
* <p>
* This form of the method is unix-escape independent because Progress style matching pattern
* passed as a parameter is already transformed at conversion time to a regular expression.
*
* @param op
* String to check for a match.
* @param pattern
* RegExp matching pattern transformed from Progress at conversion time.
*
* @return <code>true</code> if op matches the regular expression or <code>false</code> if
* there is no match or if the left operand is <code>unknown</code>.
*/
public static boolean _matches(Text op, String pattern)
{
if (op.isUnknown())
{
return false;
}
String string = op.getValue();
if (!op.isCaseSensitive())
{
string = string.toUpperCase();
pattern = pattern.toUpperCase();
}
return matchImpl(string, pattern);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* case-insensitively.
* <p>
* The Progress style matching pattern passed as a parameter is converted to a regular
* expression using {@link SQLHelper#convertToRegEx} during this method.
*
* @param op
* String to check for a match.
* @param pattern
* Progress style matching pattern.
* @param windows
* <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
* the backslash (\)is an escape character, like tilde (\).
*
* @return <code>true</code> if op matches the regular expression or <code>false</code> if
* there is no match or if the right operand is <code>unknown</code>.
*/
public static boolean _matches(String op, character pattern, boolean windows)
{
if (pattern.isUnknown())
{
return false;
}
return _matches(op, pattern.getValue(), pattern.isCaseSensitive(), windows);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* ignoring the case of the operand and the pattern.
* <p>
* This form of the method is unix-escape independent because Progress style matching pattern
* passed as a parameter is already transformed at conversion time to a regular expression.
* By default, in P4GL character operations are case insensitive.
*
* @param op
* String to check for a match.
* @param pattern
* RegExp matching pattern transformed from Progress at conversion time.
*
* @return <code>true</code> if op matches the regular expression.
*/
public static boolean _matches(String op, String pattern)
{
op = op.toUpperCase();
pattern = pattern.toUpperCase();
return matchImpl(op, pattern);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* honoring the case sensitivity preference of the caller.
* <p>
* The Progress style matching pattern passed as a parameter is converted to a regular
* expression using {@link SQLHelper#convertToRegEx} during this method.
*
* @param op
* String to check for a match.
* @param pattern
* Progress style matching pattern.
* @param caseSens
* <code>true</code> if a case-sensitive comparison should be used.
* @param windows
* <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
* the backslash (\)is an escape character, like tilde (\).
*
* @return <code>true</code> if op matches the regular expression.
*/
public static boolean _matches(String op, String pattern, boolean caseSens, boolean windows)
{
String up = caseSens ? op : op.toUpperCase();
pattern = caseSens ? pattern : pattern.toUpperCase();
return matchImpl(up, SQLHelper.convertToRegEx(pattern, windows));
}
/**
* Tests if the first operand begins with the second operand, implementing
* a case-sensitive comparison if either of the two operands is case-sensitive.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
*
* @return <code>true</code> if op1 begins with op2 or <code>false</code> if there is no
* match or if either operand is <code>unknown value</code>.
*/
public static boolean _begins(Text op1, character op2)
{
if (op1.isUnknown() || op2.isUnknown())
{
return false;
}
boolean cs = op1.isCaseSensitive() || op2.isCaseSensitive();
return _begins(op1.getValue(), op2.getValue(), cs);
}
/**
* Tests if the first operand begins with the second operand, implementing
* a case-sensitive comparison if the right operand is case-sensitive.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
*
* @return <code>true</code> if op1 begins with op2 or <code>false</code> if there is no
* match or if the right operand is <code>unknown value</code>.
*/
public static boolean _begins(String op1, character op2)
{
if (op2.isUnknown())
{
return false;
}
return _begins(op1, op2.getValue(), op2.isCaseSensitive());
}
/**
* Tests if the first operand begins with the second operand, implementing a case-sensitive
* comparison if the left operand is case-sensitive.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
*
* @return <code>true</code> if op1 begins with op2 or <code>false</code> if there is no
* match or if the left operand is <code>unknown value</code>.
*/
public static boolean _begins(Text op1, String op2)
{
if (op1.isUnknown())
{
return false;
}
return _begins(op1.getValue(), op2, op1.isCaseSensitive());
}
/**
* Tests if the first operand begins with the second operand, ignoring the case of the two
* operands.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
*
* @return <code>true</code> if op1 begins with op2.
*/
public static boolean _begins(String op1, String op2)
{
return _begins(op1, op2, false);
}
/**
* Tests if the first operand begins with the second operand, honoring the case sensitivity
* preference of the caller.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
* @param caseSens
* <code>true</code> if a case-sensitive comparison should be used.
*
* @return <code>true</code> if op1 begins with op2.
*/
public static boolean _begins(String op1, String op2, boolean caseSens)
{
String up1 = caseSens ? op1 : op1.toUpperCase();
String up2 = caseSens ? op2 : op2.toUpperCase();
return up1.startsWith(up2);
}
/**
* Tests if the first operand begins with the second operand, implementing
* a case-sensitive comparison if either of the two operands is
* case-sensitive.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
*
* @return <code>true</code> if op1 begins with op2 or
* <code>unknown value</code> if either operand is
* <code>unknown value</code>.
*/
public static logical begins(Text op1, character op2)
{
if (op1.isUnknown() || op2.isUnknown())
{
return logical.UNKNOWN;
}
boolean cs = (op1.isCaseSensitive() || op2.isCaseSensitive());
return begins(op1.getValue(), op2.getValue(), cs);
}
/**
* Tests if the first operand begins with the second operand, implementing a case-sensitive
* comparison if the right operand is case-sensitive.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
*
* @return <code>true</code> if op1 begins with op2 or <code>unknown value</code> if the
* right operand is <code>unknown value</code>.
*/
public static logical begins(String op1, character op2)
{
if (op2.isUnknown())
{
return logical.UNKNOWN;
}
return begins(op1, op2.getValue(), op2.isCaseSensitive());
}
/**
* Tests if the first operand begins with the second operand, implementing a case-sensitive
* comparison if the left operand is case-sensitive.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
*
* @return <code>true</code> if op1 begins with op2 or <code>unknown value</code> if the left
* operand is <code>unknown value</code>.
*/
public static logical begins(Text op1, String op2)
{
if (op1.isUnknown())
{
return logical.UNKNOWN;
}
return begins(op1.getValue(), op2, op1.isCaseSensitive());
}
/**
* Tests if the first operand begins with the second operand, ignoring the
* case of the two operands.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
*
* @return <code>true</code> if op1 begins with op2.
*/
public static logical begins(String op1, String op2)
{
return begins(op1, op2, false);
}
/**
* Tests if the first operand begins with the second operand, honoring the
* case sensitivity preference of the caller.
*
* @param op1
* First string to compare.
* @param op2
* Second string to compare.
* @param caseSens
* <code>true</code> if a case-sensitive comparison should be used.
*
* @return <code>true</code> if op1 begins with op2.
*/
public static logical begins(String op1, String op2, boolean caseSens)
{
String up1 = caseSens ? op1 : op1.toUpperCase();
String up2 = caseSens ? op2 : op2.toUpperCase();
return logical.of(up1.startsWith(up2));
}
/**
* Implementation of the IS-LEAD-BYTE() builtin function.
*
* @param txt
* The text to check.
*
* @return TODO
*/
public static logical isLeadByte(String txt)
{
UnimplementedFeature.missing("IS-LEAD-BYTE() is not implemented.");
return logical.UNKNOWN;
}
/**
* Implementation of the IS-LEAD-BYTE() builtin function.
*
* @param txt
* The text to check.
*
* @return TODO
*/
public static logical isLeadByte(Text txt)
{
UnimplementedFeature.missing("IS-LEAD-BYTE() is not implemented.");
return logical.UNKNOWN;
}
/**
* Returns the length of the given <code>character</code> literal in display or print character-columns.
*
* @param s
* The literal to test.
*
* @return The number of bytes in the given literal.
*/
public static integer columnLength(Text s)
{
if (s.getClass() == longchar.class)
{
ErrorManager.recordOrThrowError(11672,
"\"Column\" is not a valid option for a LONGCHAR variable", false);
}
else if (s.getClass() == clob.class)
{
ErrorManager.recordOrThrowError(14434,
"Column is not a valid LENGTH option for CLOBs", false);
}
return columnLength(s.getValue());
}
/**
* Returns the length of the given <code>character</code> literal in display or print character-columns.
*
* @param s
* The literal to test.
*
* @return The number of bytes in the given literal.
*/
public static integer columnLength(String s)
{
columnLengthToken = UnimplementedFeature.
missing("LENGTH(..., 'COLUMN') is not implemented. Temporarily, returning LENGTH(..., 'CHAR'), instead.",
columnLengthToken);
return length(s);
}
/**
* Returns the length of the given <code>character</code> or
* <code>longchar</code> instance in bytes.
*
* @param c
* The instance to test.
*
* @return The number of bytes in the given instance or -1 if the
* instance is <code>unknown value</code>.
*/
public static int byteLengthOf(Text c)
{
if (c.isUnknown())
return -1;
return c.getValue().getBytes().length;
}
/**
* Returns the length of the given <code>character</code> literal in bytes.
*
* @param s
* The literal to test.
*
* @return The number of bytes in the given literal.
*/
public static integer byteLength(String s)
{
return integer.of(s.getBytes().length);
}
/**
* Returns the length of the given {@code character} or {@code longchar} instance in bytes. This method
* takes into account the CP used. For a {@code longchar} instance its codepage is used (as configured
* with {@code FIX-CODEPAGE} statement), for a {@code character} instance, the CPINTERNAL is used.
* <p>
* <b>Note:</b> FWD is using by default UTF-8 as CPINTERNAL which differs from ISO8859-1 of ABL. Some
* result may mot be consistent.
*
* @param c
* The instance to test.
*
* @return The number of bytes in the given instance or {@code unknown value} if the instance is
* {@code unknown value}.
*/
public static integer byteLength(Text c)
{
if (c.isUnknown())
{
return integer.UNKNOWN;
}
// detect the 4GL CODEPAGE
String ablCP = null;
if (c instanceof longchar)
{
ablCP = ((longchar) c)._getCodePage();
}
if (ablCP == null)
{
// defaulting to FWD internal CP for CHARACTER and LONGCHAR values which do not have CP fixed
ablCP = I18nOps._getCPInternal();
}
// convert the 4GL CODEPAGE to Java CHARSET
String charSet = I18nOps.getJavaCharset(ablCP);
if (charSet == null)
{
// failed to obtain the java charset for [ablCP]
return integer.UNKNOWN;
}
try
{
return integer.of(c.getValue().getBytes(charSet).length);
}
catch (UnsupportedEncodingException e)
{
// the CHARSET failed to work as expected
return integer.UNKNOWN;
}
}
/**
* Convert the given text from one code page to another. As no codepage is
* specified for this API, no conversion occurs.
*
* @param text
* The text which needs to be converted.
*
* @return The same text, with no change.
*/
public static <T extends Text> T codePageConvert(T text)
{
return text;
}
/**
* Convert the given text using the specified codepage.
*
* @param text
* The text to be converted.
* @param target
* The target codepage.
*
* @return The converted text.
*/
public static <T extends Text> T codePageConvert(T text, String target)
{
return codePageConvert(text, target, (String) null);
}
/**
* Convert the given text using the specified codepage, assuming the text is encoded with the given source
* codepage.
* <p>
* Note: FWD implementation: actually no change is performed on the parameter value, only the returned
* object's CP is configured so that further usage of it will use the desired target CP.
*
* @param text
* The text to be converted.
* @param target
* The target codepage.
* @param source
* The source codepage. If the text is a {@link longchar} instance, then the text's internal
* codepage is used.
*
* @return The converted text.
*/
public static <T extends Text> T codePageConvert(T text, String target, String source)
{
if (target == null)
{
target = I18nOps._getCPInternal();
}
String javaTcs = I18nOps.convmap2Java.get(target);
if (javaTcs == null)
{
ErrorManager.recordOrThrowError(912, target, I18nOps.CONVERSION_MAP_FILE);
// Code page attribute table for <code-page> was not found in <filename>.
return null;
}
boolean check11679 = true;
if (source == null)
{
if (text instanceof longchar)
{
source = ((longchar) text)._getCodePage();
}
if (source != null)
{
check11679 = false; // skip checking for error 11679 since the source is correct
}
else
{
source = I18nOps._getCPInternal();
}
}
String javaScs = I18nOps.convmap2Java.get(source);
if (javaScs == null)
{
ErrorManager.recordOrThrowError(912, source, I18nOps.CONVERSION_MAP_FILE);
// Code page attribute table for <code-page> was not found in <filename>.
return null;
}
if (check11679 && text instanceof longchar)
{
String crtCp = ((longchar) text)._getCodePage();
if (crtCp == null)
{
crtCp = I18nOps._getCPInternal();
}
if (!crtCp.equals(source))
{
ErrorManager.recordOrThrowError(11679);
// Mismatch between source-codepage argument and LONGCHAR codepage. (11679)
return null;
}
}
String value = text.isUnknown() ? null : text.value;
// TODO: execute a true CP conversion in order to detect errors?
Text ret;
Class<? extends Text> tClass = text.getClass();
if (tClass == character.class)
{
// the [character] type doesn't know its CP
ret = new character(value);
}
else if (tClass == clob.class)
{
// the [character] type knows its CP from the specific constructor
ret = new clob(value, target);
}
else // must be a 'pure' longchar
{
// for the [longchar] type the CP must be fixed before assigning the actual value
ret = new longchar();
((longchar) ret).fixCodePage(target);
ret.assign(value);
}
ret.setCaseSensitive(text.caseSens);
return (T) ret;
}
/**
* Convert the given text using the specified codepage, assuming the text
* is encoded with the given source codepage.
*
* @param text
* The text to be converted.
* @param target
* The target codepage.
* @param source
* The source codepage. If the text is a {@link longchar}
* instance, then the text's internal codepage is used.
*
* @return The converted text.
*/
public static <T extends Text> T codePageConvert(T text,
String target,
character source)
{
// validate source
String srcValue = source == null || source.isUnknown() ? (String) null : source.getValue();
return codePageConvert(text, target, srcValue);
}
/**
* Convert the given text using the specified codepage.
*
* @param text
* The text to be converted.
* @param target
* The target codepage.
*
* @return The converted text.
*/
public static <T extends Text> T codePageConvert(T text, character target)
{
// validate target
String tarValue = target == null || target.isUnknown() ? (String) null : target.getValue();
return codePageConvert(text,
tarValue,
text instanceof longchar ? ((longchar) text)._getCodePage() : (String) null);
}
/**
* Convert the given text using the specified codepage, assuming the text
* is encoded with the given source codepage.
*
* @param text
* The text to be converted.
* @param target
* The target codepage.
* @param source
* The source codepage. If the text is a {@link longchar}
* instance, then the text's internal codepage is used.
*
* @return The converted text.
*/
public static <T extends Text> T codePageConvert(T text,
character target,
String source)
{
// validate target
String tarValue = target == null || target.isUnknown() ? (String) null : target.getValue();
return codePageConvert(text, tarValue, source);
}
/**
* Convert the given text using the specified codepage, assuming the text
* is encoded with the given source codepage.
*
* @param text
* The text to be converted.
* @param target
* The target codepage.
* @param source
* The source codepage. If the text is a {@link longchar}
* instance, then the text's internal codepage is used.
*
* @return The converted text.
*/
public static <T extends Text> T codePageConvert(T text,
character target,
character source)
{
// validate target and source
String tarValue = target == null || target.isUnknown() ? (String) null : target.getValue();
String srcValue = source == null || source.isUnknown() ? (String) null : source.getValue();
return codePageConvert(text, tarValue, srcValue);
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
character operator,
Text op2,
character strength)
{
return compare(op1, operator, op2, strength, new character());
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
String operator,
Text op2,
character strength)
{
return compare(op1, new character(operator), op2, strength);
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
character operator,
Text op2,
String strength)
{
return compare(op1, operator, op2, new character(strength), new character());
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
String operator,
Text op2,
String strength)
{
return compare(op1,
new character(operator),
op2,
new character(strength));
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
String operator,
String op2,
String strength)
{
return compare(op1,
new character(operator),
new character(op2),
new character(strength));
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(String op1,
String operator,
String op2,
String strength)
{
return compare(new character(op1),
new character(operator),
new character(op2),
new character(strength));
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
* @param collation
* The used collation. When <code>null</code>, the default
* collation is used.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
character operator,
Text op2,
character strength,
character collation)
{
int op = getCompareOperator(operator);
int str = getStrength(strength);
if (op < 0 || str < 0)
{
return logical.UNKNOWN;
}
// TODO: validate collation, if not-null
return compare(op1, op, op2, str, collation.getValue());
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
* @param collation
* The used collation. When <code>null</code>, the default
* collation is used.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
String operator,
Text op2,
character strength,
character collation)
{
return compare(op1, new character(operator), op2, strength, collation);
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
* @param collation
* The used collation. When <code>null</code>, the default
* collation is used.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
character operator,
Text op2,
String strength,
character collation)
{
return compare(op1, operator, op2, new character(strength), collation);
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
* @param collation
* The used collation. When <code>null</code>, the default
* collation is used.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
String operator,
Text op2,
String strength,
character collation)
{
return compare(op1,
new character(operator),
op2,
new character(strength),
collation);
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
* @param collation
* The used collation. When <code>null</code>, the default
* collation is used.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
character operator,
Text op2,
character strength,
String collation)
{
character chcol = collation == null ? (character) null
: new character(collation);
return compare(op1, operator, op2, strength, chcol);
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
* @param collation
* The used collation. When <code>null</code>, the default
* collation is used.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
String operator,
Text op2,
character strength,
String collation)
{
return compare(op1, new character(operator), op2, strength, collation);
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
* @param collation
* The used collation. When <code>null</code>, the default
* collation is used.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
character operator,
Text op2,
String strength,
String collation)
{
return compare(op1, operator, op2, new character(strength), collation);
}
/**
* Compare the two texts using the given operator and the given text.
* The operator must evaluate to one of the following relational operators:
* <ul>
* <li> LT (or <)
* <li> LE (or <=)
* <li> EQ (or =)
* <li> GE (or >=)
* <li> GT (or >)
* <li> NE (or <>)
* <li> BEGINS
* <li> MATCHES
* </ul>
* The strength must evaluate to one of the given values:
* <ul>
* <li> RAW - compares the two strings using the numeric values in the
* current code page.
* <li> CASE-SENSITIVE - performs a case-sensitive comparison
* <li> CASE-INSENSITIVE - performs a case-insensitive comparison
* <li> CAPS - converts any lowercase letters in the two strings to
* uppercase letters, and then performs a raw comparison of the
* resulting strings.
* When neither string contains a wildcard character, this option
* behaves the same as the MATCHES operator.
* </ul>
*
* @param op1
* The first string to be compared.
* @param operator
* The operator to be used for this comparison. Must evaluate to
* one of the strings presented above.
* @param op2
* The second string to be compared.
* @param strength
* The used strength. Must evaluate to one of the strings
* presented above.
* @param collation
* The used collation. When <code>null</code>, the default
* collation is used.
*
* @return A {@link logical} value representing the result of the
* comparison, depending on the operator and strength used.
*/
public static logical compare(Text op1,
String operator,
Text op2,
String strength,
String collation)
{
return compare(op1,
new character(operator),
op2,
new character(strength),
collation);
}
/**
* Compares two character values with a runtime-specified strength and
* operator. This method will probably generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for conversion.
* The result should match up with the Progress equivalents in the ASCII
* range.
* <p>
* This method does not handle the case of user-specified collation tables
* as is possible in Progress.
* <p>
* In addition, the case-sensitive and case-insensitive strength options
* are likely to differ in some results since the current implementation
* really just implements a direct numeric comparison based on each
* UNICODE character's value (though case-insensitive support is
* provided but it just converts everything to the same case before
* implementing the 'raw' lexicographic comparison.
*
* @param op1
* The left operand.
* @param operator
* One of {@link TextOps#LT}, {@link TextOps#LE},
* {@link TextOps#GT}, {@link TextOps#GE},
* {@link TextOps#EQ} or {@link TextOps#NE}.
* @param op2
* The right operand.
* @param strength
* One of {@link TextOps#RAW}, {@link TextOps#CASE_SENSITIVE}
* or {@link TextOps#CASE_INSENSITIVE}.
*
* @return The result of the condition evaluation.
*/
public static logical compare(Text op1,
int operator,
Text op2,
int strength)
{
return compare(op1, operator, op2, strength, null);
}
/**
* Compares two character values with a runtime-specified strength and
* operator. This method will probably generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for conversion.
* The result should match up with the Progress equivalents in the ASCII
* range.
* <p>
* This method does not handle the case of user-specified collation tables
* as is possible in Progress.
* <p>
* In addition, the case-sensitive and case-insensitive strength options
* are likely to differ in some results since the current implementation
* really just implements a direct numeric comparison based on each
* UNICODE character's value (though case-insensitive support is
* provided but it just converts everything to the same case before
* implementing the 'raw' lexicographic comparison.
*
* @param op1
* The left operand.
* @param operator
* One of {@link TextOps#LT}, {@link TextOps#LE}, {@link TextOps#GT}, {@link TextOps#GE},
* {@link TextOps#EQ} or {@link TextOps#NE}.
* @param op2
* The right operand.
* @param strength
* One of {@link TextOps#RAW}, {@link TextOps#CASE_SENSITIVE}, {@link TextOps#CAPS}
* or {@link TextOps#CASE_INSENSITIVE}.
* @param collation
* The used collation. When {@code null}, the default collation is used.
*
* @return The result of the condition evaluation.
*/
public static logical compare(Text op1,
int operator,
Text op2,
int strength,
String collation)
{
// TODO: add collation support
// TODO: finish support for MATCHES and BEGINS operators
if (operator != LT && operator != LE && operator != GT &&
operator != GE && operator != EQ && operator != NE &&
operator != MATCHES && operator != BEGINS)
{
// unrecognized operator
return logical.UNKNOWN;
}
// unknown value logic
if (op1.isUnknown() && op2.isUnknown())
{
// both are unknown
if (operator == LE || operator == GE || operator == EQ)
{
return logical.of(true);
}
else
{
return logical.of(false);
}
}
else
{
// both are NOT unknown BUT...
if (op1.isUnknown() || op2.isUnknown())
{
// one or the other is unknown
if (operator == NE)
{
return logical.of(true);
}
else
{
return logical.of(false);
}
}
}
if (operator == BEGINS)
{
String oper1 = op1.getValue();
String oper2 = op2.getValue();
if (strength == CASE_INSENSITIVE || strength == CAPS)
{
oper1 = oper1.toUpperCase();
oper2 = oper2.toUpperCase();
}
return logical.of(oper1.startsWith(oper2));
}
else if (operator == MATCHES)
{
UnimplementedFeature.missing("COMPARE / MATCHES operator");
return logical.UNKNOWN;
}
int result;
// TODO: add support for the other strength types: PRIMARY, SECONDARY, TERTIARY, QUATERNARY
if (strength == RAW || strength == CASE_SENSITIVE)
{
// RAW and CASE_SENSITIVE are the same right now
result = op1.getValue().compareTo(op2.getValue());
}
else if (strength == CASE_INSENSITIVE || strength == CAPS)
{
result = op1.getValue().compareToIgnoreCase(op2.getValue());
}
else
{
// unrecognized strength
return logical.UNKNOWN;
}
boolean conclude = false;
switch (operator)
{
case LT:
conclude = (result < 0);
break;
case LE:
conclude = (result <= 0);
break;
case GT:
conclude = (result > 0);
break;
case GE:
conclude = (result >= 0);
break;
case EQ:
conclude = (result == 0);
break;
case NE:
conclude = (result != 0);
break;
}
return logical.of(conclude);
}
/**
* Implements the <code>+</code> concatenation operator. If any operand is the
* <code>unknown value</code>, the result is <code>unknown value</code>. If any operand is
* case-sensitive, the result will be case-sensitive. <code>character</code> operands in
* the leftmost position will not be truncated at any embedded <code>null</code> char
* but all other <code>character</code> operands do in fact get silently truncated. This
* is how it works in the 4GL! Since there is no known way to get an embedded
* <code>null</code> into a <code>longchar</code> or into 4GL string literals, no special
* <code>null</code> handling is provided for those types.
* <p>
* It is valid to pass a mixture of <code>BaseDataType</code> and other object types to this
* method. Types that are not <code>character</code> or <code>longchar</code> will not affect
* the case-sensitivity or unknown status of the result. These other types will have the
* result of <code>toString()</code> inserted into the returned value.
* <p>
* TODO: this should return Text, but for now we let it return character, as this can be used
* with lots of builtin functions/methods which expect a character parameter, not a Text
* parameter.
*
* @param list
* The operands. In the 4GL, only <code>character</code>, <code>longchar</code>
* and string literals are possible types. For this reason, converted code would
* not be expected to pass anything else.
*
* @return The result of the operator.
*/
public static character concat(Object ... list)
{
StringBuilder sb = new StringBuilder();
boolean caseSens = false;
boolean first = true;
boolean javaSpacifyNull = false;
for (Object txt : list)
{
inner:
while (true)
{
if (txt instanceof String)
{
javaSpacifyNull = javaSpacifyNull || ((String) txt).indexOf('\0') >= 0;
sb.append((String) txt);
break inner;
}
if (txt instanceof Text)
{
Text op = (Text) txt;
// any character or longchar operand that is unknown causes the entire
// result to be unknown
if (op.isUnknown())
{
return new character();
}
// any character or longchar operand that is case-sensitive causes the
// entire result to be case-sensitive
caseSens = (caseSens || op.isCaseSensitive());
if (txt instanceof character && !first)
{
// the leftmost operand does not truncate at the embedded null, but all
// subsequent operands do (the safeValue() call truncates at the embedded null
// char)
txt = ((character) op).safeValue();
// null was already removed, do not alter flag
continue inner;
}
// first character op and any longchar op (no known null handling is needed)
txt = op.getValue();
// inherit from the actual instance
javaSpacifyNull = javaSpacifyNull || op.isJavaSpacifyNull();
continue inner;
}
if (txt instanceof BaseDataType)
{
txt = new character((BaseDataType) txt);
continue inner;
}
txt = txt.toString();
}
first = false;
}
return new character(sb.toString(), caseSens, javaSpacifyNull);
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static <T extends Text> T entry(NumberType i,
T list,
character delimit)
throws ErrorConditionException
{
if (i.isUnknown() || list.isUnknown() || delimit.isUnknown())
{
return (T) instantiateUnknown(list.getClass());
}
return (T) entryImpl(list.getClass(),
i.intValue(),
list.getValue(),
safeDelimiter(delimit.getValue()));
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static <T extends Text> T entry(double i, T list, character delimit)
throws ErrorConditionException
{
if (list.isUnknown() || delimit.isUnknown())
{
return (T) instantiateUnknown(list.getClass());
}
return (T) entryImpl(list.getClass(),
(int) i,
list.getValue(),
safeDelimiter(delimit.getValue()));
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static character entry(NumberType i, String list, character delimit)
throws ErrorConditionException
{
if (i.isUnknown() || delimit.isUnknown())
{
return new character();
}
return entryImpl(character.class,
i.intValue(),
list,
safeDelimiter(delimit.getValue()));
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static <T extends Text> T entry(NumberType i,
T list,
String delimit)
throws ErrorConditionException
{
if (i.isUnknown() || list.isUnknown())
{
return (T) instantiateUnknown(list.getClass());
}
return (T) entryImpl(list.getClass(), i.intValue(),
list.getValue(),
safeDelimiter(delimit));
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static <T extends Text> T entry(double i, T list, String delimit)
throws ErrorConditionException
{
if (list.isUnknown())
{
return (T) instantiateUnknown(list.getClass());
}
return (T) entryImpl(list.getClass(),
(int) i,
list.getValue(),
safeDelimiter(delimit));
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static character entry(NumberType i, String list, String delimit)
throws ErrorConditionException
{
if (i.isUnknown())
{
return new character();
}
return entryImpl(character.class,
i.intValue(),
list,
safeDelimiter(delimit));
}
/**
* Returns the specified 1-based indexed comma-delimited entry in the
* passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static <T extends Text> T entry(NumberType i, T list)
throws ErrorConditionException
{
if (i.isUnknown() || list.isUnknown())
{
return (T) instantiateUnknown(list.getClass());
}
return (T) entryImpl(list.getClass(),
i.intValue(),
list.getValue(),
',');
}
/**
* Returns the specified 1-based indexed comma-delimited entry in the
* passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static character entry(NumberType i, String list)
throws ErrorConditionException
{
if (i.isUnknown())
{
return new character();
}
return entryImpl(character.class, i.intValue(), list, ',');
}
/**
* Returns the specified 1-based indexed comma-delimited entry in the
* passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static <T extends Text> T entry(double i, T list)
throws ErrorConditionException
{
if (list.isUnknown())
{
return (T) instantiateUnknown(list.getClass());
}
return (T) entryImpl(list.getClass(), (int) i, list.getValue(), ',');
}
/**
* Returns the specified 1-based indexed comma-delimited entry in the
* passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static character entry(double i, String list)
throws ErrorConditionException
{
return entry((int) i, list, ',');
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static character entry(double i, String list, String delimit)
throws ErrorConditionException
{
return entryImpl(character.class,
(int) i,
list,
safeDelimiter(delimit));
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param i
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static character entry(double i, String list, char delimit)
throws ErrorConditionException
{
return entryImpl(character.class, (int) i, list, delimit);
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(Text src, Text target, NumberType i)
{
if (src.isUnknown() || target.isUnknown() || i.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src.getValue(),
target.getValue(),
i.doubleValue(),
src.isCaseSensitive() || target.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(String src, Text target, NumberType i)
{
if (target.isUnknown() || i.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src,
target.getValue(),
i.doubleValue(),
target.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(Text src, String target, NumberType i)
{
if (src.isUnknown() || i.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src.getValue(),
target,
i.doubleValue(),
src.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(String src, String target, NumberType i)
{
if (i.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src, target, i.doubleValue(), false);
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(Text src, Text target, double i)
{
if (src.isUnknown() || target.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src.getValue(),
target.getValue(),
i,
src.isCaseSensitive() || target.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(String src, Text target, double i)
{
if (target.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src, target.getValue(), i, target.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(Text src, String target, double i)
{
if (src.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src.getValue(), target, i, src.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(Text src, Text target)
{
if (src.isUnknown() || target.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src.getValue(),
target.getValue(),
1,
src.isCaseSensitive() || target.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(String src, Text target)
{
if (target.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src, target.getValue(), 1, target.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer indexOf(Text src, String target)
{
if (src.isUnknown())
{
return integer.UNKNOWN;
}
return indexOf(src.getValue(), target, 1, src.isCaseSensitive());
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param ii
* The (1-based) starting index from which to search.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found.
*/
public static integer indexOf(String src, String target, double ii)
{
return indexOf(src, target, ii, false);
}
/**
* Returns the 1-based index of one character expression within another
* given character expression.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found.
*/
public static integer indexOf(String src, String target)
{
return indexOf(src, target, 1, false);
}
/**
* Returns the 1-based index of one character expression within another
* given character expression based on a known starting point for the
* search.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param ii
* The (1-based) starting index from which to search.
* @param caseSensitive
* <code>true</code> if case must be considered in the search;
* <code>false</code> if the search is case insensitive.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found.
*/
public static integer indexOf(String src,
String target,
double ii,
boolean caseSensitive)
{
int i = (int) ii;
if (src.length() == 0 || target.length() == 0 || i < 1)
{
return integer.of(0);
}
if (!caseSensitive)
{
// TODO: optimisation: in case of case insensitive lookup, we should convert to lowercase
// only the characters that are really compared from both strings
src = src.toLowerCase();
target = target.toLowerCase();
}
return integer.of(src.indexOf(target, i - 1) + 1);
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning) starting at a specified
* starting index.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(Text src, Text target, NumberType i)
{
if (src.isUnknown() || target.isUnknown() || i.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src.getValue(), target.getValue(), i.intValue());
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning) starting at a specified
* starting index.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(String src, Text target, NumberType i)
{
if (target.isUnknown() || i.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src, target.getValue(), i.intValue());
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning) starting at a specified
* starting index.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(Text src, String target, NumberType i)
{
if (src.isUnknown() || i.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src.getValue(), target, i.intValue());
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning) starting at a specified
* starting index.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(String src, String target, NumberType i)
{
if (i.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src, target, i.intValue());
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning) starting at a specified
* starting index.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(Text src, Text target, double i)
{
if (src.isUnknown() || target.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src.getValue(), target.getValue(), (int) i);
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning) starting at a specified
* starting index.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(String src, Text target, double i)
{
if (target.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src, target.getValue(), (int) i);
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning) starting at a specified
* starting index.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param i
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(Text src, String target, double i)
{
if (src.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src.getValue(), target, (int) i);
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning).
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(Text src, Text target)
{
if (src.isUnknown() || target.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src.getValue(), target.getValue());
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning).
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(String src, Text target)
{
if (target.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src, target.getValue());
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning).
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found or <code>unknown value</code>
* if any input is <code>unknown value</code>.
*/
public static integer lastIndexOf(Text src, String target)
{
if (src.isUnknown())
{
return integer.UNKNOWN;
}
return lastIndexOf(src.getValue(), target);
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning) starting at a specified
* starting index.
* <p>
* If an index is specified, this is a "to" index, so the whole pattern should
* fit in the first i characters.
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
* @param ii
* The starting index to search from.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found.
*/
public static integer lastIndexOf(String src, String target, double ii)
{
int i = (int) ii;
int targetLng = target.length();
if (src.length() == 0 || targetLng == 0 || i < 1)
{
return integer.of(0);
}
// there is a mismatch in semantics between 4GL r-index and Java lastIndexOf
// In 4GL: i is a "to" index. Finding "ab" in "ab" is possible only for i = 2 (1-based index)
// In Java: i is a "from" index. Finding "ab" in "ab" is possible for i = 0 and i = 1 (0-based index)
int lastIndex = i - targetLng;
if (lastIndex < 0)
{
return integer.of(0);
}
return integer.of(src.lastIndexOf(target, lastIndex) + 1);
}
/**
* Returns the last 1-based index of one character expression within
* another given character expression (the search starts at the end
* of the string and moves toward the beginning).
*
* @param src
* The character expression to search in.
* @param target
* The character expression to search for.
*
* @return The 1-based index in the source at which the target is found
* or 0 if the target is not found.
*/
public static integer lastIndexOf(String src, String target)
{
return lastIndexOf(src, target, src.length() + 1);
}
/**
* Trims the list of contiguous trim characters from the left of an input
* string.
* <p>
* Trim operations are performed based on the case-sensitivity of only
* the input parameter, the trim character list case-sensitivity is
* ignored. Likewise, if the input parameter is case-sensitive, then
* the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T leftTrim(T input, character list)
{
if (input.isUnknown() || list.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) leftTrimImpl(input.getClass(),
input.getValue(),
list.getValue(),
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Trims the list of contiguous trim characters from the left of an input
* string.
* <p>
* Trim operations are performed based on the case-sensitivity of the
* the input parameter. If the input parameter is case-sensitive, then
* the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T leftTrim(T input, String list)
{
if (input.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) leftTrimImpl(input.getClass(),
input.getValue(),
list,
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Trims the list of contiguous trim characters from the left of an input
* string.
* <p>
* Trim operations are performed case-insensitively.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static character leftTrim(String input, character list)
{
if (list.isUnknown())
{
return new character();
}
return leftTrimImpl(character.class, input, list.getValue(), false, null, true);
}
/**
* Trims any contiguous space, tab, line feed and carriage return from the
* left of an input string.
* <p>
* Trim operations are performed based on the case-sensitivity of the
* input parameter. Likewise, if the input parameter is case-sensitive,
* then the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T leftTrim(T input)
{
if (input.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) leftTrimImpl(input.getClass(),
input.getValue(),
null,
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Trims the list of contiguous trim characters from the left of an input
* string. If the list of trim characters is <code>null</code>
* the method defaults to space, tab, line feed and carriage return.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or <code>null</code> to
* use the default list.
* @param caseSens
* Specifies the case-sensitivity of the trim operation AND of
* the returned instance.
*
* @return The trimmed result.
*/
public static character leftTrim(String input,
String list,
boolean caseSens)
{
return leftTrimImpl(character.class,
input,
list,
caseSens,
null,
true);
}
/**
* Trims the list of contiguous trim characters from the left of an input
* string. If the list of trim characters is <code>null</code>
* the method defaults to space, tab, line feed and carriage return.
* <p>
* Trim operations and the returned instance will be case-insensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or <code>null</code> to
* use the default list.
*
* @return The trimmed result.
*/
public static character leftTrim(String input, String list)
{
return leftTrim(input, list, false);
}
/**
* Trims any contiguous space, tab, line feed and carriage return from the
* left of an input string.
* <p>
* Trim operations and the returned instance will be case-insensitive.
*
* @param input
* The string to trim.
*
* @return The trimmed result.
*/
public static character leftTrim(String input)
{
return leftTrim(input, (String) null);
}
/**
* Returns the length of the given <code>character</code> instance in
* characters.
*
* @param c
* The instance to test.
*
* @return The number of characters in the given instance or -1 if the
* instance is <code>unknown value</code>.
*/
public static int lengthOf(Text c)
{
if (c.isUnknown())
return -1;
return c.lengthInt();
}
/**
* Returns the length of the given <code>character</code> instance in
* characters.
*
* @param c
* The instance to test.
*
* @return The number of characters in the given instance
* <code>unknown value</code> if the instance is
* <code>unknown value</code>.
*/
public static integer length(Text c)
{
if (c.isUnknown())
return integer.UNKNOWN;
return integer.of(c.lengthInt());
}
/**
* Returns the length of the given <code>character</code> instance, using the type parameter
* to determine the measurement.
*
* @param c
* The instance to test.
* @param type
* The measurement mode.
* "character" is supposed to be the default measurement unit
* for the LENGTH built-in function (when type is null)
*
* @return The length of the given text
*/
public static integer length(Text c, character type)
{
if (type == null)
{
return TextOps.length(c.getValue()); // calling directly the dedicated method
}
if (type.isUnknown())
{
return integer.UNKNOWN;
}
return length(c, type.toStringMessage());
}
/**
* Returns the length of the given <code>character</code> instance, using the type parameter
* to determine the measurement.
*
* @param c
* The instance to test.
* @param type
* The measurement mode.
* "character" is supposed to be the default measurement unit
* for the LENGTH built-in function (when type is null)
*
* @return The length of the given text
*/
public static integer length(Text c, String type)
{
if (c == null || c.isUnknown())
{
return integer.UNKNOWN;
}
if (type == null)
{
return TextOps.length(c.getValue()); // calling directly the dedicated method
}
return TextOps.length(c.getValue(), type);
}
/**
* Returns the length of the given <code>character</code> instance, using the type parameter
* to determine the measurement.
*
* @param c
* The instance to test.
* @param type
* The measurement mode.
* "character" is supposed to be the default measurement unit
* for the LENGTH built-in function (when type is null)
*
* @return The length of the given text
*/
public static integer length(String c, character type)
{
if (type == null)
{
return TextOps.length(c); // calling directly the dedicated method
}
if (type.isUnknown())
{
return integer.UNKNOWN;
}
return TextOps.length(c, type.toStringMessage());
}
/**
* Returns the length of the given string instance in characters.
*
* @param c
* The instance to test.
*
* @return The number of characters in the given instance.
*/
public static integer length(String c)
{
return integer.of(c.length());
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be case-sensitive
* if either argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(character c, Text list, character delim)
{
if (c.isUnknown() || list.isUnknown() || delim.isUnknown())
{
return integer.UNKNOWN;
}
boolean cs = (c.isCaseSensitive() || list.isCaseSensitive());
return lookup(c.getValue(),
list.getValue(),
safeDelimiter(delim.getValue()),
cs);
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be case-sensitive
* if either argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(character c, String list, character delim)
{
if (c.isUnknown() || delim.isUnknown())
{
return integer.UNKNOWN;
}
return lookup(c.getValue(),
list,
safeDelimiter(delim.getValue()),
c.isCaseSensitive());
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be case-sensitive
* if either argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(String c, Text list, character delim)
{
if (list.isUnknown() || delim.isUnknown())
{
return integer.UNKNOWN;
}
return lookup(c,
list.getValue(),
safeDelimiter(delim.getValue()),
list.isCaseSensitive());
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be case-sensitive
* if either argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(character c, Text list, String delim)
{
if (c.isUnknown() || list.isUnknown())
{
return integer.UNKNOWN;
}
boolean cs = (c.isCaseSensitive() || list.isCaseSensitive());
return lookup(c.getValue(), list.getValue(), safeDelimiter(delim), cs);
}
/**
* Returns the 1-based index of a specified entry in the passed
* comma-delimited list. The search will be case-sensitive if either
* argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(character c, Text list)
{
if (c.isUnknown() || list.isUnknown())
{
return integer.UNKNOWN;
}
boolean cs = (c.isCaseSensitive() || list.isCaseSensitive());
return lookup(c.getValue(), list.getValue(), ',', cs);
}
/**
* Returns the 1-based index of a specified entry in the passed
* comma-delimited list. The search will be case-sensitive if either
* argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(character c, String list)
{
if (c.isUnknown())
{
return integer.UNKNOWN;
}
return lookup(c.getValue(), list, ',', c.isCaseSensitive());
}
/**
* Returns the 1-based index of a specified entry in the passed
* comma-delimited list. The search will be case-sensitive if either
* argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(NumberType c, String list)
{
if (c.isUnknown())
{
return integer.UNKNOWN;
}
return lookup(c.toStringMessage(), list, ',', false);
}
/**
* Returns the 1-based index of a specified entry in the passed
* comma-delimited list. The search will be case-sensitive if either
* argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(String c, Text list)
{
if (list.isUnknown())
{
return integer.UNKNOWN;
}
return lookup(c, list.getValue(), ',', list.isCaseSensitive());
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be done
* case-insensitively.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found.
*/
public static integer lookup(String c, String list, char delim)
{
return lookup(c, list, delim, false);
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
* @param cs
* <code>true</code> specifies the match is to be done with
* case-sensitivity.
*
* @return The 1-based index into the list or 0 if no match is found.
*/
public static integer lookup(String c, String list, char delim, boolean cs)
{
// 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
String[] array = splitInt(list, delim);
// only use up to the first instance of the delimiter in the search
// pattern
int idx = c.indexOf(delim);
String search = idx < 0 ? c : c.substring(0, idx);
// default return value
int index = 0;
// do our search
for (int i = 0; i < array.length; i++)
{
if (cs ? array[i].equals(search) : array[i].equalsIgnoreCase(search))
{
// match found! convert from 0-based to 1-based
index = i + 1;
break;
}
}
return integer.of(index);
}
/**
* Returns the 1-based index of a specified entry in the passed
* comma-delimited list. The search will be done case-insensitively.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(String c, String list)
{
return lookup(c, list, ',', false);
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be case-sensitive
* if either argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(String c, String list, String delim)
{
return lookup(c, list, safeDelimiter(delim), false);
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be case-sensitive
* if either argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(String c, Text list, String delim)
{
if (list.isUnknown())
{
return integer.UNKNOWN;
}
return lookup(c,
list.getValue(),
safeDelimiter(delim),
list.isCaseSensitive());
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be case-sensitive
* if either argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(character c, String list, String delim)
{
if (c.isUnknown())
{
return integer.UNKNOWN;
}
return lookup(c.getValue(),
list,
safeDelimiter(delim),
c.isCaseSensitive());
}
/**
* Returns the 1-based index of a specified entry in the passed
* list with a user-specified delimiter. The search will be case-sensitive
* if either argument is case-sensitive.
*
* @param c
* The entry to find in the list.
* @param list
* The delimited list of entries.
* @param delim
* The delimiter character used to parse the list into entries.
*
* @return The 1-based index into the list or 0 if no match is found or
* the <code>unknown value</code> if any of the inputs are
* <code>unknown value</code>.
*/
public static integer lookup(String c, String list, character delim)
{
if (delim.isUnknown())
{
return integer.UNKNOWN;
}
return lookup(c, list, safeDelimiter(delim.getValue()), false);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* basing case sensitivity on the left operand.
* <p>
* The Progress style matching pattern passed as a parameter is converted to a regular
* expression using {@link SQLHelper#convertToRegEx} during this method.
*
* @param op
* String to check for a match.
* @param pattern
* Progress style matching pattern.
* @param windows
* <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
* the backslash (\)is an escape character, like tilde (\).
*
* @return <code>true</code> if op matches the regular expression or <code>unknown</code>
* value if either operand is <code>unknown</code>.
*/
public static logical matches(Text op, character pattern, boolean windows)
{
if (op.isUnknown() || pattern.isUnknown())
{
return logical.UNKNOWN;
}
boolean caseSensitive = op.isCaseSensitive() || pattern.isCaseSensitive();
return matches(op.getValue(), pattern.getValue(), caseSensitive, windows);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* basing case sensitivity on the left operand.
* <p>
* The Progress style matching pattern passed as a parameter is converted to a regular
* expression using {@link SQLHelper#convertToRegEx} during this method.
*
* @param op
* String to check for a match.
* @param pattern
* Progress style matching pattern.
* @param windows
* <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
* the backslash (\)is an escape character, like tilde (\).
*
* @return <code>true</code> if op matches the regular expression or <code>unknown</code>
* value if the left operand is <code>unknown</code>.
*/
public static logical matches(Text op, String pattern, boolean windows)
{
if (op.isUnknown())
{
return logical.UNKNOWN;
}
return matches(op.getValue(), pattern, op.isCaseSensitive(), windows);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* case-insensitively.
* <p>
* The Progress style matching pattern passed as a parameter is converted to a regular
* expression using {@link SQLHelper#convertToRegEx} during this method.
*
* @param op
* String to check for a match.
* @param pattern
* Progress style matching pattern.
* @param windows
* <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
* the backslash (\)is an escape character, like tilde (\).
*
* @return <code>true</code> if op matches the regular expression or <code>unknown</code>
* value if the right operand is <code>unknown</code>.
*/
public static logical matches(String op, character pattern, boolean windows)
{
if (pattern.isUnknown())
{
return logical.UNKNOWN;
}
return matches(op, pattern.getValue(), pattern.isCaseSensitive(), windows);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* ignoring the case of the operand and the pattern.
* <p>
* The Progress style matching pattern passed as a parameter is converted to a regular
* expression using {@link SQLHelper#convertToRegEx} during this method.
*
* @param op
* String to check for a match.
* @param pattern
* Progress style matching pattern.
* @param windows
* <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
* the backslash (\)is an escape character, like tilde (\).
*
* @return <code>true</code> if op matches the regular expression.
*/
public static logical matches(String op, String pattern, boolean windows)
{
return matches(op, pattern, false, windows);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller,
* honoring the case sensitivity preference of the caller.
* <p>
* The Progress style matching pattern passed as a parameter is converted to a regular
* expression using {@link SQLHelper#convertToRegEx} during this method.
*
* @param op
* String to check for a match.
* @param pattern
* Progress style matching pattern.
* @param caseSens
* <code>true</code> if a case-sensitive comparison should be used.
* @param windows
* <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
* the backslash (\)is an escape character, like tilde (\).
*
* @return <code>true</code> if op matches the regular expression.
*/
public static logical matches(String op, String pattern, boolean caseSens, boolean windows)
{
String up = caseSens ? op : op.toUpperCase();
pattern = caseSens ? pattern : pattern.toUpperCase();
boolean result = matchImpl(up, SQLHelper.convertToRegEx(pattern, windows));
return logical.of(result);
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller.
* <p>
* This form of the method is unix-escape independent because Progress style matching pattern
* passed as a parameter is already transformed at conversion time to a regular expression.
* By default, in P4GL character operations are case insensitive.
*
* @param op
* String to check for a match.
* @param regExp
* The RegExp pattern as obtained using {@link SQLHelper#convertToRegEx} from a
* Progress style matching pattern known at conversion time.
*
* @return <code>true</code> if op matches the regular expression.
*/
public static logical matches(String op, String regExp)
{
op = op.toUpperCase();
regExp = regExp.toUpperCase();
return logical.of(matchImpl(op, regExp));
}
/**
* Tests if the first operand matches a regular expression that is provided by the caller.
* <p>
* This form of the method is unix-escape independent because Progress style matching pattern
* passed as a parameter is already transformed at conversion time to a regular expression.
*
* @param op
* String to check for a match.
* @param regExp
* The RegExp pattern as obtained using {@link SQLHelper#convertToRegEx} from a
* Progress style matching pattern known at conversion time.
*
* @return <code>true</code> if op matches the regular expression.
*/
public static logical matches(Text op, String regExp)
{
if (op.isUnknown())
{
return logical.UNKNOWN;
}
String toMatch = op.getValue();
if (!op.isCaseSensitive())
{
toMatch = toMatch.toUpperCase();
regExp = regExp.toUpperCase();
}
return logical.of(matchImpl(toMatch, regExp));
}
/**
* Tests if the current PROGRESS user is allowed in the list.
* Since in the P2J environment the security subsystem is based on the
* Security Manager, this call simply catches the overlooked CAN-DO call
* in the PROGRESS code.
*
* @param list
* comma-separated list of user IDs, possibly with ! and *
* modifiers.
*
* @return never
* @throws IllegalArgumentException
* to catch the untranslated CAN-DO call
*/
public static logical matchesList(Text list)
throws IllegalArgumentException
{
throw new IllegalArgumentException("untranslated CAN-DO call");
}
/**
* Tests if the current PROGRESS user is allowed in the list.
* Since in the P2J environment the security subsystem is based on the
* Security Manager, this call simply catches the overlooked CAN-DO call
* in the PROGRESS code.
*
* @param list
* comma-separated list of user IDs, possibly with ! and *
* modifiers.
*
* @return never
* @throws IllegalArgumentException
* to catch the untranslated CAN-DO call
*/
public static logical matchesList(String list)
throws IllegalArgumentException
{
throw new IllegalArgumentException("untranslated CAN-DO call");
}
/**
* Tests if a string specified with the "item" parameter is allowed in the
* comma-separated list of allowed items.
*
* @param list
* comma-separated list of allowed items, possibly with ! and *
* modifiers.
* @param item
* an item being tested
*
* @return <code>true</code> if item is allowed in the list
*/
public static logical matchesList(BaseDataType list, character item)
{
return matchesList(new character(list), item);
}
/**
* Tests if a string specified with the "item" parameter is allowed in the
* comma-separated list of allowed items.
*
* @param list
* comma-separated list of allowed items, possibly with ! and *
* modifiers.
* @param item
* an item being tested
*
* @return <code>true</code> if item is allowed in the list
*/
public static logical matchesList(Text list, character item)
{
if (list.isUnknown() || item.isUnknown())
return logical.UNKNOWN;
return matchesList(list.getValue(), item.getValue());
}
/**
* Tests if a string specified with the "item" parameter is allowed in the
* comma-separated list of allowed items.
*
* @param list
* comma-separated list of allowed items, possibly with ! and *
* modifiers.
* @param item
* an item being tested
*
* @return <code>true</code> if item is allowed in the list
*/
public static logical matchesList(BaseDataType list, String item)
{
return matchesList(new character(list), item);
}
/**
* Tests if a string specified with the "item" parameter is allowed in the
* comma-separated list of allowed items.
*
* @param list
* comma-separated list of allowed items, possibly with ! and *
* modifiers.
* @param item
* an item being tested
*
* @return <code>true</code> if item is allowed in the list
*/
public static logical matchesList(Text list, String item)
{
if (list.isUnknown())
return logical.UNKNOWN;
return matchesList(list.getValue(), item);
}
/**
* Tests if a string specified with the "item" parameter is allowed in the
* comma-separated list of allowed items.
*
* @param list
* comma-separated list of allowed items, possibly with ! and *
* modifiers.
* @param item
* an item being tested
*
* @return <code>true</code> if item is allowed in the list
*/
public static logical matchesList(String list, character item)
{
if (item.isUnknown())
return logical.UNKNOWN;
return matchesList(list, item.getValue());
}
/**
* Tests if a string specified with the "item" parameter is allowed in the
* comma-separated list of allowed items.
*
* @param list
* comma-separated list of allowed items, possibly with ! and *
* modifiers.
* @param item
* an item being tested
*
* @return <code>true</code> if item is allowed in the list
*/
public static logical matchesList(String list, String item)
{
boolean result = false;
// create the regular expression (with proper escapes)
String regex = null;
// 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
String[] array = splitInt(list, ',');
// checking item against every entry in the list until match is found
int idx = 0;
int i = 0;
boolean negation = false;
String piece = null;
// max char array is double from original string
char[] charBuff = new char[list.length() * 2];
int charBuffSize = 0;
char c = 0;
Pattern p = null;
Matcher m = null;
for (idx = 0; idx < array.length; idx ++)
{
// isolate negation (!) case
if (array[idx].length() > 0 && array[idx].charAt(0) == '!')
{
piece = array[idx].substring(1);
negation = true;
}
else
{
piece = array[idx];
negation = false;
}
// build a regex out of the list entry; only * is converted into .*
charBuffSize = 0;
for (i = 0; i < piece.length(); i ++)
{
c = piece.charAt(i);
if (c != '*')
{
// inline optimisation for method isFixupRegexChar()
switch (c)
{
case '(':
case ')':
case '.':
case '*':
case '[':
case ']':
case '^':
case '{':
case '}':
case '?':
case '+':
case '$':
case '|':
case '\\':
{
charBuff[charBuffSize++] = '\\';
}
}
charBuff[charBuffSize] = c;
}
else
{
// additional char to add
charBuff[charBuffSize++] = '.';
charBuff[charBuffSize] = '*';
}
// at least one char added
charBuffSize++;
}
regex = new String(charBuff, 0, charBuffSize);
// check the pattern cache first
synchronized (listPatternCache)
{
p = listPatternCache.get(regex);
}
if (p == null)
{
// compile regex as case-insensitive and apply it to the item
p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
synchronized (listPatternCache)
{
listPatternCache.put(regex, p);
}
}
m = p.matcher(item);
if (!m.matches())
{
continue;
}
// this is a matching entry from the list; produce the result
result = !negation;
break;
}
return logical.of(result);
}
/**
* Returns the largest instance (lexicographically) from the list. This
* list may not include <code>null</code> values. Since an instance
* is returned which is one of the references that was passed in, the
* case-sensitivity of the input will be the same on output.
*
* @param list
* The list of character instances to compare.
*
* @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 <T extends Text> T maximum(T ... list)
{
return (T) CompareOps.maximum(list);
}
/**
* Returns the smallest instance (lexicographically) from the list. This
* list may not include <code>null</code> values. Since an instance
* is returned which is one of the references that was passed in, the
* case-sensitivity of the input will be the same on output.
*
* @param list
* The list of character instances to compare.
*
* @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 <T extends Text> T minimum(T ... list)
{
return (T) CompareOps.minimum(list);
}
/**
* Get the normalized form of the given string, based on the specified
* Unicode normalization form:
* The <code>format</code> parameter must evaluate to one of the following
* strings:
* <ul>
* <li> NFD - Canonical Decomposition
* <li> NFC - Canonical Decomposition, followed by Canonical Composition
* <li> NFKD - Compatibility Decomposition
* <li> NFKC - Compatibility Decomposition, followed by Canonical Composition
* <li> NONE - Returns the source string unchanged
* </ul>
*
* @param text
* The text which needs to be normalized, a {@link character} or
* {@link longchar} instance.
* @param format
* The normalization format, one of the strings above.
*
* @return The normalized form.
*/
public static <T extends Text> T normalize(T text, String format)
{
return normalize(text, new character(format));
}
/**
* Get the normalized form of the given string, based on the specified
* Unicode normalization form:
* The <code>format</code> parameter must evaluate to one of the following
* strings:
* <ul>
* <li> NFD - Canonical Decomposition
* <li> NFC - Canonical Decomposition, followed by Canonical Composition
* <li> NFKD - Compatibility Decomposition
* <li> NFKC - Compatibility Decomposition, followed by Canonical Composition
* <li> NONE - Returns the source string unchanged
* </ul>
*
* @param text
* The text which needs to be normalized, a {@link character} or
* {@link longchar} instance.
* @param format
* The normalization format, one of the strings above.
*
* @return The normalized form.
*/
public static <T extends Text> T normalize(T text, character format)
{
// TODO: IMPLEMENT THIS
return text;
}
/**
* Returns the number of entries in the passed list.
* <p>
* TODO: is this method ever used? The return value is incorrect for unknown parameters. It
* should have a nullable Integer return type and return null.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The number of entries in the list or -1 if the list is the {@code unknown value}.
*/
@Deprecated
public static int numEntriesOf(Text list, character delimit)
{
if (list.isUnknown() || delimit.isUnknown())
{
return -1;
}
return numEntriesOf(list.getValue(), safeDelimiter(delimit.getValue()));
}
/**
* Returns the number of comma-delimited entries in the passed list.
* <p>
* TODO: is this method ever used? The return value is incorrect for unknown parameters. It
* should have a nullable Integer return type and return null.
*
* @param list
* The delimited list of entries.
*
* @return The number of entries in the list or -1 if the list is the {@code unknown value}.
*/
@Deprecated
public static int numEntriesOf(Text list)
{
if (list.isUnknown())
{
return -1;
}
return numEntriesOf(list.getValue(), ',');
}
/**
* Returns the number of entries in the passed list.
* <p>
* TODO: is this method ever used? The return value is incorrect for unknown parameters. It
* should have a nullable Integer return type and return null.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The number of entries in the list.
*/
@Deprecated
public static int numEntriesOf(String list, char delimit)
{
// 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
if (list == null || list.isEmpty())
{
return 0;
}
if (delimit == '\0')
{
// special case - any null chars part of the string are not used as delim
// TODO: 4GL has a weird behavior when doing NUM-ENTRIES(, "~000"), if the string actually contains
// one or more null characters.
return 1;
}
// convert string to array of chars
char[] srcChars = list.toCharArray();
int srcLen = list.length(); // length of src string
int arraySize = 0; // number of substrings
int i = 0; // used to loop inside chars array
int start = 0; // starting point for next substring
// first find out how much elements should be in array
for (; i < srcLen; i++)
{
if (srcChars[i] == delimit)
{
// record the length of the substring
arraySize++;
// starting point for next substring just after delimiter
start = i + 1;
}
}
// last section
if (i == srcLen && start <= i)
{
arraySize++;
}
return arraySize;
}
/**
* Returns the number of comma-delimited entries in the passed list.
* <p>
* TODO: is this method ever used? The return value is incorrect for unknown parameters. It
* should have a nullable Integer return type and return null.
*
* @param list
* The delimited list of entries.
*
* @return The number of entries in the list.
*/
@Deprecated
public static int numEntriesOf(String list)
{
return numEntriesOf(list, ',');
}
/**
* Returns the number of comma-delimited entries in the passed list.
*
* @param list
* The delimited list of entries.
*
* @return The number of entries in the list or the
* <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static integer numEntries(Text list)
{
if (list.isUnknown())
{
return integer.UNKNOWN;
}
return integer.of(numEntriesOf(list.getValue()));
}
/**
* Returns the number of entries in the passed list.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The number of entries in the list or the
* <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static integer numEntries(Text list, String delimit)
{
if (list.isUnknown() || delimit == null)
{
return integer.UNKNOWN;
}
return integer.of(numEntriesOf(list.getValue(), safeDelimiter(delimit)));
}
/**
* Returns the number of entries in the passed list.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The number of entries in the list or the
* <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static integer numEntries(String list, character delimit)
{
if (list == null || delimit.isUnknown())
{
return integer.UNKNOWN;
}
return integer.of(numEntriesOf(list, safeDelimiter(delimit.getValue())));
}
/**
* Returns the number of entries in the passed list.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The number of entries in the list or the
* <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static integer numEntries(String list, String delimit)
{
if (list == null || delimit == null)
{
return integer.UNKNOWN;
}
return integer.of(numEntriesOf(list, safeDelimiter(delimit)));
}
/**
* Returns the number of entries in the passed list.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The number of entries in the list or the
* <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
public static integer numEntries(Text list, character delimit)
{
if (list.isUnknown() || delimit.isUnknown())
{
return integer.UNKNOWN;
}
return integer.of(numEntriesOf(list.getValue(),
safeDelimiter(delimit.getValue())));
}
/**
* Returns the number of entries in the passed list.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The number of entries in the list.
*/
public static integer numEntries(String list, char delimit)
{
if (list == null)
{
return integer.UNKNOWN;
}
return integer.of(numEntriesOf(list, delimit));
}
/**
* Returns the number of comma-delimited entries in the passed list.
*
* @param list
* The delimited list of entries.
*
* @return The number of entries in the list.
*/
public static integer numEntries(String list)
{
if (list == null)
{
return integer.UNKNOWN;
}
return integer.of(numEntriesOf(list));
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
* <p>
* The search and the returned instance are case-sensitive if any of the
* inputs are case-sensitive.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code>.
*/
public static <T extends Text> T replaceAll(T src,
Text from,
Text to)
{
if (src.isUnknown() || from.isUnknown() || to.isUnknown())
{
return (T) instantiateUnknown(src.getClass());
}
boolean cs = (src.isCaseSensitive() ||
from.isCaseSensitive() ||
to.isCaseSensitive());
return (T) replaceAllImpl(src.getClass(),
src.getValue(),
from.getValue(),
to.getValue(),
cs,
getCodePage(src),
src.isJavaSpacifyNull(),
from.isJavaSpacifyNull(),
to.isJavaSpacifyNull());
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
* <p>
* The search and the returned instance are case-sensitive if any of the
* inputs are case-sensitive.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code>.
*/
public static <T extends Text> T replaceAll(T src,
Text from,
String to)
{
if (src.isUnknown() || from.isUnknown())
{
return (T) instantiateUnknown(src.getClass());
}
boolean cs = (src.isCaseSensitive() || from.isCaseSensitive());
return (T) replaceAllImpl(src.getClass(),
src.getValue(),
from.getValue(),
to,
cs,
getCodePage(src),
src.isJavaSpacifyNull(),
from.isJavaSpacifyNull(),
true);
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
* <p>
* The search and the returned instance are case-sensitive if any of the
* inputs are case-sensitive.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code>.
*/
public static <T extends Text> T replaceAll(T src,
String from,
Text to)
{
if (src.isUnknown() || to.isUnknown())
{
return (T) instantiateUnknown(src.getClass());
}
boolean cs = (src.isCaseSensitive() || to.isCaseSensitive());
return (T) replaceAllImpl(src.getClass(),
src.getValue(),
from,
to.getValue(),
cs,
getCodePage(src),
src.isJavaSpacifyNull(),
true,
to.isJavaSpacifyNull());
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
* <p>
* The search and the returned instance are case-sensitive if any of the
* inputs are case-sensitive.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code>.
*/
public static character replaceAll(String src,
Text from,
Text to)
{
if (from.isUnknown() || to.isUnknown())
{
return new character();
}
boolean cs = (from.isCaseSensitive() || to.isCaseSensitive());
return replaceAllImpl(character.class,
src,
from.getValue(),
to.getValue(),
cs,
null,
true,
from.isJavaSpacifyNull(),
to.isJavaSpacifyNull());
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
* <p>
* The search and the returned instance are case-sensitive if the
* <code>src</code> instance is case-sensitive.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
*
* @return The resulting string or <code>unknown value</code> if the
* <code>src</code> string is <code>unknown value</code>.
*/
public static <T extends Text> T replaceAll(T src,
String from,
String to)
{
if (src.isUnknown())
{
return (T) instantiateUnknown(src.getClass());
}
return (T) replaceAllImpl(src.getClass(),
src.getValue(),
from,
to,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull(),
true,
true);
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
* <p>
* The search and the returned instance are case-sensitive if the
* <code>from</code> instance is case-sensitive.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
*
* @return The resulting string or <code>unknown value</code> if the
* <code>from</code> string is <code>unknown value</code>.
*/
public static character replaceAll(String src, Text from, String to)
{
if (from.isUnknown())
{
return new character();
}
return replaceAllImpl(character.class,
src,
from.getValue(),
to,
from.isCaseSensitive(),
null,
true,
from.isJavaSpacifyNull(),
true);
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
* <p>
* The search and the returned instance are case-sensitive if the
* <code>to</code> instance is case-sensitive.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
*
* @return The resulting string or <code>unknown value</code> if the
* <code>to</code> string is <code>unknown value</code>.
*/
public static character replaceAll(String src, String from, Text to)
{
if (to.isUnknown())
{
return new character();
}
return replaceAllImpl(character.class,
src,
from,
to.getValue(),
to.isCaseSensitive(),
null,
true,
true,
to.isJavaSpacifyNull());
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
* <p>
* The search and the returned instance are always case-insensitive.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
*
* @return The resulting string or <code>unknown value</code> if the
* <code>src</code> string is <code>unknown value</code>.
*/
public static character replaceAll(String src, String from, String to)
{
return replaceAllImpl(character.class, src, from, to, false, null, true, true, true);
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
* @param cs
* Determines if a case-sensitive match should be performed AND
* the case-sensitivity of the returned instance.
*
* @return The resulting string.
*/
public static character replaceAll(String src,
String from,
String to,
boolean cs)
{
return replaceAllImpl(character.class, src, from, to, cs, null, true, true, true);
}
/**
* Trims the list of contiguous trim characters from the right of an input
* string.
* <p>
* Trim operations are performed based on the case-sensitivity of only
* the input parameter, the trim character list case-sensitivity is
* ignored. Likewise, if the input parameter is case-sensitive, then
* the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T rightTrim(T input, character list)
{
if (input.isUnknown() || list.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) rightTrimImpl(input.getClass(),
input.getValue(),
list.getValue(),
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Trims the list of contiguous trim characters from the right of an input
* string.
* <p>
* Trim operations are performed based on the case-sensitivity of the
* the input parameter. If the input parameter is case-sensitive, then
* the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T rightTrim(T input, String list)
{
if (input.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) rightTrimImpl(input.getClass(),
input.getValue(),
list,
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Right-trim and lower case the given input text. Right-trimming removes the default characters (space,
* tab, line feed, carriage return).
*
* @param input
* Input text.
*
* @return Adjusted form of the text.
*/
public static String rightTrimLower(String input)
{
return trim(input.toLowerCase(), null, false, false);
}
/**
* Trims any contiguous space, tab, line feed and carriage return from the
* right of an input string.
* <p>
* Trim operations are performed based on the case-sensitivity of the
* input parameter. Likewise, if the input parameter is case-sensitive,
* then the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T rightTrim(T input)
{
if (input.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) rightTrimImpl(input.getClass(),
input.getValue(),
null,
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Trims the list of contiguous trim characters from the right of an input
* string.
* <p>
* Trim operations are performed based case-insensitively.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static character rightTrim(String input, character list)
{
if (list.isUnknown())
{
return new character();
}
return rightTrimImpl(character.class, input, list.getValue(), false, null, true);
}
/**
* Trims the list of contiguous trim characters from the right of an input
* string. If the list of trim characters is <code>null</code>
* the method defaults to space, tab, line feed and carriage return.
* <p>
* Trim operations and the returned instance will be case-insensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or <code>null</code> to
* use the default list.
*
* @return The trimmed result.
*/
public static character rightTrim(String input, String list)
{
return rightTrimImpl(character.class, input, list, false, null, true);
}
/**
* Trims any contiguous space, tab, line feed and carriage return from the
* right of an input string.
* <p>
* Trim operations and the returned instance will be case-insensitive.
*
* @param input
* The string to trim.
*
* @return The trimmed result.
*/
public static character rightTrim(String input)
{
return rightTrim(input, (String) null);
}
/**
* Trims any contiguous space, tab, line feed and carriage return from the
* right of an input string.
* <p>
* Trim operations and the returned instance will be case-insensitive.
*
* @param input
* The string to trim.
*
* @return The trimmed result.
*/
public static String rightTrimNative(String input)
{
return trim(input, null, false, true);
}
/**
* Trims the list of contiguous trim characters from the right of an input
* string. If the list of trim characters is <code>null</code>
* the method defaults to space, tab, line feed and carriage return.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or <code>null</code> to
* use the default list.
* @param caseSens
* Specifies the case-sensitivity of the trim operation AND of
* the returned instance.
*
* @return The trimmed result.
*/
public static character rightTrim(String input,
String list,
boolean caseSens)
{
return rightTrimImpl(character.class, input, list, caseSens, null, true);
}
/**
* Perform positional replacement of '&n' specifiers in the format string
* <code>fmt</code>, using the array of <code>character</code> and/or
* <code>String</code> objects as the list of replacements. The 1-based
* index position of the '&n' specifiers (where n and range from 1 to
* 9) is converted to a 0-based index into the <code>replacements</code>
* array. Instances of '&&' yield a single '&' in the result.
* Any specifiers that do not correspond to an object in the array are
* replaced with an empty string.
*
* @param fmt
* The string specifying the text and format specifiers.
* @param replacements
* The list of replacements.
*
* @return The resulting string or <code>unknown value</code> if the
* <code>fmt</code> string is <code>unknown value</code>.
*/
public static <T extends Text> T substitute(T fmt, Object... replacements)
throws ErrorConditionException
{
if (fmt.isUnknown())
return (T) instantiateUnknown(fmt.getClass());
return (T) substituteImpl(fmt.getClass(), fmt.getValue(), replacements);
}
/**
* Perform positional replacement of '&n' specifiers in the format string
* <code>fmt</code>, using the array of <code>character</code> and/or
* <code>String</code> objects as the list of replacements. The 1-based
* index position of the '&n' specifiers (where n and range from 1 to
* 9) is converted to a 0-based index into the <code>replacements</code>
* array. Instances of '&&' yield a single '&' in the result.
* Any specifiers that do not correspond to an object in the array are
* replaced with an empty string.
*
* @param fmt
* The string specifying the text and format specifiers.
* @param replacements
* The list of replacements.
*
* @return The resulting string.
*/
public static character substitute(String fmt, Object... replacements)
throws ErrorConditionException
{
return substituteImpl(character.class, fmt, replacements);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
NumberType pos,
NumberType len,
String type)
{
if (src.isUnknown() || pos.isUnknown() || len.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos.intValue(),
len.intValue(),
type,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
NumberType pos,
NumberType len,
character type)
{
if (src.isUnknown() || pos.isUnknown() || len.isUnknown() || type.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos.intValue(),
len.intValue(),
type.getValue(),
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
int pos,
NumberType len,
String type)
{
if (src.isUnknown() || len.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos,
len.intValue(),
type,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
int pos,
NumberType len,
character type)
{
if (src.isUnknown() || len.isUnknown() || type.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos,
len.intValue(),
type.getValue(),
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
NumberType pos,
int len,
String type)
{
if (src.isUnknown() || pos.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos.intValue(),
len,
type,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
NumberType pos,
int len,
character type)
{
if (src.isUnknown() || pos.isUnknown() || type.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos.intValue(),
len,
type.getValue(),
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring or <code>unknown value</code> if the
* <code>src</code> is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
int pos,
int len,
String type)
{
if (src.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos,
len,
type,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring or <code>unknown value</code> if the
* <code>src</code> is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
int pos,
int len,
character type)
{
if (src.isUnknown() || type.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos,
len,
type.getValue(),
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length. Operates on characters rather than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
NumberType pos,
NumberType len)
{
if (src.isUnknown() || pos.isUnknown() || len.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos.intValue(),
len.intValue(),
null,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length. Operates on characters rather than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
int pos,
NumberType len)
{
if (src.isUnknown() || len.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos,
len.intValue(),
null,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length. Operates on characters rather than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src,
NumberType pos,
int len)
{
if (src.isUnknown() || pos.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos.intValue(),
len,
null,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length. Operates on characters rather than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
*
* @return The resulting substring or <code>unknown value</code> if the
* <code>src</code> is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src, int pos, int len)
{
if (src.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos,
len,
null,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* extending for the rest of the string. Operates on characters rather
* than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src, NumberType pos)
{
if (src.isUnknown() || pos.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos.intValue(),
-1,
null,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* extending for the rest of the string. Operates on characters rather
* than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
*
* @return The resulting substring or <code>unknown value</code> if the
* <code>src</code> is <code>unknown</code>.
*/
public static <T extends Text> T substring(T src, int pos)
{
if (src.isUnknown())
return (T) instantiateUnknown(src.getClass());
return (T) substringImpl(src.getClass(),
src.getValue(),
pos,
-1,
null,
src.isCaseSensitive(),
getCodePage(src),
src.isJavaSpacifyNull());
}
/**
* Returns the substring of the string at the given 1-based index and
* extending for the rest of the string. Operates on characters rather than
* bytes.
* <p>
* The returned instance is case-insensitive.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
*
* @return The resulting substring.
*/
public static character substring(String src, int pos)
{
return substringImpl(character.class, src, pos, -1, null, false, null, true);
}
/**
* Returns the substring of the string at the given 1-based index and
* extending for the rest of the string. Operates on characters rather than
* bytes.
* <p>
* The returned instance is case-insensitive.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
*
* @return The resulting substring.
*/
public static character substring(String src, NumberType pos)
{
if (src != null || pos == null || pos.isUnknown())
{
return new character();
}
return substringImpl(character.class, src, pos.intValue(), -1, null, false, null, true);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length. Operates on characters rather than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static character substring(String src,
NumberType pos,
NumberType len)
{
if (pos.isUnknown() || len.isUnknown())
return new character();
return substringImpl(character.class,
src,
pos.intValue(),
len.intValue(),
null,
false,
null,
true);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length. Operates on characters rather than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static character substring(String src,
NumberType pos,
int len)
{
if (pos.isUnknown())
return new character();
return substringImpl(character.class,
src,
pos.intValue(),
len,
null,
false,
null,
true);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length. Operates on characters rather than bytes.
* <p>
* Case-sensitivity of the returned instance is determined by the
* <code>src</code> instance case-sensitivity.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
*
* @return The resulting substring or <code>unknown value</code> if any
* input is <code>unknown</code>.
*/
public static character substring(String src,
int pos,
NumberType len)
{
if (len.isUnknown())
return new character();
return substringImpl(character.class,
src,
pos,
len.intValue(),
null,
false,
null,
true);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length. Operates on characters rather than bytes.
* <p>
* The returned instance is case-insensitive.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
*
* @return The resulting substring.
*/
public static character substring(String src, int pos, int len)
{
return substringImpl(character.class, src, pos, len, null, false, null, true);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* The returned instance is case-insensitive.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring.
*/
public static character substring(String src,
integer pos,
int len,
String type)
{
if (pos.isUnknown())
return instantiateUnknown(character.class);
return substringImpl(character.class, src, pos.intValue(), len, type, false, null, true);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
* <p>
* The returned instance is case-insensitive.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
*
* @return The resulting substring.
*/
public static character substring(String src,
int pos,
int len,
String type)
{
return substringImpl(character.class, src, pos, len, type, false, null, true);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
* @param caseSens
* Determines the case-sensitivity of the returned instance.
*
* @return The resulting substring.
*/
public static character substring(String src,
int pos,
int len,
String type,
boolean caseSens)
{
return substringImpl(character.class, src, pos, len, type, caseSens, null, true);
}
/**
* Returns a character instance that contains the lowercased version of
* the specified string. This may generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for case
* conversion. If this returned value is different than would be returned
* under Progress AND if that Progress-specific value is used in
* comparison with this returned value, then a difference in logic will be
* seen.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
* <p>
* If the operand is case-sensitive, the result will be case-sensitive.
*
* @param c
* The character instance to convert (cannot be
* <code>null</code>).
*
* @return The lowercased character instance (which should match up
* with the Progress equivalents in the ASCII range) or
* <code>unknown value</code> if the input string is
* <code>unknown value</code>.
*/
public static <T extends Text> T toLowerCase(T c)
{
if (c.isUnknown())
return (T) instantiateUnknown(c.getClass());
T res = (T) toLowerCaseImpl(c.getClass(), c.getValue(), getCodePage(c), c.isJavaSpacifyNull());
// honor case-sensitivity
res.setCaseSensitive(c.isCaseSensitive());
return res;
}
/**
* Returns a character instance that contains the lowercased version of
* the specified string. This may generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for case
* conversion. If this returned value is different than would be returned
* under Progress AND if that Progress-specific value is used in
* comparison with this returned value, then a difference in logic will be
* seen.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
* <p>
* The result will be case-insensitive.
*
* @param str
* The string to convert (cannot be <code>null</code>).
*
* @return The lowercased character instance which should match up
* with the Progress equivalents in the ASCII range.
*/
public static character toLowerCase(String str)
{
return toLowerCaseImpl(character.class, str, null, true);
}
/**
* Returns a character instance that contains the lowercased version of
* the specified string. This may generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for case
* conversion. If this returned value is different than would be returned
* under Progress AND if that Progress-specific value is used in
* comparison with this returned value, then a difference in logic will be
* seen.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
* <p>
* The result will be case-insensitive.
*
* @param c
* The text to convert (cannot be <code>null</code>).
*
* @return The lowercased character instance which should match up
* with the Progress equivalents in the ASCII range.
*/
public static Text toLowerCase(BaseDataType c)
{
if (c instanceof Text)
{
return toLowerCase((Text) c);
}
return toLowerCase(c.toStringMessage());
}
/**
* Returns a character instance that contains the uppercased version of
* the specified string. This may generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for case
* conversion. If this returned value is different than would be returned
* under Progress AND if that Progress-specific value is used in
* comparison with this returned value, then a difference in logic will be
* seen.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
* <p>
* If the operand is case-sensitive, the result will be case-sensitive.
*
* @param c
* The character instance to convert (cannot be
* <code>null</code>).
*
* @return The uppercased character instance (which should match up
* with the Progress equivalents in the ASCII range) or
* <code>unknown value</code> if the input string is
* <code>unknown value</code>.
*/
public static Text toUpperCase(BaseDataType c)
{
if (c instanceof Text)
{
return toUpperCase((Text) c);
}
return toUpperCase(c.toStringMessage());
}
/**
* Returns a character instance that contains the uppercased version of
* the specified string. This may generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for case
* conversion. If this returned value is different than would be returned
* under Progress AND if that Progress-specific value is used in
* comparison with this returned value, then a difference in logic will be
* seen.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
* <p>
* If the operand is case-sensitive, the result will be case-sensitive.
*
* @param c
* The character instance to convert (cannot be
* <code>null</code>).
*
* @return The uppercased character instance (which should match up
* with the Progress equivalents in the ASCII range) or
* <code>unknown value</code> if the input string is
* <code>unknown value</code>.
*/
public static <T extends Text> T toUpperCase(T c)
{
if (c.isUnknown())
return (T) instantiateUnknown(c.getClass());
T res = (T) toUpperCaseImpl(c.getClass(), c.getValue(), getCodePage(c), c.isJavaSpacifyNull());
// honor case-sensitivity
res.setCaseSensitive(c.isCaseSensitive());
return res;
}
/**
* Returns a character instance that contains the uppercased version of
* the specified string. This may generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for case
* conversion. If this returned value is different than would be returned
* under Progress AND if that Progress-specific value is used in
* comparison with this returned value, then a difference in logic will be
* seen.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
* <p>
*The result will be case-insensitive.
*
* @param str
* The string to convert (cannot be <code>null</code>).
*
* @return The uppercased character instance which should match up
* with the Progress equivalents in the ASCII range.
*/
public static character toUpperCase(String str)
{
return toUpperCaseImpl(character.class, str, null, true);
}
/**
* Returns a string instance that contains the uppercased version of
* the specified string. This may generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for case
* conversion. If this returned value is different than would be returned
* under Progress AND if that Progress-specific value is used in
* comparison with this returned value, then a difference in logic will be
* seen.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
* <p>
*The result will be case-insensitive.
*
* @param str
* The string to convert (cannot be <code>null</code>).
* @param runtimeString
* <code>true</code> if the target string is a runtime string,
* <code>false</code> if it is a hardcoded string into a source
* file.
*
* @return The uppercased character instance which should match up
* with the Progress equivalents in the ASCII range.
*/
public static String toUpperCase(String str, boolean runtimeString)
{
str = str.toUpperCase();
if (!runtimeString)
{
StringBuilder sb = new StringBuilder();
int end = str.length();
for (int i = 0; i < end; i++)
{
char current = str.charAt(i);
char next;
if (current == '\\')
{
i++;
next = str.charAt(i);
sb.append('\\');
// change back mistakenly uppercased special characters \t \r \n \b \f into a hardcoded string
// NOTE: this seems dangerous, what if the original text contained these uppercased letters,
// prefixed by backslash?
switch (next)
{
case 'T':
sb.append('t');
break;
case 'R':
sb.append('r');
break;
case 'N':
sb.append('n');
break;
case 'B':
sb.append('b');
break;
case 'F':
sb.append('f');
break;
default:
sb.append(next);
}
}
else
{
sb.append(current);
}
}
str = sb.toString();
}
return str;
}
/**
* Trims the list of contiguous trim characters from the left and right
* of an input string.
* <p>
* Trim operations are performed based on the case-sensitivity of only
* the input parameter, the trim character list case-sensitivity is
* ignored. Likewise, if the input parameter is case-sensitive, then
* the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T trim(T input, character list)
{
if (input.isUnknown() || list.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) trimImpl(input.getClass(),
input.getValue(),
list.getValue(),
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Trims the list of contiguous trim characters from the left and right
* of an input string.
* <p>
* Trim operations are performed based on the case-sensitivity of the
* input parameter. Likewise, if the input parameter is case-sensitive,
* then the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T trim(T input, String list)
{
if (input.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) trimImpl(input.getClass(),
input.getValue(),
list,
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Trims any contiguous space, tab, line feed and carriage return from the
* left and right of an input string.
* <p>
* Trim operations are performed based on the case-sensitivity of the
* input parameter. Likewise, if the input parameter is case-sensitive,
* then the returned instance will be case-sensitive.
*
* @param input
* The string to trim.
*
* @return The trimmed result.
*/
public static <T extends Text> T trim(T input)
{
if (input.isUnknown())
{
return (T) instantiateUnknown(input.getClass());
}
return (T) trimImpl(input.getClass(),
input.getValue(),
null,
input.isCaseSensitive(),
getCodePage(input),
input.isJavaSpacifyNull());
}
/**
* Trims the list of contiguous trim characters from the left and right
* of an input string.
* <p>
* Trim operations are performed case-insensitively and the returned
* result will be case-insensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim.
*
* @return The trimmed result.
*/
public static character trim(String input, character list)
{
if (list.isUnknown())
{
return new character();
}
return trimImpl(character.class, input, list.getValue(), false, null, true);
}
/**
* Trims the list of contiguous trim characters from the left and right
* of an input string. If the list of trim characters is <code>null</code>
* the method defaults to space, tab, line feed and carriage return.
* <p>
* Trim operations and the returned instance will be case-insensitive.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or <code>null</code> to
* use the default list.
*
* @return The trimmed result.
*/
public static character trim(String input, String list)
{
return trimImpl(character.class, input, list, false, null, true);
}
/**
* Trims any contiguous space, tab, line feed and carriage return from the
* left and right of an input string. Trim operations are performed
* case-insensitively.
* <p>
* Trim operations and the returned instance will be case-insensitive.
*
* @param input
* The string to trim.
*
* @return The trimmed result.
*/
public static character trim(String input)
{
return trimImpl(character.class, input, (String) null, false, null, true);
}
/**
* Trims the list of contiguous trim characters from the left and right
* of an input string. If the list of trim characters is <code>null</code>
* the method defaults to space, tab, line feed and carriage return.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or <code>null</code> to
* use the default list.
* @param caseSens
* Specifies the case-sensitivity of the trim operation AND of
* the returned instance.
*
* @return The trimmed result.
*/
public static character trim(String input, String list, boolean caseSens)
{
// trim left
String result = trim(input, list, true, caseSens);
// trim right
return new character(trim(result, list, false, caseSens), caseSens);
}
/**
* Trims the list of contiguous trim characters from the left or right of an input string. If
* the list of trim characters is {@code null} the method defaults to space.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or {@code null} to use the default list.
* @param left
* Specifies if the trim should occur on the left or right side of the string.
* @param caseSens
* If {@code true}, a case-sensitive trim is performed.
*
* @return The trimmed result.
*/
public static String trim(String input, String list, boolean left, boolean caseSens)
{
if (input == null)
{
return null;
}
// implement our default list
if (list == null)
{
list = " \t\n\r";
// using white-space doesn't require case insensitivity
caseSens = true;
}
else if (!StringHelper.hasAlphabet(list))
{
caseSens = true;
}
int len = input.length();
int llen = list.length();
int i;
for (i = left ? 0 : len - 1; left ? i < len : i >= 0;)
{
boolean found = false;
char c = input.charAt(i);
char cu = caseSens ? c : Character.toUpperCase(c); // in case sensitive cu is ignored, anyway
for (int j = 0; j < llen; j++)
{
char ch = list.charAt(j);
if (caseSens && ch == c)
{
found = true;
break;
}
else if (!caseSens)
{
char chu = Character.toUpperCase(ch);
if (chu == cu)
{
found = true;
break;
}
}
}
if (!found)
{
break;
}
if (left)
{
i++;
}
else
{
i--;
}
}
return left ? input.substring(i) : input.substring(0, i + 1);
}
/**
* Check if a text is empty or contains only spaces.
*
* In 4GL a string containing only empty spaces is equal with an empty string.
*
* @param value The text to check.
*
* @return True if the text is unknown, empty or contains only spaces.
*/
public static boolean isEmpty(Text value) {
return value == null || isEmpty(value.getValue());
}
/**
* Check if a text is empty or contains only spaces.
*
* In 4GL a string containing only empty spaces is equal with an empty string.
*
* @param value The string to check.
*
* @return True if the string is null, empty or contains only spaces.
*/
public static boolean isEmpty(String value) {
// only real spaces are considered (not tab, cr, lf)
if (value == null)
{
return true;
}
int idx = 0;
while (idx < value.length())
{
if (value.charAt(idx) != ' ')
{
return false;
}
idx = idx + 1;
}
return true;
}
/**
* Generate an error for a substring or overlay index that is negative.
*/
static void genNegativeIndexError()
{
String err = "Starting position for SUBSTRING, OVERLAY, etc. must" +
" be 1 or greater";
ErrorManager.recordOrThrowError(82, err);
}
/**
* Generate an error for a value out of range.
*
* @param idx
* The index that is out of range.
* @param list
* The list being indexed.
*/
static void genOutsideRangeError(int idx, String list)
{
String spec = "Entry %d is outside the range of list %s";
String err = String.format(spec, idx, list);
ErrorManager.recordOrThrowError(560, err, false);
}
/**
* Fixup the given character to be used in regular expression.
*
* @param ch
* The character to test.
*
* @return The resulting regular expression.
*/
static String fixupRegexChar(char ch)
{
String result = null;
if (isFixupRegexChar(ch))
{
result = new String(new char[] { '\\', ch });
}
else
{
result = new String(new char[] { ch });
}
return result;
}
/**
* Check if the given character is need to be fixed while using in regular expression.
*
* @param ch
* The character to test.
*
* @return <code>true</code> if the fuxup is required, <code>false</code> otherwise.
*/
static boolean isFixupRegexChar(char ch)
{
boolean result = false;
switch (ch)
{
case '(':
case ')':
case '.':
case '*':
case '[':
case ']':
case '^':
case '{':
case '}':
case '?':
case '+':
case '$':
case '|':
case '\\':
result = true;
break;
default:
result = false;
break;
}
return result;
}
/**
* Given a delimiter string, extract the first character and retrun it. If
* the string is empty, return a space character.
*
* @param delimiter
* Delimiter string
*
* @return Delimiter character as described above.
*/
static char safeDelimiter(String delimiter)
{
return (delimiter.length() == 0 ? ' ' : delimiter.charAt(0));
}
/**
* Convert the compare operator's string representation to its numeric representation, to be used by the
* {@link #compare(Text, int, Text, int)} method.
*
* @param compare
* The operator's string representation.
*
* @return The numeric representation.
*/
private static int getCompareOperator(character compare)
{
String op = compare.toJavaType();
if (op != null)
{
if ("=".equals(op) || "EQ".equalsIgnoreCase(op))
return EQ;
if ("<>".equals(op) || "NE".equalsIgnoreCase(op))
return NE;
if ("<".equals(op) || "LT".equalsIgnoreCase(op))
return LT;
if ("<=".equals(op) || "LE".equalsIgnoreCase(op))
return LE;
if (">".equals(op) || "GT".equalsIgnoreCase(op))
return GT;
if (">=".equals(op) || "GE".equalsIgnoreCase(op))
return GE;
if ("MATCHES".equalsIgnoreCase(op))
return MATCHES;
if ("BEGINS".equalsIgnoreCase(op))
return BEGINS;
}
ErrorManager.recordOrThrowError(9092, op == null ? "?" : op.toUpperCase(), "");
// Invalid COMPARE operator: <COMPARE operator> (9092)
return -1;
}
/**
* Convert the strength's string representation to its numeric
* representation, to be used by the {@link #compare(Text, int, Text, int)}
* method.
*
* @param strength
* The strength's string representation.
*
* @return The numeric representation.
*/
private static int getStrength(character strength)
{
String value = strength.toJavaType();
if (value != null)
{
if ("CASE-SENSITIVE".equalsIgnoreCase(value))
return CASE_SENSITIVE;
if ("CASE-INSENSITIVE".equalsIgnoreCase(value))
return CASE_INSENSITIVE;
if ("RAW".equalsIgnoreCase(value))
return RAW;
if ("CAPS".equalsIgnoreCase(value))
return CAPS;
}
ErrorManager.recordOrThrowError(9093, value == null ? "?" : value.toUpperCase(), "");
// Invalid COMPARE strength: <Compare strength> (9093)
return -1;
}
/**
* Returns the specified 1-based indexed entry in the passed list.
*
* @param ii
* The 1-based index into the list.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return The entry in the list or the <code>unknown value</code> if
* any of the inputs are <code>unknown value</code>.
*/
private static <T extends Text> T entryImpl(Class<T> cls,
double ii,
String list,
char delimit)
throws ErrorConditionException
{
if (list == null)
{
return instantiateUnknown(cls);
}
// 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) is not returned
// - the element at the given position
// - suffix (everything after the found element) is not returned
String[] array = splitInt(list, delimit, idx, true);
if (array == null)
{
genOutsideRangeError(i, list);
// this is the safest thing to return (it should be undone anyway)
return instantiate(cls, "");
}
return instantiate(cls, array[1]);
}
/**
* Returns the list of entries in the passed list.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return All entries in the list using the delimiter provided.
*/
public static String[] entries(String list, String delimit)
{
return entries(list, delimit, 0);
}
/**
* Returns the list of entries in the passed list.
*
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
* @param numEntries
* The number of entries to return, if less out of range error is thrown.
*
* @return All entries in the list using the delimiter provided.
*/
public static String[] entries(String list, String delimit, int numEntries)
{
// 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
String[] entries = splitInt(list, safeDelimiter(delimit));
if (numEntries > 0 && numEntries > entries.length) {
genOutsideRangeError(numEntries, list);
return new String[0];
}
return entries;
}
/**
* Returns the list of entries in the passed list.
*
* @param cls
* The class used to instantiate the list entries.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
*
* @return All entries in the list using the delimiter provided.
*/
public static <T extends Text> T[] entries(Class<T> cls,
String list,
String delimit)
throws ErrorConditionException {
return entries(cls, list, delimit, 0);
}
/**
* Returns the list of entries in the passed list.
*
* @param cls
* The class used to instantiate the list entries.
* @param list
* The delimited list of entries.
* @param delimit
* The delimiter character.
* @param numEntries
* The number of entries to return, if less out of range error is thrown.
*
* @return All entries in the list using the delimiter provided.
*/
public static <T extends Text> T[] entries(Class<T> cls,
String list,
String delimit,
int numEntries)
throws ErrorConditionException {
List<T> entries = Arrays.stream(entries(list, delimit, numEntries)).map(e -> instantiate(cls, e)).collect(Collectors.toList());
return entries.toArray((T[]) Array.newInstance(cls, entries.size()));
}
/**
* Create a new instance of the given {@link Text} subclass and set it to
* unknown.
*
* @param cls
* A {@link Text} subclass for which an unknown instance is
* needed.
*
* @return See above.
*/
private static <T extends Text> T instantiateUnknown(Class<T> cls)
{
try
{
return BaseDataTypeFactory.instantiate(cls);
}
catch (Exception e)
{
// TODO: should this be ignored ?
ErrorManager.handleStopException(e.getMessage());
}
return (T) Text.generateUnknown(cls);
}
/**
* Optimized replacement for JavaSE split function. Considering the count option as <code>-1</code>.
*
* @param src
* The source string to split.
* @param delim
* The delimiter char value to be used in source string splitting.
*
* @return The array of strings split from source.
*/
public static String[] splitInt(String src, char delim)
{
String[] result = null;
// convert string to array of chars
char[] srcChars = src.toCharArray();
// protection from some inexpected cases
if (srcChars == null || srcChars.length == 0 || delim == '\0')
{
result = new String[1];
result[0] = src;
return result;
}
int srcLen = src.length(); // length of src string
int arraySize = 0; // number of substrings
int[] subLengths = new int[srcLen + 1]; // lengths of substrings
int i = 0; // used to loop inside chars array
int start = 0; // starting point for next substring
// first find out how much elements should be in array
for (; i < srcLen; i++)
{
if (srcChars[i] == delim)
{
// record the length of the substring
subLengths[arraySize] = i - start;
arraySize++;
// starting point for next substring just after delimiter
start = i + 1;
}
}
// last section
if (i == srcLen && start <= i)
{
subLengths[arraySize] = i - start;
arraySize++;
}
// allocate new array and fill it with respective substrings
result = new String[arraySize];
if (arraySize == 1)
{
// this is the simplest case we can do in a fastest way
result[0] = src;
}
else
{
for (i = 0, start = 0; i < arraySize; i++)
{
result[i] = new String(srcChars, start, subLengths[i]);
start += subLengths[i] + 1;
}
}
return result;
}
/**
* Finds the entry at the position given by 'element', using the specified delimiter. This does not return
* all entries after splitting the string using the delimiter - instead, a 3-size array is returned, where:
* <ul>
* <li>
* First entry (position zero) is everything before the found element (may be null for first element).
* This will end with the delimiter.
* </li>
* <li>Second entry (position one) is the found element.</li>
* <li>
* Third entry (position two) is everything after the found element (may be null if the element is
* the last entry).
* </li>
* </ul>
*
* @param src
* The source string to split.
* @param delim
* The delimiter to use.
* @param element
* The zero-based element to find.
* @param onlyEntry
* Flag indicating just the entry needs to be returned on index 1 in the result.
*
* @return See above. {@code null} will be returned if the element can't be resolved.
*/
public static String[] splitInt(String src, char delim, int element, boolean onlyEntry)
{
if (element < 0)
{
return null;
}
if (src.isEmpty())
{
return element == 0 ? new String[] { null, "", null } : null;
}
if (delim == '\0')
{
if (element != 0)
{
return null;
}
int idx = src.indexOf(delim);
return new String[] { null, idx == -1 ? src : src.substring(0, idx), null };
}
// convert string to array of chars
char[] srcChars = src.toCharArray();
int srcLen = src.length(); // length of src string
int arraySize = 0; // number of substrings
int[] subLengths = new int[srcLen + 1]; // lengths of substrings
int i = 0; // used to loop inside chars array
int start = 0; // starting point for next substring
// first find out how much elements should be in array
for (; i < srcLen; i++)
{
if (srcChars[i] == delim)
{
// record the length of the substring
subLengths[arraySize] = i - start;
arraySize++;
// starting point for next substring just after delimiter
start = i + 1;
if (arraySize - 1 == element)
{
// we found the element, the suffix no longer needs to be split
i = srcLen;
break;
}
}
}
// last section
if (i == srcLen && start <= i)
{
subLengths[arraySize] = i - start;
arraySize++;
}
if (element >= arraySize)
{
return null;
}
int prefixLength = 0;
for (i = 0; i < element; i++)
{
prefixLength += subLengths[i] + 1;
}
String[] result = new String[3];
start = prefixLength;
if (onlyEntry)
{
result[0] = null;
result[1] = new String(srcChars, start, subLengths[element]);
result[2] = null;
}
else
{
result[0] = start == 0 ? null : new String(srcChars, 0, start);
result[1] = new String(srcChars, start, subLengths[element]);
start += subLengths[element] + 1;
result[2] = start >= srcChars.length
// be aware of an empty element as the last in the list
? (element < arraySize - 1 && srcChars[srcChars.length - 1] == delim ? "" : null)
: new String(srcChars, start, srcChars.length - start);
}
return result;
}
/**
* Create a new instance of the given {@link Text} subclass and set its
* value. If the <code>val</code> is null, the new instance will be set
* to unknown.
*
* @param cls
* A {@link Text} subclass for which a new instance is needed.
* @param val
* The value to set for the new instance.
*
* @return See above.
*/
private static <T extends Text> T instantiate(Class<T> cls, String val)
{
return instantiate(cls, val, null, true);
}
/**
* Create a new instance of the given {@link Text} subclass and set its
* value. If the <code>val</code> is null, the new instance will be set
* to unknown.
*
* @param cls
* A {@link Text} subclass for which a new instance is needed.
* @param val
* The value to set for the new instance.
* @param codepage
* The codepage to apply to the result; non-null only for {@link longchar} values.
* @param javaSpacifyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
*
* @return See above.
*/
private static <T extends Text> T instantiate(Class<T> cls,
String val,
String codepage,
boolean javaSpacifyNull)
{
if (val == null)
{
return instantiateUnknown(cls);
}
try
{
T t;
if (cls == character.class)
{
t = (T) new character(val, false, javaSpacifyNull);
}
else
{
t = BaseDataTypeFactory.instantiate(cls);
if (codepage != null)
{
((longchar) t).fixCodePage(codepage);
}
t.assign(val);
}
return t;
}
catch (Exception e)
{
// TODO: should this be ignored ?
ErrorManager.handleStopException(e.getMessage());
}
return (T) Text.generateUnknown(cls);
}
/**
* Trims the list of contiguous trim characters from the left of an input
* string. If the list of trim characters is <code>null</code>
* the method defaults to space, tab, line feed and carriage return.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or <code>null</code> to
* use the default list.
* @param caseSens
* Specifies the case-sensitivity of the trim operation AND of
* the returned instance.
* @param codepage
* The codepage to apply to the result; non-null only for {@link longchar} values.
* @param javaSpacifyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
*
* @return The trimmed result.
*/
private static <T extends Text> T leftTrimImpl(Class<T> cls,
String input,
String list,
boolean caseSens,
String codepage,
boolean javaSpacifyNull)
{
T res = (T) instantiate(cls, trim(input, list, true, caseSens), codepage, javaSpacifyNull);
res.setCaseSensitive(caseSens);
return res;
}
/**
* Searches for all matches to the <code>from</code> string in the
* <code>src</code> string and replaces these with the <code>to</code>
* string.
*
* @param src
* The string to operate upon.
* @param from
* The substring to search for.
* @param to
* The substring to replace when a match is found.
* @param cs
* Determines if a case-sensitive match should be performed AND
* the case-sensitivity of the returned instance.
* @param codepage
* The codepage to apply to the result; non-null only for {@link longchar} values.
* @param srcJavaSpacifcyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the src value.
* @param fromJavaSpacifcyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the from value.
* @param toJavaSpacifcyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the to value.
*
* @return The resulting string.
*/
private static <T extends Text> T replaceAllImpl(Class<T> cls,
String src,
String from,
String to,
boolean cs,
String codepage,
boolean srcJavaSpacifcyNull,
boolean fromJavaSpacifcyNull,
boolean toJavaSpacifcyNull)
{
int flen = from.length();
boolean csOrig = cs;
if (flen == 0)
{
ErrorManager.recordOrThrowError(2918, "Second parameter of REPLACE must have non-zero length", false);
return null;
}
int len = src.length();
if (flen > len)
{
T res = instantiate(cls, src, codepage, srcJavaSpacifcyNull);
res.setCaseSensitive(csOrig);
return res;
}
if (from.length() == 1 && to.length() == 1)
{
int cfrom = from.codePointAt(0);
int cto = to.codePointAt(0);
if (!(Character.isAlphabetic(cfrom) || Character.isAlphabetic(cto)))
{
src = src.replace(from.charAt(0), to.charAt(0));
T res = instantiate(cls, src, codepage, srcJavaSpacifcyNull);
res.setCaseSensitive(csOrig);
return res;
}
}
if (src.length() == from.length() && TextOps.getUpper(src).equals(TextOps.getUpper(from)))
{
T res = instantiate(cls, to, codepage, toJavaSpacifcyNull);
res.setCaseSensitive(csOrig);
return res;
}
if (!cs)
{
boolean allNonAlpha = true;
for (int i = 0; i < from.length(); i++)
{
if (Character.isAlphabetic(from.charAt(i)))
{
allNonAlpha = false;
break;
}
}
// if there is nothing to look for which would be case-insensitive, assume case-sensitive
cs = allNonAlpha;
}
StringBuilder sb = null;
// find the indexes on which the pattern appears in the source, and use these indexes for the result
List<Integer> indexes = kmpSearch(src, from, cs);
if (indexes != null)
{
sb = new StringBuilder(src.length() + (indexes.size() * to.length()));
int i1 = 0;
int i2 = indexes.get(0);
if (i2 != 0)
{
sb.append(src.subSequence(0, i2));
}
for (int i = 0; i < indexes.size(); i++)
{
sb.append(to);
i1 = indexes.get(i) + flen;
i2 = i == indexes.size() - 1 ? src.length() : indexes.get(i + 1);
sb.append(src.substring(i1, i2));
}
}
String resVal = sb == null ? src : sb.toString();
T res = instantiate(cls,
resVal,
codepage,
srcJavaSpacifcyNull || fromJavaSpacifcyNull || toJavaSpacifcyNull);
res.setCaseSensitive(csOrig);
return res;
}
/**
* Perform a KMP search of all occurrences of the pattern in the given source string, without the pattern
* overlapping.
*
* @param source
* The source string.
* @param pattern
* The pattern to search.
* @param cs
* Determines if a case-sensitive match should be performed.
*
* @return The list of all indexes where this pattern appears, or {@code null} if none found.
*/
private static List<Integer> kmpSearch(String source, String pattern, boolean cs)
{
if (pattern.isEmpty() || source.isEmpty())
{
return null;
}
int m = pattern.length();
// calculate the longest prefix which is also a suffix, for each substring
int len = 0;
int[] lps = new int[m];
int i = 1;
lps[0] = 0;
while (i < m)
{
if (StringHelper.charEquals(pattern.charAt(i), pattern.charAt(len), cs))
{
len = len + 1;
lps[i] = len;
i = i + 1;
}
else
{
if (len == 0)
{
lps[i++] = 0;
}
else
{
len = lps[len - 1];
}
}
}
List<Integer> found = null;
i = 0;
int j = 0;
int n = source.length();
while (i < n)
{
if (StringHelper.charEquals(pattern.charAt(j), source.charAt(i), cs))
{
j++;
i++;
}
if (j == m)
{
if (found == null)
{
found = new ArrayList<>(3);
}
found.add(i - j);
j = 0; // start over, as occurrences must not overlap
}
else if (i < n && !StringHelper.charEquals(pattern.charAt(j), source.charAt(i), cs))
{
if (j == 0)
{
i = i + 1;
}
else
{
j = lps[j - 1];
}
}
}
return found;
}
/**
* Trims the list of contiguous trim characters from the right of an input
* string. If the list of trim characters is <code>null</code>
* the method defaults to space, tab, line feed and carriage return.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or <code>null</code> to
* use the default list.
* @param caseSens
* Specifies the case-sensitivity of the trim operation AND of
* the returned instance.
* @param codepage
* The codepage to apply to the result; non-null only for {@link longchar} values.
* @param javaSpacifyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
*
* @return The trimmed result.
*/
private static <T extends Text> T rightTrimImpl(Class<T> cls,
String input,
String list,
boolean caseSens,
String codepage,
boolean javaSpacifyNull)
{
T res = instantiate(cls, trim(input, list, false, caseSens), codepage, javaSpacifyNull);
res.setCaseSensitive(caseSens);
return res;
}
/**
* Perform positional replacement of '&n' specifiers in the format string
* <code>fmt</code>, using the array of <code>character</code> and/or
* <code>String</code> objects as the list of replacements. The 1-based
* index position of the '&n' specifiers (where n and range from 1 to
* 9) is converted to a 0-based index into the <code>replacements</code>
* array. Instances of '&&' yield a single '&' in the result.
* Any specifiers that do not correspond to an object in the array are
* replaced with an empty string.
*
* @param fmt
* The string specifying the text and format specifiers.
* @param replacements
* The list of replacements.
*
* @return The resulting string.
*/
private static <T extends Text> T substituteImpl(Class<T> cls,
String fmt,
Object... replacements)
throws ErrorConditionException
{
StringBuilder sb = null;
int len = fmt.length();
for (int i = 0; i < len; i++)
{
char c = fmt.charAt(i);
if (c == '&')
{
if (sb == null)
{
sb = new StringBuilder(len);
sb.append(fmt.subSequence(0, i));
}
// ignore this char and look at the next one
i++;
c = fmt.charAt(i);
if (c >= '0' && c <= '9')
{
// convert this character into a 0-based index into the array
int idx = Character.digit(c, 10) - 1;
// check if this is a valid replacement index (note that 0 is
// always ignored/invalid), if not the result is an empty
// string since we don't make any replacement in that case
if (idx >= 0 && idx < replacements.length)
{
Object obj = replacements[idx];
if (obj != null)
{
// make a replacement if it is an object we recognize
if (obj instanceof String)
{
sb.append((String) obj);
}
else if (obj instanceof Boolean)
{
// this is required because P4GL uses yes/no instead of true/false
sb.append(logical.of(((Boolean) obj)));
}
else if (obj instanceof Text)
{
Text ch = (Text) obj;
sb.append(ch.isUnknown() ? "?" : ch.getValue());
}
else if (obj instanceof object)
{
object o = (object) obj;
if (o.isUnknown())
{
sb.append("?");
}
else if (o.ref instanceof LegacyEnum)
{
sb.append(((LegacyEnum) o.ref)._getValue());
}
else
{
sb.append(object.resourceId(o));
}
}
else if (obj instanceof BaseDataType)
{
BaseDataType bdt = (BaseDataType) obj;
sb.append(bdt.toStringMessage());
}
else
{
sb.append(obj.toString());
}
}
}
}
else if (c == '&')
{
sb.append(c);
}
else
{
String spec = "Invalid character \'%c\' following \'&\'" +
" in SUBSTITUTE";
ErrorManager.recordOrThrowError(2920, String.format(spec, c));
// this is the safest thing to return (it should be undone anyway)
return instantiate(cls, "");
}
}
else
{
if (sb != null)
{
// no match, put the char into the output
sb.append(c);
}
}
}
String res = sb == null ? fmt : sb.toString();
return instantiate(cls, res);
}
/**
* Returns the substring of the string at the given 1-based index and
* of the given length.
*
* @param src
* The string to operate upon.
* @param pos
* The 1-based index at which the substring starts.
* @param len
* The length of the substring to return. Use -1 for the rest of
* the string.
* @param type
* Specifies whether this substring operates on bytes or
* characters. At this time only "raw" and "character" are
* supported.
* @param caseSens
* Determines the case-sensitivity of the returned instance.
* @param codepage
* The codepage to apply to the result; non-null only for {@link longchar} values.
* @param javaSpacifyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
*
* @return The resulting substring.
*/
private static <T extends Text> T substringImpl(Class<T> cls,
String src,
int pos,
int len,
String type,
boolean caseSens,
String codepage,
boolean javaSpacifyNull)
{
String result = null;
// convert the Progress 1-based index into Java's 0-based index
int start = pos - 1;
if (pos < 1)
{
genNegativeIndexError();
return instantiate(cls, "", codepage, false);
}
if (type != null && "raw".equalsIgnoreCase(type))
{
// raw mode (operates on bytes)
byte[] buffer = src.getBytes();
if (start >= buffer.length)
{
// requesting data past the end of the string yields the empty string
result = "";
}
else
{
int end = start + len;
if (len == -1 || end >= buffer.length)
{
len = buffer.length - start;
}
result = new String(buffer, start, len);
}
}
else
{
// character mode
if (start >= src.length())
{
// requesting data past the end of the string yields the empty
// string
result = "";
}
else
{
int end = start + len;
if (len == -1 || end >= src.length())
{
// if the requested data is more than the rest of the string
// or if the rest of the string was explicitly requested via
// -1
result = src.substring(start);
}
else
{
// only return a portion
result = src.substring(start, end);
}
}
}
T res = instantiate(cls, result, codepage, javaSpacifyNull);
res.setCaseSensitive(caseSens);
return res;
}
/**
* Returns a character instance that contains the uppercased version of the specified string.
* This may generate incorrect results in a DBCS environment since it will use UNICODE as the
* basis for case conversion. If this returned value is different than would be returned under
* Progress AND if that Progress-specific value is used in comparison with this returned value,
* then a difference in logic will be seen.
* <p>
* This method does not handle the case of user-specified source and/or target codepages as is
* possible in Progress.
* <p>
* The result will be case-insensitive.
*
* @param str
* The string to convert (cannot be {@code null}).
* @param codepage
* The codepage to apply to the result; non-null only for {@link longchar} values.
* @param javaSpacifyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
*
* @return The uppercased character instance which should match up with the Progress
* equivalents in the ASCII range.
*/
private static <T extends Text> T toUpperCaseImpl(Class<T> cls,
String str,
String codepage,
boolean javaSpacifyNull)
{
// TODO: use the correct locale for switching cases
String upper = getUpper(str);
return instantiate(cls, upper, codepage, javaSpacifyNull);
}
/**
* Returns a character instance that contains the lowercased version of
* the specified string. This may generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for case
* conversion. If this returned value is different than would be returned
* under Progress AND if that Progress-specific value is used in
* comparison with this returned value, then a difference in logic will be
* seen.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
* <p>
* The result will be case-insensitive.
*
* @param str
* The string to convert (cannot be <code>null</code>).
* @param codepage
* The codepage to apply to the result; non-null only for {@link longchar} values.
* @param javaSpacifyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
*
* @return The lowercased character instance which should match up
* with the Progress equivalents in the ASCII range.
*/
private static <T extends Text> T toLowerCaseImpl(Class<T> cls,
String str,
String codepage,
boolean javaSpacifyNull)
{
// TODO: use the correct locale for switching cases
return instantiate(cls, str.toLowerCase(), codepage, javaSpacifyNull);
}
/**
* Trims the list of contiguous trim characters from the left and right of an input string. If
* the list of trim characters is {@code null} the method defaults to space.
*
* @param input
* The string to trim.
* @param list
* The list of characters to trim or {@code null} to use the default list.
* @param caseSens
* Specifies the case-sensitivity of the trim operation AND of the returned instance.
* @param codepage
* The codepage to apply to the result; non-null only for {@link longchar} values.
* @param javaSpacifyNull
* Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
*
* @return The trimmed result.
*/
private static <T extends Text> T trimImpl(Class<T> cls,
String input,
String list,
boolean caseSens,
String codepage,
boolean javaSpacifyNull)
{
// trim left
String result = trim(input, list, true, caseSens);
// trim right
T res = instantiate(cls, trim(result, list, false, caseSens), codepage, javaSpacifyNull);
res.setCaseSensitive(caseSens);
return res;
}
/**
* Returns the length of the given <code>character</code> instance, using the type parameter
* to determine the measurement.
* This method checks the <code>type</code> argument and decide to what method to delegate
* the actual implementation depending on the 2nd parameter.
*
* @param c
* The instance to test.
* @param type
* The measurement mode.
*
* @return The length of the given text
*/
private static integer length(String c, String type)
{
if (type == null)
{
return integer.UNKNOWN;
}
switch (type.toLowerCase())
{
case "raw":
return TextOps.byteLength(c);
case "character":
case "char":
return TextOps.length(c);
case "column":
return TextOps.columnLength(c);
default:
// the code below is correct, but causes dependency on [com.goldencode.p2j.ui] package
// if (LogicalTerminal.isChui())
// {
// // in ChUI mode returning the same value as length(..., 'CHAR')
// return TextOps.length(c);
// }
// in GUI mode this is an error:
ErrorManager.recordOrThrowError(
1186, "The data type argument value must be \"raw\", \"character\" or \"column\"", true);
return integer.UNKNOWN;
}
}
/**
* Check if the given text matches the pattern. This enforces the DOT to match anything.
*
* @param c
* The text to match.
* @param p
* The pattern to match.
*
* @return <code>true</code> if the text matches the pattern.
*/
private static boolean matchImpl(String c, String p)
{
p = "^" + p + "$"; // in 4GL, matches must always satisfy the entire text
Pattern p2;
// check the pattern cache first
synchronized (matchPatternCache)
{
p2 = matchPatternCache.get(p);
}
if (p2 == null)
{
// compile regex as case-insensitive and apply it to the item
p2 = Pattern.compile(p, Pattern.DOTALL);
synchronized (matchPatternCache)
{
matchPatternCache.put(p, p2);
}
}
return p2.matcher(c).find();
}
/**
* If the input is a {@link longchar}, get the codepage.
*
* @param input
* The source.
*
* @return The source codepage if this is a {@link longchar}, otherwise <code>null</code>.
*/
private static <T> String getCodePage(T input)
{
if (input instanceof longchar)
{
return ((longchar) input)._getCodePage();
}
return null;
}
}