character.java
/*
** Module : character.java
** Abstract : Progress 4GL compatible character object
**
** Copyright (c) 2004-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 GES 20050517 @21295 Created initial version with full support
** for all Progress character processing
** including the concatination, begins, matches
** operators (but not CONTAINS which is not
** needed at this time), all builtin functions,
** case-sensitivity and the unknown value.
** 002 GES 20050606 @21424 Minor additions for function support.
** 003 GES 20050621 @21440 Added an assign() method to modify this
** instance's state based on the result of
** an expression of compatible type.
** 004 GES 20050627 @21554 Added replace/overlay methods to provide the
** substring/overlay assignment type language
** statements.
** 005 GES 20050628 @21565 Fixed typo in method name and added unknown
** value processing to toStringMessage().
** 006 SVG 20050720 @21768 Cloning support for BaseDataType added.
** 007 NVS 20050728 @21908 Added implementation of PROGRESS builtin fun-
** ction CAN-DO as public static canDo, taking
** either one or two parameters. One parameter
** version is just a catcher for untranslated
** CAN-DO calls, throwing an exception. Two par-
** ameter version is a real code which is typic-
** ally used as a utility function.
** 008 GES 20050728 @21920 Added support for the _cbit() built-in
** function (see the testBitAt() method). Also
** changed the name of canDo to matchesList
** which is more generic and less like a
** Progress security-oriented function.
** 009 GES 20050815 @22118 Added Undoable interface support.
** 010 GES 20050825 @22253 Fixed toStringExport() to double up double
** quotes appearing inside the string.
** 011 GES 20050826 @22270 Implement case-sensitivity in output from
** specific functions and operators depending
** on the case-sensitivity of character operands
** or parameters.
** 012 GES 20050828 @22279 Added support for temporarily setting
** case-sensitivity with an automatic reset at
** the end of the scope in which the setting
** was made.
** 013 GES 20050829 @22284 Split assignment of configuration and state
** from normal assign(). deepAssign() is now
** provided to set both configuration and state
** at the same time.
** 014 GES 20050829 @22371 Added error condition support.
** 015 GES 20050907 @22594 Added "raw" type support to substring().
** 016 ECF 20051004 @22958 Implement hashCode() method. Required to be
** consistent with BaseDataType's implementation
** of equals() method.
** 017 GES 20051024 @23122 Added convertToSQLLike() method.
** 018 ECF 20051104 @23237 Added iterate() method to SensitivityResetter
** inner class. No-op implementation; required
** to satisfy expanded Finalizable interface.
** 019 GES 20060112 @23892 Added additional valueOf signatures for the
** literal forms of the string(), fill(),
** entry(), indexOf() and lastIndexOf()
** functions.
** 020 GES 20060114 @23903 Fixed substring (2nd parm was used wrong).
** 021 GES 20060206 @24297 Made compareTo() safe to process any object
** type (avoids ClassCastException).
** 022 GES 20060208 @24387 Expose a format string expansion helper as
** public and added a default format string
** getter.
** 023 GES 20060303 @24882 Don't escape the single quote char in Java
** strings.
** 024 GES 20060312 @24985 New variants for overlay and replace to
** handle all possible combos with primitives.
** 025 ECF 20060322 @25203 Honor case sensitivity in indexOf() methods.
** Added new indexOf variant to which all others
** delegate. Performs scan with proper case
** sensitivity setting.
** 026 GES 20060403 @25318 New variants for lookup().
** 027 GES 20060410 @25457 New variants for substring().
** 028 GES 20060417 @25552 Added default value generation.
** 029 GES 20060420 @25620 Added new signatures for right/leftTrim.
** 030 GES 20060422 @25650 Changed assignment style syntax backing
** methods to return "this" such that the result
** can be assigned back in the case of a
** database field. Added entry statement
** support.
** 031 GES 20060427 @25762 New signatures for trim().
** 032 GES 20060427 @25767 New signature for length().
** 033 GES 20060508 @26023 Fix for Progress pattern to regular
** expression conversion routine
** (convertToRegEx).
** 034 ECF 20060510 @26062 Introduced a runtime variant of static method
** convertToSQLLike(). This variant accepts a
** character parameter.
** 035 GES 20060517 @26192 Match new Finalizable interface requirement.
** 036 GES 20060518 @26280 Safety code for negative start indexes in
** substring.
** 037 GES 20060525 @26530 Fix for asc() which needed a 0-based index.
** 038 ECF 20060613 @27243 Fix for asc(). Replaced implementation to
** convert the low order character byte to an
** unsigned integer.
** 039 GES 20060724 @28158 Added a forced assign() abstract method.
** 040 GES 20060807 @28471 Moved to externalizable interface to optimize
** network performance.
** 041 ECF 20060812 @28586 Added methods needed for database server-side
** function support.
** 042 GES 20060904 @29214 Added helpers to space-ify strings with an
** embedded null character or null char escape
** sequence.
** 043 GES 20060905 @29222 Added embedded null processing for the
** toString() and concat() variants. Fixed
** null byte processing for asc().
** 044 GES 20060905 @29228 Added safeDuplicate().
** 045 GES 20060907 @29282 Fixed substring processing in "raw" mode.
** 046 GES 20060908 @29346 Fixed lookup() to return 0 instead of -1 on
** failure.
** 047 ECF 20060908 @29384 Made constructors which accept a J2SE String
** null-safe. A null string argument results in
** the instance being set to unknown value.
** 048 GES 20061109 @31050 Added formatLength().
** 049 ECF 20070306 @32284 Added case-sensitive variants of equals and
** hashCode. These are equalsCaseSensitively and
** caseSensitiveHashCode. Also modified existing
** hashCode method to be more consistent with
** equals. The previous implementation was not
** consistent with equals for case-insensitive
** character instances.
** 050 GES 20070321 @32504 Fix for some forms of lastIndexOf() which
** were starting the backwards search from the
** beginning of the source string instead of the
** end as it should have been. This affected
** all forms of the method which only have 2
** parameters. Also fixed replaceAll() which
** would fail to replace two back-to-back
** instances of the same target string (the
** first would be replaced and the first char
** of the second match would be dropped).
** 051 GES 20070926 @35243 Provide external control over 4GL compatible
** escape characters when processing non-runtime
** strings in progressSpacifyNull(). On Windows
** that code should not treat the backslash as
** an escape character.
** 052 GES 20080312 @37442 Fixes and documentation added to format
** string processing. Fill characters and
** padding were not always being handled
** properly. Data characters were not being
** copied into the right locations in the output
** string when non-trailing fill characters were
** present.
** 053 GES 20080414 @38012 Fixed chr/asc to support all non-DBCS
** charsets. Added fromExportString().
** 054 ECF 20080804 @39286 Added 2 new flavors of numEntries(). Needed
** to handle combinations of character and
** String parameters.
** 055 GES 20080909 @39766 Moved concat() to varargs. This makes it
** possible to merge multiple concat operator
** usage into a single method call, which is
** significantly more efficient and cleaner.
** 056 GES 20080911 @39788 Added assign(String) signature.
** 057 GES 20081002 @40033 Modified method names to shorten calling
** code.
** 058 SVL 20081224 @40985 valueOf(..) returns unknown character value
** if converted data is an unknown value.
** 059 ECF 20090212 @41296 Made class final.
** 060 GES 20090422 @41905 Converted to standard string formatting.
** 061 GES 20090424 @41968 Import change.
** 062 GES 20090706 @43106 Added signatures for overlay().
** 063 ECF 20090814 @43621 Fixed various methods which use delimiters. Added
** replaceEntry(double, character, String) variant.
** 064 ECF 20090816 @43659 Rolled back #059 (@41296).
** 065 GES 20110303 Moved maximum/minimum to varargs.
** 066 GES 20110422 Fixed case-sensitivity inheritance in the return
** value of concat().
** 067 SVL 20110622 Modified convertToSQLLike, added
** convertToSQLBegins, postprocessStringLiteral,
** toUpperCase(String, boolean) and
** substring(String, int).
** 068 GES 20110801 Modified substring() signatures to use NumberType.
** 069 CS 20121119 Implement the quoter built-in function.
** 070 CS 20121231 Added String overloads methods for quoter
** built-in function
** 071 EVL 20130107 Changing progressToJavaString() adding the version
** of the method that makes special processing for
** '\' path related strings for Windows-target case.
** 072 CA 20130123 Added c'tor which builds a new instance from a
** BaseDataType value, if possible. This should be
** used only when constructing an instance using the
** value returned by a DYNAMIC-FUNCTION call; 4GL
** does some special casting in this case, which is
** not implemented yet.
** 073 OM 20121023 Added support for TO-ROWID(STRING(row_id)).
** 074 AIL 20130202 Moved static methods common to character and
** longchar to TextOps class.
** 075 OM 20130212 Added support CONTAINS clause.
** 076 GES 20130322 Fixed the BDT c'tor javadoc.
** 077 OM 20130422 Added support for int64 data-type.
** 078 EVL 20140310 Change in progressToJavaString() to consider both "\\" and "\\\\"
** characters as single file separator for Windows target.
** 079 OM 20140917 Moved SQL string conversions functions to SQLHelper class.
** Added support for pipeline processing in small steps:
** ProgressParser, HQLConversion/Preprocessing, Java-String conversion.
** Updated integrated test-cases with Windows (non unix-escape) set.
** 080 OM 20141024 Fixed case-sensitivity of valueOf(Text), quoterWorker().
** 081 GES 20141003 Moved some helper methods to parent class and added forceBytes()
** to allow deliberate bypass of embedded null character protection.
** Improved or added explicit null character handling as needed.
** 082 OM 20141118 Fixed quoterWorker() regression.
** 083 GES 20150131 Corrected format string length calculation to account for unescaped
** tildes.
** 084 ECF 20150715 Replace StringBuffer with StringBuilder.
** 085 CA 20151222 Added instantiateDefaultExtent.
** 086 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 087 IAS 20160331 Fixed potential NPE.
** 088 GES 20160504 Added javaRuntimeToProgressSourceString().
** 089 CA 20160627 Reworked undoable support - the undoables register themselves with
** all blocks up the stack, until either 1. the tx block which created
** it is reached or 2. the last tx block where it was saved is reached.
** 090 HC 20180223 Improved assignment of SCREEN-VALUE value, the value may contain
** fill characters according to the widget format and data type.
** 091 CA 20181106 forceBytes(String) must interpret the bytes using a single-byte
** charset.
** 092 CA 20181220 Allow POLY for format argument in STRING builtin function.
** Added source and target code-page args for CHR and ASC functions.
** 093 OM 20190206 Added implementation of field size needed by record-length.
** Adjsuted valueOf(rowid).
** 094 SBI 20190405 Added truncateValue().
** 095 EVL 20190417 Adding codepage support for CHR/ASC functions.
** 096 GES 20190604 Added additional undocumented quote escaping quirk to string literal
** processing.
** EVL 20190924 Adding Unicode chars support to Progress to Java strings conversion.
** GES 20191107 Fixed incorrect Unicode escapes on windows. Added support for the
** full range of UTF-32 characters which can be encoded in 4GL source
** strings (as ~UXXXXXX escape sequences).
** 097 CA 20200304 Allow STRING(javaInstance).
** 098 ME 20200810 Switched to improved I18N workers for chr/asc.
** 099 IAS 20201007 Added Type enum
** 100 EVL 20201222 Variable based on unknown rowid var should be made unknown, not known '?'.
** VVT 20210317 character valueOf() fixed for the case null passed af the format argument.
** OM 20210321 Exposed members to LOB classes.
** VVT 20210331 character valueOf(rowid) fixed: uses rowid.toString(), misleading javadoc
** removed. See #5218.
** CA 20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
** initialized at the method's execution, and not at the caller's arguments.
** IAS 20210922 Fixed processing of the empty repeat format spec.
** HC 20211001 Implementation of i18n support.
** CA 20211004 An empty string formatted with 'fill chars' must return all-space string.
** IAS 20211223 No NumberFormatException in the preprocessFormatString() method.
** AL2 20220328 Added proxy checks for BDT.
** 101 CA 20230215 'duplicate()' method returns the real type instead of BDT.
** 102 CA 20230526 Added 'forceLengthLimit', which is used to disable character limit enforcement,
** when creating a character via 'forceCharacterType'.
** 103 SB 20240411 Changed forceBytes to use the Java equivalent for the used SESSION:CPINTERNAL
** charset. Refs #8468.
** 104 CA 20241030 Added character constant and cache support - these are immutable instances which
** must not 'escape' from FWD runtime, and only used via assignment or as read-only.
** Avoid 'Text.javaSpacifyNull' if the string is known to already have been
** processed.
** 105 ICP 20250129 Used int64.of to leverage caches instances.
** ICP 20250207 Modified the character constant cache to use CacheManager and have it's
** maximum size configurable.
*/
/*
** 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.nio.charset.*;
import java.util.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.util.*;
/**
* A class that represents a Progress 4GL compatible character object whose
* data is mutable. All Progress language features related to character
* types are supported including all functionality that can be accessed via
* the character concatination operator and builtin Progress string
* functions. Normal character comparison operators are implemented in
* the {@link CompareOps} class. Operator and functions common to character
* and longchar types are implemented as statics in the {@link TextOps}
* class.
* <p>
* In addition to the implementation of the base string processing functions
* and operators, this class hides the representation of the
* <code>unknown value</code> and properly maintains the processing of
* <code>unknown value</code>.
* <p>
* Format string related processing is handled in this class, see method
* {@link #toString(String)}.
* <p>
* The following is the mapping of Progress language features to the
* corresponding feature in this class:
* <p>
* <pre>
* contains operator not supported at this time
* _cbit function {@link #testBitAt}
* chr function {@link #chr}
* asc function {@link #asc}
* string function {@link #valueOf}
* fill function {@link #fill}
* quoter function {@link #quoter}
* </pre>
* <p>
* <b>Please note that at this time, no conversion of Progress strings to
* Java strings is done. All strings passed are expected to be Java
* strings. This behavior may need changing in the case where a Progress
* string is built at runtime but needs to be used as a Java string.</b> In
* this case the {@link #progressToJavaString} method should be used.
*
* @author GES
*/
public class character
extends Text
{
/** Default maximum number of cached constant character instances */
private static final int DEFAULT_CHARACTER_CACHE_LIMIT = 2048;
/** Empty character constant. */
public static final character EMPTY_STRING = new characterConstant("");
/** Unknown character constant. */
public static final character UNKNOWN = new characterConstant();
/** Maximum number of cached integer instances */
private static int CHARACTER_CACHE_LIMIT;
/** JVM-wide cache of constant character instances. */
private static Map<String, character> CHARACTER_CONSTANTS;
/** The default format string. */
static final String defaultFormat = "x(8)";
/**
* Default constructor, creates an instance that represents the unknown
* value.
*/
public character()
{
super();
}
/**
* This is a special c'tor which should be used only when converting the
* value returned by a function or method with polymorphic return type into the
* expected type (i.e. DYNAMIC-FUNCTION()). In such cases, the 4GL does some
* automatic type conversion (this is delegated to the superclass' constructor).
*
* @param value
* The value to be used for this instance.
*/
public character(BaseDataType value)
{
super(value);
}
/**
* Create a new instance, by copying the state of the passed {@link Text}.
*
* @param value
* The value which needs to be copied.
*/
public character(Text value)
{
super(value);
}
/**
* Constructs an instance that has the <code>String</code> value passed
* in and which <b>does not</b> represent the <code>unknown value</code>,
* unless the <code>value</code> argument is <code>null</code>.
*
* @param value
* The character value of the resulting instance.
*/
public character(String value)
{
super(value);
}
/**
* Constructs an instance that has the <code>String</code> value passed
* in and which <b>does not</b> represent the <code>unknown value</code>
* unless the <code>value</code> argument is <code>null</code>.
*
* @param value
* The character value of the resulting instance.
* @param caseSens
* Specifies whether this instance should use case sensitive
* comparisons.
*/
public character(String value, boolean caseSens)
{
super(value, caseSens, true);
}
/**
* Constructs an instance that has the <code>String</code> value passed
* in and which <b>does not</b> represent the <code>unknown value</code>
* unless the <code>value</code> argument is <code>null</code>.
*
* @param value
* The character value of the resulting instance.
* @param caseSens
* Specifies whether this instance should use case sensitive
* comparisons.
* @param javaSpacifyNull
* Flag indicating if {@link #javaSpacifyNull(String)} needs to be executed on the value or not.
*/
public character(String value, boolean caseSens, boolean javaSpacifyNull)
{
super(value, caseSens, javaSpacifyNull);
}
/**
* Constructs an instance that has a <code>char</code> value passed
* in and which <b>does not</b> represent the <code>unknown value</code>.
*
* @param ch
* The character value of the resulting instance.
*/
public character(char ch)
{
super(ch);
}
/**
* Constructs an instance that has the <code>String</code> value and
* <code>unknown value</code> that exactly matches that of the passed-in
* instance.
*
* @param value
* The character and unknown value of the resulting instance.
*/
public character(character value)
{
super(value);
}
/**
* Initializes the cache using CacheManager. The default value of the cache
* is used when there is no size available from the configuration.
*/
public static void initializeCache()
{
CHARACTER_CONSTANTS = CacheManager.createIdentityHashMapCache(character.class,
"character-cache",
DEFAULT_CHARACTER_CACHE_LIMIT);
CHARACTER_CACHE_LIMIT = CacheManager.getCacheSize(character.class,
"character-cache",
DEFAULT_CHARACTER_CACHE_LIMIT);
}
/**
* Retrieve the {@link #CHARACTER_CONSTANTS constant} for the given value. If it does not exist, create it.
* <p>
* The keys in the constant map will be interned - use with care.
* <p>
* The result needs to be assigned to a real character instance, and not cached in other maps.
*
* @param value
* The value to retrieve the character constant.
*
* @return See above.
*/
public static character of(String value)
{
if (value == null)
{
return UNKNOWN;
}
else if (value.isEmpty())
{
return EMPTY_STRING;
}
value = value.intern();
character res = null;
if (CHARACTER_CONSTANTS == null)
{
return new characterConstant(value);
}
synchronized (CHARACTER_CONSTANTS)
{
res = CHARACTER_CONSTANTS.get(value);
}
if (res == null)
{
res = new characterConstant(value);
synchronized (CHARACTER_CONSTANTS)
{
if (CHARACTER_CONSTANTS.size() < CHARACTER_CACHE_LIMIT)
{
CHARACTER_CONSTANTS.put(value, res);
}
}
}
return res;
}
/**
* Returns the numeric value of a character expression which must be
* only 1 character in length. This method will probably generate
* incorrect results in a DBCS environment since it will use UNICODE
* as the basis for 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.
*
* @param c
* The character expression to convert (must be 1 character in
* length).
* @param targetCP
* The target code page.
* @param sourceCP
* The source code page.
*
* @return The numeric value of that character which should match up
* with the Progress equivalents in the ASCII range.
*/
public static int64 asc(character c, String targetCP, String sourceCP)
{
if (c.isUnknown())
{
return int64.UNKNOWN;
}
return int64.of((long)I18nOps.asc(c.getValue(), targetCP, sourceCP));
}
/**
* Returns the numeric value of a character expression which must be
* only 1 character in length. This method will probably generate
* incorrect results in a DBCS environment since it will use UNICODE
* as the basis for 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.
*
* @param c
* The character expression to convert (must be 1 character in
* length).
*
* @return The numeric value of that character which should match up
* with the Progress equivalents in the ASCII range.
*/
public static int64 asc(character c)
{
return asc(c, (String)null, (String)null);
}
/**
* Returns the numeric value of a string which must be only 1 character
* in length. This method will probably generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for 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.
*
* @param str
* The string to convert (must be 1 character in length).
*
* @return The numeric value of that character which should match up
* with the Progress equivalents in the ASCII range.
*/
public static int64 asc(String str)
{
// null embedded characters will be specified by the constructor
return asc(new character(str), (String) null, (String) null);
}
/**
* Returns the numeric value of a string which must be only 1 character
* in length. This method will probably generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for 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.
*
* @param str
* The string to convert (must be 1 character in length).
* @param tarCodePage
* The target code page.
*
* @return The numeric value of that character which should match up
* with the Progress equivalents in the ASCII range.
*/
public static int64 asc(character str, character tarCodePage)
{
String targetCP = (tarCodePage == null || tarCodePage.isUnknown()) ? null : tarCodePage.getValue();
return asc(str, targetCP, null);
}
/**
* Returns the numeric value of a string which must be only 1 character
* in length. This method will probably generate incorrect results in a
* DBCS environment since it will use UNICODE as the basis for 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.
*
* @param str
* The string to convert (must be 1 character in length).
* @param tarCodePage
* The target code page.
* @param srcCodePage
* The source code page.
*
* @return The numeric value of that character which should match up
* with the Progress equivalents in the ASCII range.
*/
public static int64 asc(character str, character tarCodePage, character srcCodePage)
{
String targetCP = tarCodePage == null || tarCodePage.isUnknown()
? (String)null : tarCodePage.getValue();
String sourceCP = srcCodePage == null || srcCodePage.isUnknown()
? (String)null : srcCodePage.getValue();
return asc(str, targetCP, sourceCP);
}
/**
* Returns the character value of a numeric expression which must be
* between 1 and 65534 (inclusive). This method will probably generate
* incorrect results in a DBCS environment since it will use UNICODE
* as the basis for 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. The result should match up with the Progress
* equivalents in the ASCII range.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
*
* @param i
* The int64 to convert.
*
* @return The associated character or a character representing the
* empty string (0 length) when the input is out of range or
* <code>unknown value</code> if the input is
* <code>unknown value</code>.
*/
public static character chr(int64 i)
{
return chr(i, (String)null, (String)null);
}
/**
* Returns the character value of a numeric expression which must be
* between 1 and 65534 (inclusive). This method will probably generate
* incorrect results in a DBCS environment since it will use UNICODE
* as the basis for 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. The result should match up with the Progress
* equivalents in the ASCII range.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
*
* @param i
* The long to convert.
*
* @return The associated character or a character representing the
* empty string (0 length) when the input is out of range.
*/
public static character chr(long i)
{
return chr(i, (String)null, (String)null);
}
/**
* Returns the character value of a numeric expression which must be
* between 1 and 65534 (inclusive). This method will probably generate
* incorrect results in a DBCS environment since it will use UNICODE
* as the basis for 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. The result should match up with the Progress
* equivalents in the ASCII range.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
*
* @param i
* The int64 to convert.
* @param tarCodePage
* The target code page.
*
* @return The associated character or a character representing the
* empty string (0 length) when the input is out of range or
* <code>unknown value</code> if the input is
* <code>unknown value</code>.
*/
public static character chr(int64 i, character tarCodePage)
{
String targetCP = tarCodePage == null || tarCodePage.isUnknown()
? (String)null : tarCodePage.getValue();
return chr(i, targetCP, (String)null);
}
/**
* Returns the character value of a numeric expression which must be
* between 1 and 65534 (inclusive). This method will probably generate
* incorrect results in a DBCS environment since it will use UNICODE
* as the basis for 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. The result should match up with the Progress
* equivalents in the ASCII range.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
*
* @param i
* The int64 to convert.
* @param tarCodePage
* The target code page.
* @param srcCodePage
* The source code page.
*
* @return The associated character or a character representing the
* empty string (0 length) when the input is out of range or
* <code>unknown value</code> if the input is
* <code>unknown value</code>.
*/
public static character chr(int64 i, character tarCodePage, character srcCodePage)
{
String targetCP = tarCodePage == null || tarCodePage.isUnknown()
? (String)null : tarCodePage.getValue();
String sourceCP = srcCodePage == null || srcCodePage.isUnknown()
? (String)null : srcCodePage.getValue();
return chr(i, targetCP, sourceCP);
}
/**
* Returns the character value of a numeric expression which must be
* between 1 and 65534 (inclusive). This method will probably generate
* incorrect results in a DBCS environment since it will use UNICODE
* as the basis for 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. The result should match up with the Progress
* equivalents in the ASCII range.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
*
* @param i
* The int64 to convert.
* @param targetCP
* The target code page.
* @param sourceCP
* The source code page.
*
* @return The associated character or a character representing the
* empty string (0 length) when the input is out of range or
* <code>unknown value</code> if the input is
* <code>unknown value</code>.
*/
public static character chr(int64 i, String targetCP, String sourceCP)
{
if (i.isUnknown())
{
return new character();
}
return chr(i.longValue(), targetCP, sourceCP);
}
/**
* Returns the character value of a numeric expression which must be
* between 1 and 65534 (inclusive). This method will probably generate
* incorrect results in a DBCS environment since it will use UNICODE
* as the basis for 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. The result should match up with the Progress
* equivalents in the ASCII range.
* <p>
* This method does not handle the case of user-specified source and/or
* target codepages as is possible in Progress.
*
* @param i
* The long to convert.
* @param targetCP
* The target code page.
* @param sourceCP
* The source code page.
*
* @return The associated character or a character representing the
* empty string (0 length) when the input is out of range.
*/
public static character chr(long i, String targetCP, String sourceCP)
{
String ch = I18nOps.chr((int)i, targetCP, sourceCP);
return ch == null ? new character("") : new character(ch);
}
/**
* Get the type
* @return type
*/
public Type getType()
{
return Type.CHARACTER;
}
/**
* Return a character instance that is comprised of <code>num</code>
* concatenated copies of a given string.
*
* @param c
* The string to be repeated.
* @param num
* The number of times to repeat.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code> or an empty string if
* <code>num</code> < 1.
*/
public static character fill(character c, NumberType num)
{
if (c == null || c.isUnknown() || num == null || num.isUnknown())
{
return new character();
}
return fillWorker(c, num.intValue(), c.isCaseSensitive());
}
/**
* Return a character instance that is comprised of <code>num</code>
* concatenated copies of a given string.
*
* @param c
* The string to be repeated.
* @param num
* The number of times to repeat.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code> or an empty string if
* <code>num</code> < 1.
*/
public static character fill(character c, int num)
{
if (c == null || c.isUnknown())
{
return new character();
}
return fillWorker(c, num, c.isCaseSensitive());
}
/**
* Return a character instance that is comprised of <code>num</code>
* concatenated copies of a given string.
*
* @param c
* The string to be repeated.
* @param num
* The number of times to repeat.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code> or an empty string if
* <code>num</code> < 1.
*/
public static character fill(character c, double num)
{
if (c == null || c.isUnknown())
{
return new character();
}
return fillWorker(c, (int) num, c.isCaseSensitive());
}
/**
* Return a character instance that is comprised of <code>num</code>
* concatenated copies of a given string.
*
* @param str
* The string to be repeated.
* @param num
* The number of times to repeat.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code> or an empty string if
* <code>num</code> < 1.
*/
public static character fill(String str, NumberType num)
{
if (num == null || num.isUnknown())
{
return new character();
}
// null embedded characters will be spacified by the constructor
return fillWorker(new character(str), num.intValue(), false);
}
/**
* Return a character instance that is comprised of <code>num</code>
* concatenated copies of a given string.
*
* @param str
* The string to be repeated.
* @param num
* The number of times to repeat.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code> or an empty string if
* <code>num</code> < 1.
*/
public static character fill(String str, double num)
{
// null embedded characters will be spacified by the constructor
return fillWorker(new character(str), (int) num, false);
}
/**
* Return a character instance that is comprised of <code>num</code>
* concatenated copies of a given string.
*
* @param str
* The string to be repeated.
* @param num
* The number of times to repeat.
*
* @return The resulting string or <code>unknown value</code> if any
* input is <code>unknown value</code> or an empty string if
* <code>num</code> < 1.
*/
public static character fill(String str, int num)
{
// null embedded characters will be spacified by the constructor
return fillWorker(new character(str), num, false);
}
/**
* General quoter function implementation without additional parameters.
* <p>
* This puts the <code>character</code> value of the variable between
* double quotes if the value is not <code>unknown</code> or replace it
* with '?' if the value is <code>unknown</code>.
*
* @param var
* The <code>BaseDataType</code> variable to operate upon.
*
* @return The resulting quoted character value of the function.
*/
public static character quoter(BaseDataType var)
{
return quoterWorker(var, null, null);
}
/**
* General quoter function implementation without additional parameters.
* <p>
* This puts the <code>character</code> value of the variable between
* double quotes if the value is not <code>null</code> or replace it
* with '?' if the value is <code>null</code>.
*
* @param var
* The <code>String</code> variable to operate upon.
*
* @return The resulting quoted character value of the function.
*/
public static character quoter(String var)
{
return quoterWorker(new character(var), null, null);
}
/**
* No parameter quoter function implementation.
* This will be called when trying to quote an unknown literal.
*
* @return The default unknown character ?.
*/
public static character quoter()
{
return new character("?");
}
/**
* Quoter function call for 2 parameters.
* <p>
* This puts the <code>character</code> value of the variable between
* double quotes if the value is not <code>unknown</code> or replace it
* with '?' if the value is <code>unknown</code>.
* <p>
* Additionally a parameter representing a custom quote character
* replacement is provided. If not <code>null</code>, then the first
* character of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can only be given in the 3 parameter call.
*
* @param var
* The <code>BaseDataType</code> variable to operate upon
* @param customQuote
* The <code>String</code> variable
* that represents the custom quoter.Custom <code>unknown</code>
* can only be given in 3 parameter call.
*
* @return The resulting quoted character value of the function.
*/
public static character quoter(BaseDataType var, String customQuote)
{
return quoterWorker(var, customQuote, null);
}
/**
* Quoter function call for 2 parameters.
* <p>
* This puts the <code>String</code> value of the variable between
* double quotes if the value is not <code>null</code> or replace it
* with '?' if the value is <code>null</code>.
* <p>
* Additionally a parameter representing a custom quote character
* replacement is provided. If not <code>null</code>, then the first
* character of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can only be given in the 3 parameter call.
*
* @param var
* The <code>String</code> variable to operate upon
* @param customQuote
* The <code>String</code> variable
* that represents the custom quoter.Custom <code>unknown</code>
* can only be given in 3 parameter call.
*
* @return The resulting quoted character value of the function.
*/
public static character quoter(String var, String customQuote)
{
return quoterWorker(new character(var), customQuote, null);
}
/**
* Quoter function call for 2 parameters.
* <p>
* This puts the <code>character</code> value of the variable between
* double quotes if the value is not <code>unknown</code> or replace it
* with '?' if the value is <code>unknown</code>.
* <p>
* Additionally a parameter representing a custom character
* replacement is provided. If not <code>null</code>, then the first
* character of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can only be given in the 3 parameter call.
*
* @param var
* The <code>BaseDataType</code> variable to operate upon
* @param customQuote
* The <code>String</code> variable
* that represents the custom quoter.Custom <code>unknown</code>
* can only be given in 3 parameter call.
*
* @return The resulting quoted character value of the function.
*/
public static character quoter(BaseDataType var, character customQuote)
{
return quoter(var, customQuote, (String) null);
}
/**
* Quoter function call for 2 parameters.
* <p>
* This puts the <code>String</code> value of the variable between
* double quotes if the value is not <code>null</code> or replace it
* with '?' if the value is <code>null</code>.
* <p>
* Additionally a parameter representing a custom character
* replacement is provided. If not <code>null</code>, then the first
* character of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can only be given in the 3 parameter call.
*
* @param var
* The <code>String</code> variable to operate upon
* @param customQuote
* The <code>String</code> variable
* that represents the custom quoter.Custom <code>unknown</code>
* can only be given in 3 parameter call.
*
* @return The resulting quoted character value of the function.
*/
public static character quoter(String var, character customQuote)
{
return quoter(new character(var), customQuote, (String) null);
}
/**
* Quoter function implementation for 3 parameters.
* <p>
* This puts the <code>character</code> value of the variable between
* double quotes if the value is not <code>unknown</code> or replace it
* with '?' if the value is <code>unknown</code>.
* <p>
* Additionally, a parameter representing a custom quote character
* is provided. If not <code>null</code>, then the first character
* of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can also be provided. If
* not <code>null</code>, then the given text will be used instead
* of the default ? <code>unknown</code> replacement to be used.
*
*
* @param var
* The <code>BaseDataType</code> variable to operate upon.
* @param customQuote
* The custom <code>character</code> quoter variable.
* @param customNull
* The custom <code>character</code> <code>unknown</code>
* replacement to be used.
*
* @return The resulting quoted <code>character</code> value
* of the function.
*/
public static character quoter(BaseDataType var,
character customQuote,
character customNull)
{
return quoterWorker(var,
customQuote.isUnknown() ? null : customQuote.getValue(),
customNull.isUnknown() ? null : customNull.getValue());
}
/**
* Quoter function implementation for 3 parameters.
* <p>
* This puts the <code>String</code> value of the variable between
* double quotes if the value is not <code>null</code> or replace it
* with '?' if the value is <code>null</code>.
* <p>
* Additionally, a parameter representing a custom quote character
* is provided. If not <code>null</code>, then the first character
* of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can also be provided. If
* not <code>null</code>, then the given text will be used instead
* of the default ? <code>unknown</code> replacement to be used.
*
*
* @param var
* The <code>String</code> variable to operate upon.
* @param customQuote
* The custom <code>character</code> quoter variable.
* @param customNull
* The custom <code>character</code> <code>unknown</code>
* replacement to be used.
*
* @return The resulting quoted <code>character</code> value
* of the function.
*/
public static character quoter(String var,
character customQuote,
character customNull)
{
return quoterWorker(new character(var),
customQuote.isUnknown() ? null : customQuote.getValue(),
customNull.isUnknown() ? null : customNull.getValue());
}
/**
* Quoter function implementation for 3 parameters.
* <p>
* This puts the <code>character</code> value of the variable between
* double quotes if the value is not <code>unknown</code> or replace it
* with '?' if the value is <code>unknown</code>.
* <p>
* Additionally, a parameter representing a custom quote character
* is provided. If not <code>null</code>, then the first character
* of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can also be provided. If
* not <code>null</code>, then the given text will be used instead
* of the default ? <code>unknown</code> replacement to be used.
*
* @param var
* The <code>BaseDataType</code> variable to operate upon.
* @param customQuote
* The custom <code>String</code> quoter variable.
* @param customNull
* The custom <code>character</code> <code>unknown</code>
* replacement to be used.
*
* @return The resulting quoted <code>character</code> value
* of the function.
*/
public static character quoter(BaseDataType var,
String customQuote,
character customNull)
{
return quoterWorker(var,
customQuote,
customNull.isUnknown() ? null : customNull.getValue());
}
/**
* Quoter function implementation for 3 parameters.
* <p>
* This puts the <code>String</code> value of the variable between
* double quotes if the value is not <code>null</code> or replace it
* with '?' if the value is <code>null</code>.
* <p>
* Additionally, a parameter representing a custom quote character
* is provided. If not <code>null</code>, then the first character
* of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can also be provided. If
* not <code>null</code>, then the given text will be used instead
* of the default ? <code>unknown</code> replacement to be used.
*
* @param var
* The <code>String</code> variable to operate upon.
* @param customQuote
* The custom <code>String</code> quoter variable.
* @param customNull
* The custom <code>character</code> <code>unknown</code>
* replacement to be used.
*
* @return The resulting quoted <code>character</code> value
* of the function.
*/
public static character quoter(String var,
String customQuote,
character customNull)
{
return quoterWorker(new character(var),
customQuote,
customNull.isUnknown() ? null: customNull.getValue());
}
/**
* Quoter function implementation for 3 parameters.
* <p>
* This puts the <code>character</code> value of the variable between
* double quotes if the value is not <code>unknown</code> or replace it
* with '?' if the value is <code>unknown</code>.
* <p>
* Additionally, a parameter representing a custom quote character
* is provided. If not <code>null</code>, then the first character
* of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can also be provided. If
* not <code>null</code>, then the given text will be used instead
* of the default ? <code>unknown</code> replacement to be used.
*
* @param var
* The <code>BaseDataType</code> variable to operate upon.
* @param customQuote
* The custom <code>character</code> quoter variable.
* @param customNull
* The custom <code>String</code> <code>unknown</code>
* replacement to be used.
*
* @return The resulting quoted <code>character</code> value
* of the function.
*/
public static character quoter(BaseDataType var,
character customQuote,
String customNull)
{
return quoterWorker(var,
customQuote.isUnknown() ? null : customQuote.getValue(),
customNull);
}
/**
* Quoter function implementation for 3 parameters.
* <p>
* This puts the <code>String</code> value of the variable between
* double quotes if the value is not <code>null</code> or replace it
* with '?' if the value is <code>null</code>.
* <p>
* Additionally, a parameter representing a custom quote character
* is provided. If not <code>null</code>, then the first character
* of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can also be provided. If
* not <code>null</code>, then the given text will be used instead
* of the default ? <code>unknown</code> replacement to be used.
*
* @param var
* The <code>String</code> variable to operate upon.
* @param customQuote
* The custom <code>character</code> quoter variable.
* @param customNull
* The custom <code>String</code> <code>unknown</code>
* replacement to be used.
*
* @return The resulting quoted <code>character</code> value
* of the function.
*/
public static character quoter(String var,
character customQuote,
String customNull)
{
return quoterWorker(new character(var),
customQuote.isUnknown() ? null : customQuote.getValue(),
customNull);
}
/**
* Quoter function implementation for 3 parameters.
* <p>
* This puts the <code>character</code> value of the variable between
* double quotes if the value is not <code>unknown</code> or replace it
* with '?' if the value is <code>unknown</code>.
* <p>
* Additionally, a parameter representing a custom quote character
* is provided. If not <code>null</code>, then the first character
* of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can also be provided. If
* not <code>null</code>, then the given text will be used instead
* of the default ? <code>unknown</code> replacement to be used.
*
* @param var
* The <code>BaseDataType</code> variable to operate upon.
* @param customQuote
* The custom <code>String</code> quoter variable.
* @param customNull
* The custom <code>String</code> <code>unknown</code>
* replacement to be used.
*
* @return The resulting quoted <code>character</code> value
* of the function.
*/
public static character quoter(BaseDataType var, String customQuote, String customNull)
{
return quoterWorker(var, customQuote, customNull);
}
/**
* Quoter function implementation for 3 parameters.
* <p>
* This puts the <code>String</code> value of the variable between
* double quotes if the value is not <code>null</code> or replace it
* with '?' if the value is <code>null</code>.
* <p>
* Additionally, a parameter representing a custom quote character
* is provided. If not <code>null</code>, then the first character
* of the given text will be used instead of the "
* character.
* <p>
* A custom unknown value can also be provided. If
* not <code>null</code>, then the given text will be used instead
* of the default ? <code>unknown</code> replacement to be used.
*
* @param var
* The <code>String</code> variable to operate upon.
* @param customQuote
* The custom <code>String</code> quoter variable.
* @param customNull
* The custom <code>String</code> <code>unknown</code>
* replacement to be used.
*
* @return The resulting quoted <code>character</code> value
* of the function.
*/
public static character quoter(String var, String customQuote, String customNull)
{
return quoterWorker(new character(var), customQuote, customNull);
}
/**
* Tests a bit in the given position in the input array to determine if
* that bit is a 0 or 1. The input string is treated as a multibyte
* bitfield (a byte array). The first character of the string corresponds
* to bit positions 0-7 (if it is non-DBCS and presumably in a DBCS
* character the positions would be 0-15, although this is not known). In
* a non-DBCS charset, the second char would correspond to bits 8-15.
* <p>
* This feature supports an undocumented Progress built-in function called
* _cbit().
*
* @param data
* A string with 0 or more characters.
* @param index
* The bit position to test.
*
* @return The return is always <code>false</code> if the empty string
* is input or if a negative index is specified. For any string
* of > 0 bytes length, the return is <code>true</code> if the
* bit in the specified position is 1 and <code>false</code> if
* it is 0.
*/
public static logical testBitAt(character data, int64 index)
{
if (data.isUnknown() || index.isUnknown())
{
return new logical();
}
return testBitAt(data, index.intValue());
}
/**
* Tests a bit in the given position in the input array to determine if
* that bit is a 0 or 1. The input string is treated as a multibyte
* bitfield (a byte array). The first character of the string corresponds
* to bit positions 0-7 (if it is non-DBCS and presumably in a DBCS
* character the positions would be 0-15, although this is not known). In
* a non-DBCS charset, the second char would correspond to bits 8-15.
* <p>
* This feature supports an undocumented Progress built-in function called
* _cbit().
*
* @param data
* A string with 0 or more characters.
* @param index
* The bit position to test.
*
* @return The return is always <code>false</code> if the empty string
* is input or if a negative index is specified. For any string
* of > 0 bytes length, the return is <code>true</code> if the
* bit in the specified position is 1 and <code>false</code> if
* it is 0.
*/
public static logical testBitAt(character data, int index)
{
if (data == null || data.isUnknown())
{
return new logical();
}
// invalid/empty data or invalid index
if (data == null || TextOps.lengthOf(data) == 0 || index < 0)
{
return new logical(false);
}
byte[] bits = data.getValue().getBytes();
int byteOff = index / 8;
// index is too large
if (byteOff >= bits.length)
{
return new logical(false);
}
// as with the 4GL, if there are embedded null chars already in the var, this will
// honor those characters
byte bitOff = (byte) (0xFF & (0x01 << (index % 8)));
byte result = (byte) (0xFF & (bits[byteOff] & bitOff));
return new logical(result != 0x00);
}
/**
* Tests a bit in the given position in the input array to determine if
* that bit is a 0 or 1. The input string is treated as a multibyte
* bitfield (a byte array). The first character of the string corresponds
* to bit positions 0-7 (if it is non-DBCS and presumably in a DBCS
* character the positions would be 0-15, although this is not known). In
* a non-DBCS charset, the second char would correspond to bits 8-15.
* <p>
* This feature supports an undocumented Progress built-in function called
* _cbit().
*
* @param data
* A string with 0 or more characters.
* @param index
* The bit position to test.
*
* @return The return is always <code>false</code> if the empty string
* is input or if a negative index is specified. For any string
* of > 0 bytes length, the return is <code>true</code> if the
* bit in the specified position is 1 and <code>false</code> if
* it is 0.
*/
public static logical testBitAt(String data, int64 index)
{
if (index.isUnknown())
{
return new logical();
}
return testBitAt(data, index.intValue());
}
/**
* Tests a bit in the given position in the input array to determine if
* that bit is a 0 or 1. The input string is treated as a multibyte
* bitfield (a byte array). The first character of the string corresponds
* to bit positions 0-7 (if it is non-DBCS and presumably in a DBCS
* character the positions would be 0-15, although this is not known). In
* a non-DBCS charset, the second char would correspond to bits 8-15.
* <p>
* This feature supports an undocumented Progress built-in function called
* _cbit().
*
* @param data
* A string with 0 or more characters.
* @param index
* The bit position to test.
*
* @return The return is always <code>false</code> if the empty string
* is input or if a negative index is specified. For any string
* of > 0 bytes length, the return is <code>true</code> if the
* bit in the specified position is 1 and <code>false</code> if
* it is 0.
*/
public static logical testBitAt(String data, int index)
{
// protects from null input by using the character constructor
return testBitAt(new character(data), index);
}
/**
* Convert a <code>double</code> into a character object using
* the 'message' format for the <code>decimal</code> data type.
*
* @param num
* The data to convert.
*
* @return The resulting string representation.
*/
public static character valueOf(double num)
{
return valueOf(new decimal(num));
}
/**
* Convert an <code>int</code> into a character object using
* the 'message' format for the <code>int64</code> data type.
*
* @param num
* The data to convert.
*
* @return The resulting string representation.
*/
public static character valueOf(long num)
{
return valueOf(new int64(num));
}
/**
* Convert a <code>String</code> into a character object using
* the 'message' format for the <code>character</code> data type.
*
* @param s
* The data to convert.
*
* @return The resulting string representation.
*/
public static character valueOf(String s)
{
return valueOf(new character(s));
}
/**
* Convert a <code>boolean</code> into a character object using
* the 'message' format for the <code>logical</code> data type.
*
* @param b
* The data to convert.
*
* @return The resulting string representation.
*/
public static character valueOf(boolean b)
{
return valueOf(new logical(b));
}
/**
* Convert a <code>rowid</code> into a character object using
* the 'message' format for the <code>rowid</code> data type.
* <p>
* The returned value is guaranteed to have:
* <ul>
* <li>'0x' prefix
* <li>only lowercase hexadecimal digits
* <li>even length, but not more that 16 digits
* </ul>
*
* @param r
* The data to convert.
*
* @return The resulting string representation.
*/
public static character valueOf(rowid r)
{
if (r.isUnknown())
{
return new character(); // not "?", just return unknown
}
return new character(r.toString());
}
/**
* Convert any Progress compatible wrapper into a character object using the 'message' format
* for the data type being converted.
*
* @param bdt
* The data to convert.
*
* @return The resulting string representation.
*/
public static character valueOf(BaseDataType bdt)
{
if (bdt.isUnknown())
{
// there is one exception here: string(blob) = "?" not unknown value
return (bdt instanceof blob) ? new character("?") : new character();
}
character ret = new character(bdt.toStringMessage(), false, false);
if (bdt instanceof Text && ((Text) bdt).isCaseSensitive())
{
// honor case-sensitivity
ret.setCaseSensitive(true);
}
return ret;
}
/**
* Get the string representation of any Java instance. If this is a {@link BaseDataType},
* delegate the call to {@link #valueOf(BaseDataType)}. Otherwise, {@link Object#toString()}.
*
* @param obj
* The instance to get the string representation.
*
* @return The resulting string representation.
*/
public static character valueOf(Object obj)
{
if (obj instanceof BaseDataType)
{
return valueOf((BaseDataType) obj);
}
return obj == null ? new character() : new character(obj.toString());
}
/**
* Convert a <code>double</code> into a character object using
* the given format for the <code>decimal</code> data type.
*
* @param num
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(double num, String fmt)
{
return valueOf(new decimal(num), fmt);
}
/**
* Convert an <code>int</code> into a character object using
* the given format for the <code>int64</code> data type.
*
* @param num
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(long num, String fmt)
{
return valueOf(new int64(num), fmt);
}
/**
* Convert a <code>String</code> into a character object using
* the given format for the <code>character</code> data type.
*
* @param s
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(String s, String fmt)
{
return valueOf(new character(s), fmt);
}
/**
* Convert a <code>boolean</code> into a character object using
* the given format for the <code>logical</code> data type.
*
* @param b
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(boolean b, String fmt)
{
return valueOf(new logical(b), fmt);
}
/**
* Convert any Progress compatible wrapper into a character object using
* the given format for the data type being converted.
*
* @param bdt
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(BaseDataType bdt, String fmt)
{
if (bdt.isUnknown() || fmt == null)
return new character();
return new character(bdt.toString(fmt));
}
/**
* Convert a <code>double</code> into a character object using
* the given format for the <code>decimal</code> data type.
*
* @param num
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(double num, character fmt)
{
return valueOf(new decimal(num), fmt);
}
/**
* Convert an <code>int</code> into a character object using
* the given format for the <code>int64</code> data type.
*
* @param num
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(long num, character fmt)
{
return valueOf(new int64(num), fmt);
}
/**
* Convert a <code>String</code> into a character object using
* the given format for the <code>character</code> data type.
*
* @param s
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(String s, character fmt)
{
return valueOf(new character(s), fmt);
}
/**
* Convert a <code>boolean</code> into a character object using
* the given format for the <code>logical</code> data type.
*
* @param b
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(boolean b, character fmt)
{
return valueOf(new logical(b), fmt);
}
/**
* Convert any Progress compatible wrapper into a character object using
* the given format for the data type being converted.
*
* @param bdt
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(BaseDataType bdt, character fmt)
{
return valueOf(bdt, fmt.getValue());
}
/**
* Convert a <code>double</code> into a character object using
* the given format for the <code>decimal</code> data type.
*
* @param num
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(double num, BaseDataType fmt)
{
return valueOf(new decimal(num), fmt.toStringMessage());
}
/**
* Convert an <code>int</code> into a character object using
* the given format for the <code>int64</code> data type.
*
* @param num
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(long num, BaseDataType fmt)
{
return valueOf(new int64(num), fmt.toStringMessage());
}
/**
* Convert a <code>String</code> into a character object using
* the given format for the <code>character</code> data type.
*
* @param s
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(String s, BaseDataType fmt)
{
return valueOf(new character(s), fmt.toStringMessage());
}
/**
* Convert a <code>boolean</code> into a character object using
* the given format for the <code>logical</code> data type.
*
* @param b
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(boolean b, BaseDataType fmt)
{
return valueOf(new logical(b), fmt.toStringMessage());
}
/**
* Convert any Progress compatible wrapper into a character object using
* the given format for the data type being converted.
*
* @param bdt
* The data to convert.
* @param fmt
* The format string to use.
*
* @return The resulting string representation.
*/
public static character valueOf(BaseDataType bdt, BaseDataType fmt)
{
if (bdt.isUnknown())
return new character();
return new character(bdt.toString(fmt.toStringMessage()));
}
/**
* Removes a set of enclosing double quotes (first and last characters)
* and converts any doubled up instances (2 instances sequentially) of
* double quotes into a single instance of double quotes. This reverses
* the reversible portion of the export format into a normal string as
* Java would expect to see it.
*
* @param export
* A string with the contents in export format as created by
* {@link #toStringExport}.
*
* @return A valid Java string.
*/
public static String fromExportString(String export)
{
// this will always work as long as the input string is valid
String contents = export.substring(1, export.length() - 1);
// remove any doubling up of double quote chars and convert to a single instance
return contents.replaceAll("\"\"", "\"");
}
/**
* Converts a Progress 4GL compatible string to the equivalent Java source
* string, by stripping off enclosing quotes/string options and converting
* the contents (including rewriting all escape sequences). Note that the
* output format is designed to be written to a Java source file, which
* requires a different approach than writing a string that can be used
* at runtime.
* <p>
* Escaped characters (using either the tilde or the backslash character)
* and special formations will be converted to native representations and
* any characters that need to be escaped will be so escaped. In
* particular, almost all usage of the backslash is compatible between
* Progress and Java, so only those constructs that must be different
* will be handled differently.
* <p>
* The following conversions will be made:
* <p>
* <pre>
* ~b to '\b' - backspace
* ~t to '\t' - horizontal tab
* ~n to '\n' - linefeed
* ~f to '\f' - formfeed
* ~r to '\r' - carriage return
* ~" to '\"' - double quote
* ~' to '\'' - single quote
* ~\ to '\\' - backslash
* ~~ to '~' - single tilde
* ~{ to '{' - left curly brace
* ~E to '\033' - the ASCII escape char
* ~nnn to '\nnn' - octal escape sequence (nnn is a 3 digit octal number)
* '' to '\'' - single quote (only when inside a
* string that was enclosed by single
* quotes)
* "" to '\"' - double quote (only when inside a
* string that was enclosed by double
* quotes)
* \~ to '~' - single tilde
* \\ to '\\' - backslash
* \{ to '{' - left curly brace
* \E to '\033' - the ASCII escape char
* </pre>
* <p>
* Any other escape sequence that is unrecognized will result in the
* tilde or backslash being dropped and the following character output
* unchanged which is the same behavior as Progress.
* <p>
* The final result when output will be output inside enclosing double
* quotes but there is no need for those quotes inside the AST form.
*
* @param progress
* The Progress compatible string as parsed from the source
* file.
*
* @return A valid Java string that can be written as a string literal.
*/
public static String progressToJavaString(String progress)
throws IllegalArgumentException
{
return progressToJavaString(progress, false, false);
}
/**
* Converts a Progress 4GL compatible string to the equivalent Java source string, by stripping
* off enclosing quotes/string options and converting the contents (including rewriting all
* escape sequences). Note that the output format is designed to be written to a Java source
* file, which requires a different approach than writing a string that can be used at runtime.
* <p>
* Escaped characters (using either the tilde or the backslash character) and special
* formations will be converted to native representations and any characters that need to be
* escaped will be so escaped. In particular, almost all usage of the backslash is compatible
* between Progress and Java, so only those constructs that must be different will be handled
* differently.
* <p>
* The following conversions will be made:
* <p>
* <pre>
* ~b to '\b' - backspace
* ~t to '\t' - horizontal tab
* ~n to '\n' - linefeed
* ~f to '\f' - formfeed
* ~r to '\r' - carriage return
* ~" to '\"' - double quote
* ~' to '\'' - single quote
* ~\ to '\\' - backslash
* ~~ to '~' - single tilde
* ~{ to '{' - left curly brace
* ~E to '\033' - the ASCII escape char
* ~nnn to '\nnn' - octal escape sequence (nnn is a 3 digit octal number)
* '' to '\'' - single quote (only when inside a string that was enclosed by single
* quotes)
* "" to '\"' - double quote (only when inside a string that was
* enclosed by double quotes)
* \~ to '~' - tilde
* \\ to '\\' - backslash
* \{ to '{' - left curly brace
* \E to '\033' - the ASCII escape char
* </pre>
* <p>
* Any other escape sequence that is unrecognized will result in the tilde or backslash being
* dropped and the following character output unchanged which is the same behavior as Progress.
* <p>
* The final result when output will be output inside enclosing double quotes but there is no
* need for those quotes inside the AST form.
* <p>
* <strong>Note:</strong><br>
* Do NOT SQL postprocess the resulting string before it is written to java source!
*
* @param progress
* The Progress compatible string as parsed from the source file.
* @param windows
* The flag indicating the OS is Windows for possible '\' characters.
* @param isPathName
* The flag indicating the special processing for path elements in Windows.
*
* @return A valid Java string that can be written as a string literal.
*/
public static String progressToJavaString(String progress, boolean windows, boolean isPathName)
throws IllegalArgumentException
{
if (progress == null)
{
throw new IllegalArgumentException("Invalid input: <null>.");
}
if (progress.isEmpty())
{
throw new IllegalArgumentException("Invalid input: string with no enclosing quotes.");
}
// are we starting with ' or "
char lead = progress.charAt(0);
String contents;
try
{
// this will always work as long as the input string is valid
contents = progress.substring(1, progress.lastIndexOf(lead));
}
catch (IndexOutOfBoundsException iob)
{
throw new IllegalArgumentException("Missing closing quote (" + lead + ").");
}
StringBuilder sb = new StringBuilder();
int end = contents.length();
// quick exit for the empty string
if (end == 0)
{
return "";
}
// by now the enclosing quotes are gone, as are any string options
// so we must convert the contents into the equivalent Java form
for (int i = 0; i < end; i++)
{
char current = contents.charAt(i);
char next;
switch (current)
{
case '\'':
case '\"':
// does the quote type match our enclosing quotes or not?
if (current == lead)
{
// this should only occur in 2 cases:
// 1. "doubled up" quotes and we need to "consume" the extra character
// 2. "~" or "\" or '~' or '\' which is the same as " or '
char la1 = contents.charAt(i + 1);
// the next char is an escape char
if (la1 == '~' || (!windows && la1 == '\\'))
{
// must be case 2
int k = i + 2;
// confirm the escape char is followed by a matching quote char
if (k < end && contents.charAt(k) == lead)
{
// ignore current and let the rest process normally
continue;
}
else
{
throw new IllegalArgumentException("Malformed string (expected a " +
"matching escaped quote char).");
}
}
i++;
next = readChar(contents, i);
// must be case 1, check to ensure we aren't smoking something here
if (next != current)
{
throw new IllegalArgumentException("Malformed string (expected doubled " +
"quote chars).");
}
}
// escape the double quote in java source as in Progress it safely stands alone
if (current == '\"')
{
sb.append('\\');
}
sb.append(current);
break;
case '~':
case '\\':
// on Windows we have to make special processing for '\' characters since they are
// not escape chars
if (windows && current == '\\')
{
// backslash must be doubled to get one unescaped \ char in a Java source string
sb.append("\\\\");
// check the next character without increasing the char counter
int j = i + 1;
// for Windows paths, an extra \ char on input is "eaten"
if (isPathName && j < end && readChar(contents, j) == '\\')
{
i++;
}
// we must drop out here so that normal escape processing is bypassed
break;
}
// this is an escape char case; there must always be a following character
i++;
next = readChar(contents, i);
// handle the special cases that don't have a one-to-one correspondence in Java
if (next == 'E')
{
// the special case for the ESC char
sb.append("\\033");
}
else
{
// only certain chars need to be AND in fact other than the
// single quote (which we deliberately omit here) only
// these MAY BE escaped in Java so drop the preceding
// escape char unless the following char is in this list
if (next == 'b' || next == 't' || next == 'n' ||
next == 'f' || next == 'r' || next == '\"' ||
next == '\\' || next == 'u' || (next >= '0' && next <= '7'))
{
// we must convert the leading tilde into the Java
// escape char because all of these must be escaped in Java
sb.append('\\');
}
// the ~UXXXXXX form has to be processed differently
if (next == 'U')
{
if (!isUnicodeInString(contents, i - 1))
{
throw new IllegalArgumentException("Malformed supplementary unicode " +
"escape (" +
contents.substring(i - 1) +
").");
}
// the direct use of substring here should be safe because of the testing
// above (there should be 6 hexadecimal digits to parse in the string)
sb.append(convertSupplementaryUnicode(contents.substring(i + 1, i + 7)));
i += 6;
}
else
{
// insert our following char (warning: this is a bit funky for the octal and
// UTF-16 unicode escapes because we don't write the whole thing at once,
// but the following chars will all trip the default processing and be
// passed through so this works even if it isn't as obvious as we would
// like)
sb.append(next);
}
}
break;
default:
// not an escaped char or special case
sb.append(current);
}
}
return sb.toString();
}
/**
* Converts a Java runtime string into one that can be encoded in Progress 4GL source code
* as a string literal. The contents are processed and any values that need escaping are
* handled and then the result is enclosed in double quote characters.
* <p>
* If the runtime Java string is a converted version of a 4GL string literal, then the result
* of this call is not guaranteed to generate the exact same literal representation. This is
* due to the fact that Progress has two escape characters (tilde and backslash) on input but
* on output to Java there is only a single escape character (backslash). For this reason, all
* mappings are to the tilde escaped version since this will work on UNIX/Linux and Windows
* in the 4GL.
* <p>
* The following conversions will be made:
* <p>
* <pre>
* '\b' to ~b - backspace
* '\t' to ~t - horizontal tab
* '\n' to ~n - linefeed
* '\f' to ~f - formfeed
* '\r' to ~r - carriage return
* '\"' to ~" - double quote
* '\'' to ~' - single quote
* '\\' to ~\ - backslash
* '~' to ~~ - tilde
* '{' to ~{ - left curly brace
* '\033' to ~E - the ASCII escape char
* control chars to ~nnn - octal escape sequence (nnn is a 3 digit octal number)
* </pre>
* <p>
* The final result when output will be output inside enclosing double quotes.
*
* @param contents
* The Java runtime string which should be encoded.
*
* @return A valid Progress string that can be written as a string literal in 4GL source
* code.
*/
public static String javaRuntimeToProgressSourceString(String contents)
throws IllegalArgumentException
{
if (contents == null)
{
throw new IllegalArgumentException("Invalid input: <null>.");
}
StringBuilder sb = new StringBuilder();
int end = contents.length();
for (int i = 0; i < end; i++)
{
char current = contents.charAt(i);
switch (current)
{
case '\'':
case '\"':
case '~':
case '\\':
case '{':
sb.append('~');
sb.append(current);
break;
case '\b':
sb.append("~b");
break;
case '\f':
sb.append("~f");
break;
case '\n':
sb.append("~n");
break;
case '\r':
sb.append("~r");
break;
case '\t':
sb.append("~t");
break;
case '\033':
// the special case for the ESC char
sb.append("~E");
break;
default:
// check for octal encoding (needed for non-printable stuff, but the 4GL only
// allows for a single byte to be encoded this way, so we limit it to the set
// of 3 digit octal values)
if (Character.isISOControl(current))
{
if ((int)current < 256)
{
sb.append('~');
sb.append(Integer.toOctalString(current));
}
else
{
// TODO: what to do here? for now we just skip this char
}
}
else
{
// not an escaped char or special case
sb.append(current);
}
}
}
// we can always encode with containing double quotes because we consistently use tildes
// to escape all single and double quotes
return "\"" + sb.toString() + "\"";
}
/**
* Parses a Progress 4GL compatible string, by stripping off enclosing quotes/string options
* and converting the contents (including rewriting escape sequences).
* <p>
* Escaped characters (using either the tilde or the backslash character) and special
* formations will be converted to native representations and any characters that need to be
* escaped will be so escaped.
* <p>
* The following conversions will be made:
* <p>
* <pre>
* ~b to '\b' - backspace
* ~t to '\t' - horizontal tab
* ~n to '\n' - linefeed
* ~f to '\f' - formfeed
* ~r to '\r' - carriage return
* ~" to '"' - double quote
* ~' to ''' - single quote
* ~\ to '\' - backslash
* ~~ to '~' - single tilde
* ~{ to '{' - left curly brace
* ~E to '\033' - the ASCII escape char
* ~nnn to '\nnn' - octal escape sequence (nnn is a 3 digit octal number)
* '' to ''' - single quote (only when inside a string that was enclosed by single
* quotes)
* "" to '"' - double quote (only when inside a string that was
* enclosed by double quotes)
* \~ to '~' - tilde
* \\ to '\' - backslash
* \{ to '{' - left curly brace
* \E to '\033' - the ASCII escape char
* </pre>
* <p>
* Any other escape sequence that is unrecognized will result in the tilde or backslash being
* dropped and the following character output unchanged which is the same behavior as Progress.
* <p>
* The final result must be <code>encodeToJavaSource</code> when it will be output inside
* enclosing double quotes in a java source file.
*
* @param progress
* The Progress compatible string as parsed from the source file.
* <code>true</code> if additional escaping for writing the result into a java source
* file is needed. If <code>false</code> the result is the normal text representation
* of the literal used at runtime or if supplementary processing is needed, in which
* case must be applied.
*
* @return the string value of the input string, unencoded.
*/
public static String parseProgressCharLiteral(String progress, boolean windows)
{
if (progress == null)
{
throw new IllegalArgumentException("Invalid input: <null>.");
}
// remember if we are starting with ' or "
char lead = progress.charAt(0);
String contents;
try
{
// this will always work as long as the input string is valid
contents = progress.substring(1, progress.lastIndexOf(lead));
}
catch (IndexOutOfBoundsException iob)
{
throw new IllegalArgumentException("Missing closing quote (" + lead + ").");
}
int end = contents.length();
if (end == 0)// quick exit for the empty string
{
return "";
}
StringBuilder sb = new StringBuilder();
// the enclosing quotes are gone, so we must parse the contents to remove progress escapes
for (int i = 0; i < end; i++)
{
char current = contents.charAt(i);
char next;
switch (current)
{
case '\'':
case '\"':
// does the quote type match our enclosing quotes or not?
if (current == lead)
{
// this should only occur if we have "doubled up" quotes and we need to
// "consume" the extra character
i++;
next = readChar(contents, i);
// check to ensure we aren't smoking something here
if (next != current)
{
throw new IllegalArgumentException("Malformed string.");
}
}
sb.append(current);
break;
case '\\':
if (windows)
{
sb.append(current);
break;
}
// else break trough next case '~', on non-windows \ is also escape character
case '~':
// after a progress escape, there must always be a following character
i++;
next = readChar(contents, i);
// handle the special cases that don't have a one-to-one correspondence in Java
switch (next)
{
case 'E':
// the special case for the ESC char
sb.append(033);
break;
case 'b':
sb.append('\b');
break;
case 't':
sb.append('\t');
break;
case 'n':
sb.append('\n');
break;
case 'f':
sb.append('\f');
break;
case 'r':
sb.append('\r');
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
int octVal = next - '0';
if (i + 1 < end)
{
char secondDigit = contents.charAt(i + 1);
if (secondDigit >= '0' && secondDigit <= '7')
{
i++; // consume the second digit
octVal = octVal * 8 + (secondDigit - '0');
if (i + 1 < end)
{
char thirdDigit = contents.charAt(i + 1);
if (thirdDigit >= '0' && thirdDigit <= '7')
{
i++; // consume the third digit
octVal = octVal * 8 + (thirdDigit - '0');
// TODO: replace with a 'while' for multibyte char support ?
}
}
}
}
sb.append((char) octVal);
break;
default:
sb.append(next);
break;
} // end of switch (next)
break;
default:
// not an escaped char or special case
sb.append(current);
} // switch (current)
}
return sb.toString();
}
/**
* Converts a runtime string to text representation of a java string literal. The following
* characters are escaped: " and \so they became: \" and respectively \\. The compiler (java
* lexer) will convert them back when the source file is compiled.
*
* The whitespace characters that can be escaped in Java are escaped using \. Characters
* outside the ASCII printable are encoded as octal representation.
*
* @param data
* The runtime string.
*
* @return the string escaped, ready to be written as a java string literal.
*/
public static String encodeToJavaSource(String data)
{
if (data.isEmpty())
{
// quick exit for the empty string
return "";
}
StringBuilder sb = new StringBuilder();
int end = data.length();
for (int i = 0; i < end; i++)
{
char current = data.charAt(i);
switch (current)
{
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\b':
sb.append("\\b");
break;
case '\t':
sb.append("\\t");
break;
case '\r':
sb.append("\\r");
break;
case '\n':
sb.append("\\n");
break;
case '\f':
sb.append("\\f");
break;
default:
if (current > 255)
{
// character is unicode, above ascii interval, output it using hexadecimal
String hexStr = Integer.toHexString(current);
while (hexStr.length() < 4)
{
hexStr = "0" + hexStr;
}
sb.append("\\u").append(hexStr);
}
else if (current < ' ' /*#32*/ || current > '~' /* #127 */)
{
// ascii character is not 'safe', to avoid issues, output it as octal
String octStr = Integer.toOctalString(current);
while (octStr.length() < 3)
{
octStr = "0" + octStr;
}
sb.append("\\").append(octStr);
}
else
{
// no processing necessary for the remaining chars
sb.append(current);
}
break;
}
}
return sb.toString();
}
/**
* Convert the Progress 4GL style character type format string into a form that is easier
* to process. All non-escaped 'A(n)' forms are expanded into n instances of A. All
* recognized (non-fill) format characters are lowercased in the output to make comparisons
* easier.
* <p>
* DO NOT directly use the result of this expansion for calculation of the format
* length without also processing any the unescaped tildes that are present in the
* resulting returned format string. If you are looking for the format length, please
* use the <code>formatLength()</code> method of this class.
*
* @param fmt
* The Progress 4GL format string for a character data type.
*
* @return The simplified/expanded format string which may still have unescaped tildes.
*/
public static String preprocessFormatString(String fmt)
{
final StringBuilder sb = new StringBuilder();
int len = fmt.length();
char c = (char) 0;
char last;
boolean nonFill = false;
for (int i = 0; i < len; i++)
{
last = c;
c = fmt.charAt(i);
switch (c)
{
case 'x':
case 'X':
case 'n':
case 'N':
case 'a':
case 'A':
case '!':
case '9':
if (last != '~')
{
// these are recognized valid
nonFill = true;
sb.append(Character.toLowerCase(c));
}
else
{
// any fill chars just go into the output unchanged
nonFill = false;
sb.append(c);
}
break;
case '(':
if (last != '~' && nonFill)
{
// find the end of the parens
int j = fmt.indexOf(')', i + 1);
if (j < 0)
{
String spec = "Character format %s is incomplete";
String err = String.format(spec, fmt);
ErrorManager.recordOrThrowError(164, err);
// this is the safest thing to return (any results
// should be undone anyway)
return "";
}
// how many repeats do we have?
String nrpt = fmt.substring(i+1, j);
int num = 0;
try
{
num = "".equals(nrpt.trim()) ? 0 : Integer.parseInt(nrpt);
}
catch (NumberFormatException e)
{
int nc;
for(nc = i + 1; nc < j; nc++)
{
if (!Character.isDigit(fmt.charAt(nc)))
{
break;
}
}
ErrorManager.recordOrThrowError(22,
String.format("Character number %d of format %s is invalid", nc, fmt));
}
// get our last char
char ch = Character.toLowerCase(last);
// create and add the resulting string which takes advantage
// of the fact that the last char is already in the buffer
// and so we only need to repeat (num - 1) times
sb.append(StringHelper.repeatChar(ch, num - 1));
// move our index up to the end of the section, it will
// be incremented past/tested against the string length
// by the for loop itself
i = j;
}
else
{
if (last == '~')
{
// replace the tilde with this char
sb.setCharAt(sb.length() - 1, c);
}
else
{
// we must be following a fill char so this is a fill
// char too
sb.append(c);
}
}
nonFill = false;
break;
default:
// any fill chars just go into the output unchanged
nonFill = false;
sb.append(c);
}
}
return sb.toString();
}
/**
* Returns 4GL literal options from the supplied Progress string. The options are returned without the
* leading colon.
*
* @param progress
* Unprocessed progress string.
*
* @return See above.
*/
public static String getStringOptions(String progress)
{
if (progress == null)
{
throw new IllegalArgumentException("Invalid input: <null>.");
}
// remember if we are starting with ' or "
char lead = progress.charAt(0);
// this will always work as long as the input string is valid
int closingQuote = progress.lastIndexOf(lead);
if (closingQuote == 0)
{
throw new IllegalArgumentException("Missing closing quote (" + lead + ").");
}
String opts = progress.substring(closingQuote + 1);
int colon = opts.indexOf(':');
if (colon >= 0)
{
return opts.substring(colon + 1);
}
else
{
return null;
}
}
/**
* Returns string content from the supplied Progress string. I.e. enclosing quotes and any 4GL string
* literal options with the colon delimiter are not returned in the result.
*
* @param progress
* Unprocessed progress string.
*
* @return See above.
*/
public static String getStringContent(String progress)
{
if (progress == null)
{
throw new IllegalArgumentException("Invalid input: <null>.");
}
// remember if we are starting with ' or "
char lead = progress.charAt(0);
// this will always work as long as the input string is valid
int closingQuote = progress.lastIndexOf(lead);
if (closingQuote == 0)
{
throw new IllegalArgumentException("Missing closing quote (" + lead + ").");
}
return progress.substring(1, closingQuote);
}
/**
* Parses the supplied Progress string and returns {@code true} if the string contains the 'U' 4GL
* string literal option, i.e. the string is untransatable.
*
* @param progress
* Unprocessed progress string to test.
*
* @return See above.
*/
public static boolean isUntranslatable(String progress)
{
String opts = getStringOptions(progress);
return opts != null && (opts.indexOf('u') != -1 || opts.indexOf('U') != -1);
}
/**
* This method allows the explicit bypass of embedded null character protections that are
* in place for all construction and assignment of text values. This is needed because
* of locations in the 4GL that do allow embedded null characters to leak into variables.
* <p>
* The data will be converted to a string and stored as the instance's data. DO NOT USE
* THIS METHOD except for duplicating the 4GL "null character leaking" behavior.
*
* @param data
* The bytes to force in.
*/
void forceBytes(byte[] data)
{
// deliberately bypasses embedded null character protections
this.value = new String(data, Charset.forName(I18nOps.getJavaCPInternal()));
this.unknown = false;
this.javaSpacifyNull = true;
}
/**
* This method allows the explicit bypass of embedded null character protections that are
* in place for all construction and assignment of text values. This is needed because
* of locations in the 4GL that do allow embedded null characters to leak into variables.
* <p>
* The data will be converted to a string and stored as the instance's data. DO NOT USE
* THIS METHOD except for duplicating the 4GL "null character leaking" behavior.
*
* @param data
* The bytes to force in.
* @param length
* The number of bytes to copy.
*/
void forceBytes(byte[] data, int length)
{
// deliberately bypasses embedded null character protections
this.value = new String(data, 0, length, Charset.forName(I18nOps.getJavaCPInternal()));
this.unknown = false;
this.javaSpacifyNull = true;
}
/**
* Returns a clone of the current instance which has all <code>null</code>
* characters removed from its data (in particular, the data is truncated
* starting at any <code>null</code> character).
*
* @return An instance which has no embedded <code>null</code> characters.
*/
public character safeDuplicate()
{
character ch = (character) duplicate();
if (!unknown)
{
// this assignment protects from embedded null characters
ch.value = javaSpacifyNull ? safeValue() : value;
ch.javaSpacifyNull = false;
}
return ch;
}
/**
* Returns the instance's value safely truncated at any <code>null</code>
* byte.
*
* @return A value with no <code>null</code> bytes.
*/
protected String safeValue()
{
return javaTruncateNull(value);
}
/**
* Obtain the length (in bytes) of this BDT will use when serialized.
*
* @return the length of this BDT will use when serialized.
*/
@Override
public int getSize()
{
if (isUnknown())
{
return 1;
}
int strLen = value.length();
// the documentation say 240 but, from tests, the threshold is actually 230
return 1 + (strLen >= 230 ? (2 + strLen) : strLen);
}
/**
* Sets the state (data and unknown value) of this instance based on the state of the passed
* instance.
* <p>
* It will also check the maximum size is not exceeded.
*
* @param value
* The instance from which to copy state.
*/
@Override
public void assign(Text value)
{
// the value may be a proxy; make sure to unwrap and work with the real value
if (BaseDataType.isProxy(value))
{
assign(value.val());
return;
}
if (value instanceof longchar && TextOps.byteLengthOf(value) > 31991 && forceLengthLimit())
{
ErrorManager.recordOrThrowError(9324, "Attempt to exceed maximum size of a CHARACTER variable");
return;
}
super.assign(value);
}
/**
* Return the default display format string for this type.
*
* @return The default format string.
*/
public String defaultFormatString()
{
return defaultFormat;
}
/**
* Calculate the length of any formatted value (the text form of the value)
* using this format string.
*
* @param fmt
* The format string.
*
* @return The length in characters of the any resulting formatted
* value.
*/
public int formatLength(String fmt)
{
if (fmt == null)
{
fmt = defaultFormatString();
}
String normalized = preprocessFormatString(fmt);
// this creates a fake result string that fills all valid char positions with a space,
// drops unescaped tildes and outputs fill characters at that proper positions; the
// result can be used to detect the actual length of data output using this format
String blanked = processDataString("", normalized);
return blanked.length();
}
/**
* Does the same as standard <code>clone()</code> method but returns an
* instance of <code>BaseDataType</code> and doesn't throw the
* <code>CloneNotSupportedException</code>.
*
* @return A clone of this instance.
*/
public character duplicate()
{
return new character(this);
}
/**
* Creates a new instance of the same type that represents the
* default initialized value.
*
* @return An instance that represents the default value.
*/
public BaseDataType instantiateDefault()
{
return new character("");
}
/**
* Creates a string representation of the instance data in a form that is
* compatible with the <code>MESSAGE</code> language statement. If the
* instance represents the <code>unknown value</code>, a '?' will be
* returned.
*
* @return The 'message' formatted string.
*/
public String toStringMessage()
{
// quick-out when unknown
if (unknown)
return "?";
return javaTruncateNull(getValue());
}
/**
* Creates a string representation of the instance data using the 'export'
* format. For the character data type the entire string is returned
* enclosed in a pair of double quotes. If the instance represents the
* <code>unknown value</code>, a '?' will be returned. Any instances of
* double quotes inside the string will be doubled up on output.
*
* @return The 'export' formatted string.
*/
public String toStringExport()
{
// quick-out when unknown (no user override for that case)
if (unknown)
return "?";
// open the string
StringBuilder sb = new StringBuilder("\"");
// truncate at any embedded null
String txt = javaSpacifyNull ? javaTruncateNull(value) : value;
// add the contents, doubling up all instance of double quote chars
sb.append(txt.replaceAll("\"", "\"\""));
// close the string
sb.append("\"");
return sb.toString();
}
/**
* Creates a string representation of the instance data with the default
* Progress 4GL format of 'x(8)'. If the instance represents the
* <code>unknown value</code>, a '?' will be returned.
*
* @return The formatted string.
*/
public String toString()
{
return toString(null);
}
/**
* Creates a string representation of the instance data using the user-
* specified Progress 4GL format string. If the passed-in format is
* <code>null</code>, then a default format string of 'x(8)' will be
* used. If the instance represents the <code>unknown value</code>,
* a '?' will be returned.
*
* @param fmt
* The Progress 4GL format string for a character data type.
*
* @return The formatted string.
*/
public String toString(String fmt)
{
// quick-out when unknown (no user override for that case)
if (unknown)
return "?";
String clean = null;
// implement 'default' behavior
if (fmt == null)
{
fmt = defaultFormat;
// default case doesn't spacify
clean = value;
}
else
{
// for some reason, when there is an explicit format string, the value is spacified
clean = javaSpacifyNull ? javaSpacifyNull(value) : value;
}
// make the format string easier to process
String format = preprocessFormatString(fmt);
// trim trailing whitespace off the value
String trimmed = StringHelper.safeTrimTrailing(clean);
// build the output string and return it
return processDataString(trimmed, format);
}
/**
* Remove trailing spaces.
*
* @return This character object with trancated value
*/
public character truncateValue()
{
value = StringHelper.safeTrimTrailingSpaces(value);
return this;
}
/**
* Get the default initialization for an extent variable of this type.
*
* @return See above.
*/
@Override
protected BaseDataType instantiateDefaultExtent()
{
return new character("");
}
/**
* Check if length limit must be enforced in {@link #assign(Text)}.
*
* @return Always true.
*/
protected boolean forceLengthLimit()
{
return true;
}
/**
* Convert the input data into an output string that is formatted as
* specified by the Progress 4GL style character type format string.
* <p>
* Recognized format specifier characters are x, X, n, N, a, A, 9 and
* !. The presence of any unescaped (with tilde) instance of one of these
* characters in the format string means that should there be a
* corresponding character (at the same format specifier index) in the
* data string, it will be copied to the output string at that same
* position. No transformation of the data is done except for the !
* specifier which causes the data character to be uppercased before
* output.
* <p>
* Any non-recognized format specifier and any escaped character
* (including escaped format specifiers) are considered user-defined
* "fill" characters. These characters are copied from the format
* string (not the data string) into the output string. They are
* copied unchanged. If present in the input string they are ignored
* as non-significant characters. Any unescaped tildes are dropped
* (not copied to the output).
* <p>
* The input data will be copied to a location in the output string based
* on the simple index position in the input data AND the relative position
* of each format specifier. The presence of fill characters does not
* change the relative position of format specifier characters. This means
* that the 1st character in the input string will be copied on output
* to the location of the 1st format specifier character in the format
* string, which may not be the first position in the output string (if
* there are fill characters in the format string before the first format
* specifier). The 2nd character in the input string will be copied on
* output to the location of the 2nd format specifier, which may not
* immediately follow the 1st data character in the output string if there
* are intervening fill characters between the first and second format
* specifiers.
* <p>
* If there is less data characters (in the input string) than format
* specifier characters (in the format string), then a ' ' (space) will
* be output for each trailing format specifier position which has no
* data to be copied. This is a kind of padding.
* <p>
* If there are more data characters (in the input string) than format
* specifier characters (in the format string), then the output string
* will be truncated, not copying those extra data characters (in the
* input string) to the output string.
* <p>
* The output string length will always be equivalent to the size of the
* format string less any dropped tilde escape characters.
*
* @param data
* The input string.
* @param fmt
* The Progress 4GL format string for a character data type.
*
* @return The output (formatted) string.
*/
static String processDataString(String data, String fmt)
{
StringBuilder sb = new StringBuilder();
int dlen = data.length();
int flen = fmt.length();
char c = (char) 0;
char last;
// iterate through all characters in the format string (the result
// will be the same length as the *real* number of characters in the
// format string --> unescaped tildes don't count)
for (int j = 0, i = 0; i < flen; i++)
{
// remember the last character to track if it is a tilde
last = c;
// get next format string character
c = fmt.charAt(i);
switch (c)
{
case '~':
if (last == '~')
{
// we leave a single tilde in the output and reset our
// character to ensure that tilde processing is reset
sb.append(c);
c = (char) 0;
}
break;
case 'x':
case 'X':
case 'n':
case 'N':
case 'a':
case 'A':
case '9':
case '!':
// these are recognized valid format characters
if (last == '~')
{
// this was escaped so it is a fill char, this just goes
// into the output unchanged
sb.append(c);
}
else
{
if (j < dlen)
{
// there is still data to output
// only the ! causes the data to be transformed before output,
// all others cause the unchanged char to be output
char out = data.charAt(j);
if (c == '!')
out = Character.toUpperCase(out);
sb.append(out);
j++;
}
else
{
// no more data, append a space to pad the end
sb.append(' ');
}
}
break;
default:
// must be an unescaped fill char, add to the output unchanged
sb.append(dlen == 0 ? ' ' : c);
// skip the fill char in the input string if present
if (j < dlen && data.charAt(j) == c)
{
j++;
}
}
}
return sb.toString();
}
/**
* Return a character instance that is comprised of <code>num</code> concatenated copies of a
* given string.
*
* @param ch
* The string to be repeated.
* @param num
* The number of times to repeat.
*
* @return The resulting string or <code>unknown value</code> if any input is
* <code>unknown</code> value or an empty string if <code>num</code> < 1.
*/
private static character fillWorker(character ch, int num, boolean cs)
{
if (num < 1)
{
return new character("", cs);
}
StringBuilder sb = new StringBuilder();
String str = ch.getValue();
// this does copy null chars if they are present, which is the 4GL way
for (int i = 0; i < num; i++)
{
sb.append(str);
}
return new character(sb.toString(), cs);
}
/**
* This is the core quoter function implementation. This puts the text value of the variable
* between double quotes if the value is not unknown, or it will replace it with the
* <code>?</code> character if the value is <code>unknown</code>. If the variable is a
* <code>character</code> type, any contained double quote character will be doubled (another
* double quote character will be added next to it - Progress behavior).
* <p>
* The caller can provide replacement characters to use instead of the double quote character
* (") and instead of the default <code>unknown</code> character <code>?</code>.
*
* @param var
* The <code>BaseDataType</code> variable to operate upon.
* @param customQuote
* The custom quoter character to be used. If <code>null</code>, the default is the
* double quote character.
* @param customNull
* The custom unknown text replacement to be used. If <code>null</code> the default
* is '?'.
*
* @return The resulting quoted string or <code>unknown</code>, if the input is
* <code>unknown</code>.
*/
private static character quoterWorker(BaseDataType var, String customQuote, String customNull)
{
// custom null character
String nullVal = (customNull != null) ? customNull : "?";
// custom quoter character (in the 4GL if given a custom character that isn't " or ' it will
// take the first letter as the custom quote char
String quoteVal = (customQuote != null) ? customQuote.substring(0, 1) : "\"";
String result = nullVal;
boolean caseSensitive = false;
if (!var.isUnknown())
{
StringBuilder sb = new StringBuilder();
sb.append(quoteVal);
if (var instanceof character)
{
String in = quoteVal;
String out = quoteVal + quoteVal;
// quote character will be doubled where found inside contents, otherwise
// it is just the contents truncated at any null chars
character charVar = (character) var;
sb.append(javaTruncateNull(charVar.getValue()).replaceAll(in, out));
caseSensitive = charVar.isCaseSensitive();
}
else if (var instanceof date)
{
// date format result of quoter is 99/99/9999
date dateVar = (date) var;
sb.append(dateVar.toString("99/99/9999"));
}
else
{
// the rest of the data types format result as the default message format
sb.append(var.toStringMessage());
}
sb.append(quoteVal);
result = sb.toString();
}
return new character(result, caseSensitive);
}
/**
* Converts 6 hexadecimal digits (representing a single UTF-32 supplementary character)
* into the Java source representation of such characters as two UTF-16 encoded high/low
* supplementary characters. If the parsed UTF-32 character can be represented as a
* single UTF-16 escape sequence (from the basic multilingual plane), then that form
* will be returned.
*
* @param txt
* Text with 6 hexadecimal digits. DO NOT call this without exactly 6 hexadecimal
* digits!
*
* @return The properly encoded unicode in escaped source code form.
*/
private static String convertSupplementaryUnicode(String txt)
{
String encoded = null;
int utf32 = Integer.valueOf(txt, 16);
if (Character.isSupplementaryCodePoint(utf32))
{
char high = Character.highSurrogate(utf32);
char low = Character.lowSurrogate(utf32);
encoded = String.format("\\u%04X\\u%04X", (int) high, (int) low);
}
else
{
if (Character.isBmpCodePoint(utf32))
{
// short cut: the input must have been in the form 00XXXX so we just use the last 4
encoded = "\\u" + txt.substring(2);
}
else
{
throw new IllegalArgumentException("Malformed supplementary char (" + txt + ").");
}
}
return encoded;
}
/**
* Checks if the given Progress string has unicode character embedded by '~uXXXX',
* '~UXXXXXX' (or the two '\' escaped alternatives) formatted string. Each X is a
* hexadecimal digit.
*
* @param strToTest
* The Progress compatible string to test.
* @param startNdx
* The character position inside the string to start from.
*
* @return <code>true</code> if Unicode char is matched, <code>false</code> otherwise.
*/
private static boolean isUnicodeInString(String strToTest, int startNdx)
{
// get the string size
int strLen = strToTest.length();
// we must check the next 2 chars to know the following size; make sure we have data
if (strLen - startNdx < 3)
{
return false;
}
// the first char is expected as '\' or '~'
char nextCh = strToTest.charAt(startNdx);
if (nextCh != '\\' && nextCh != '~')
{
return false;
}
// the second char is expected as 'u' (basic multi-lingual plane) or 'U' (supplementary
// chars)
nextCh = strToTest.charAt(startNdx + 1);
if (nextCh != 'u' && nextCh != 'U')
{
return false;
}
int sz = (nextCh == 'u') ? 6 : 8;
// the string size should be 6+ from startNdx
if (strLen - startNdx < sz)
{
return false;
}
// the next chars are expected to be hexadecimal digits
for (int i = 2; i < sz; i++)
{
nextCh = strToTest.charAt(startNdx + i);
if ((nextCh >= '0' && nextCh <= '9') ||
(nextCh >= 'A' && nextCh <= 'F') ||
(nextCh >= 'a' && nextCh <= 'f'))
{
continue;
}
else
{
return false;
}
}
return true;
}
/**
* Used as comparator for TreeMap using <character>
*/
public static final Comparator<character> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator implements Comparator<character>
{
public int compare(character s1, character s2)
{
return s1.compareTo(s2);
}
}
/**
* Encapsulates the logic of storing and executing a set of tests using
* a minimum set of input (2 string operands and a set of boolean results
* that are to be expected).
*/
private static class TestCase
{
/**
* Use this static flag to switch from unix-escapes to windows mode for backslash during the
* initialization and then during the tests themselves.
*/
private static boolean unixEscapes = true;
/** Operand 1. */
character op1 = null;
/** Operand 2. */
character op2 = null;
/** Expected test results for begins. */
boolean begins;
/** Expected test results for equals. */
boolean eq;
/** Expected test results for notEquals. */
boolean ne;
/** Expected test results for isGreaterThan. */
boolean gt;
/** Expected test results for isLessThan. */
boolean lt;
/** Expected test results for isGreaterThanOrEqual. */
boolean ge;
/** Expected test results for isLessThanOrEqual. */
boolean le;
/** Expected test results for matches. */
boolean matches;
/** Expected test results for case-sensitive begins. */
boolean cBegins;
/** Expected test results for case-sensitive equals. */
boolean cEq;
/** Expected test results for case-sensitive notEquals. */
boolean cNe;
/** Expected test results for case-sensitive isGreaterThan. */
boolean cGt;
/** Expected test results for case-sensitive isLessThan. */
boolean cLt;
/** Expected test results for case-sensitive isGreaterThanOrEqual. */
boolean cGe;
/** Expected test results for case-sensitive isLessThanOrEqual. */
boolean cLe;
/** Expected test results for case-sensitive matches. */
boolean cMatches;
/** Save the original Progress string before pre processing in constructor. */
private String op14gl;
/** Save the original Progress string before pre processing in constructor. */
private String op24gl;
TestCase(String op1 , String op2 , boolean begins , boolean eq,
boolean ne , boolean gt , boolean lt , boolean ge,
boolean le , boolean matches, boolean cBegins, boolean cEq,
boolean cNe, boolean cGt , boolean cLt , boolean cGe,
boolean cLe, boolean cMatches)
{
op14gl = op1;
op24gl = op2;
// simulate the resulting runtime strings
String s1 = progressToJavaString("'" + op1 + "'", !unixEscapes, false);
String s2 = progressToJavaString("'" + op2 + "'", !unixEscapes, false);
this.op1 = new character(StringHelper.processEscapes(s1));
this.op2 = new character(StringHelper.processEscapes(s2));
// check internal implementation
final String ppl1 = parseProgressCharLiteral("'" + op14gl + "'", !unixEscapes);
if (!ppl1.equals(this.op1.toStringMessage()))
{
System.out.println("Failed to convert [" + op14gl + "] [" + ppl1 + "]");
}
final String ppl2 = parseProgressCharLiteral("'" + op24gl + "'", !unixEscapes);
if (!ppl2.equals(this.op2.toStringMessage()))
{
System.out.println("Failed to convert [" + op24gl + "] [" + ppl2 + "]");
}
this.begins = begins;
this.eq = eq;
this.ne = ne;
this.gt = gt;
this.lt = lt;
this.ge = ge;
this.le = le;
this.matches = matches;
this.cBegins = cBegins;
this.cEq = cEq;
this.cNe = cNe;
this.cGt = cGt;
this.cLt = cLt;
this.cGe = cGe;
this.cLe = cLe;
this.cMatches = cMatches;
}
boolean test()
{
boolean flag = true;
op1.setCaseSensitive(false);
if (TextOps._begins(op1, op2) != begins)
{
flag = false;
System.out.println("begins failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(true);
if (TextOps._begins(op1, op2) != cBegins)
{
flag = false;
System.out.println("begins(c) failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(false);
if (CompareOps.equals(op1, op2) != eq)
{
flag = false;
System.out.println("isEqual failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(true);
if (CompareOps.equals(op1, op2) != cEq)
{
flag = false;
System.out.println("isEqual(c) failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(false);
if (CompareOps.notEqual(op1, op2) != ne)
{
flag = false;
System.out.println("notEqual failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(true);
if (CompareOps.notEqual(op1, op2) != cNe)
{
flag = false;
System.out.println("notEqual(c) failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(false);
if (CompareOps.greaterThan(op1, op2) != gt)
{
flag = false;
System.out.println("isGreaterThan failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(true);
if (CompareOps.greaterThan(op1, op2) != cGt)
{
flag = false;
System.out.println("isGreaterThan(c) failure for \"" + op1 +
"\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(false);
if (CompareOps.lessThan(op1, op2) != lt)
{
flag = false;
System.out.println("isLessThan failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(true);
if (CompareOps.lessThan(op1, op2) != cLt)
{
flag = false;
System.out.println("isLessThan(c) failure for \"" + op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(false);
if (CompareOps.greaterThanOrEqual(op1, op2) != ge)
{
flag = false;
System.out.println("isGreaterThanOrEqual failure for \"" + op1 +
"\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(true);
if (CompareOps.greaterThanOrEqual(op1, op2) != cGe)
{
flag = false;
System.out.println("isGreaterThanOrEqual(c) failure for \"" +
op1 + "\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(false);
if (CompareOps.lessThanOrEqual(op1, op2) != le)
{
flag = false;
System.out.println("isLessThanOrEqual failure for \"" + op1 +
"\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(true);
if (CompareOps.lessThanOrEqual(op1, op2) != cLe)
{
flag = false;
System.out.println("isLessThanOrEqual(c) failure for \"" + op1 +
"\" and \"" + op2 + "\"");
}
op1.setCaseSensitive(false);
if (TextOps._matches(op1, op2, !unixEscapes) != matches)
{
flag = false;
System.out.println("matches failure for \"" + this.op1 +
"\" (\"" + op1 + "\") and \"" + this.op2 +
"\" (\"" + op2 + "\")");
}
op1.setCaseSensitive(true);
if (TextOps._matches(op1, op2, !unixEscapes) != cMatches)
{
flag = false;
System.out.println("matches(c) failure for \"" + this.op1 +
"\" (\"" + op1 + "\") and \"" + this.op2 +
"\" (\"" + op2 + "\")");
}
return flag;
}
}
/**
* An immutable character type, to be used for constants cached in {@link character#CHARACTER_CONSTANTS}.
* <p>
* Instances of this type are serializable.
*/
public static class characterConstant
extends character
implements BaseDataTypeConstant
{
/** Pre-calculated hash code. */
private final int hashCode;
/**
* Default constructor, creates an instance that represents the unknown value.
*/
public characterConstant()
{
// unknown
hashCode = 0;
unknown = true;
value = null;
}
/**
* Default constructor, creates an instance that for the given value.
*/
public characterConstant(String value)
{
if (value != null)
{
value = javaSpacifyNull(value);
}
this.javaSpacifyNull = false;
this.unknown = value == null;
this.value = value;
this.hashCode = unknown ? 0 : value.hashCode();
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
public void deepAssign(Text value)
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
public void setCaseSensitive(boolean caseSens)
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
public void setUnknown()
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
public void setValue(String value)
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
void forceBytes(byte[] data)
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
void forceBytes(byte[] data, int length)
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
public character truncateValue()
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
public void assign(BaseDataType value)
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
public void assign(Text value)
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable character instance.
*/
@Override
public Text replaceEntry(double i, character delimit, String replace)
{
throw new UnsupportedOperationException("This is an immutable character instance.");
}
/**
* Get the pre-calculated {@link #hashCode}.
*
* @return Hash code value for this object instance.
*/
@Override
public int hashCode()
{
return hashCode;
}
/**
* Equality test.
* <p>
* Only character constants can be compared. If trying to compare against a {@link character}, an
* {@link IllegalStateException} will be thrown - constants can't escape the cache, real character
* instances must be used.
*
* @param ref
* The instance to test.
*
* @return See above.
*/
@Override
public boolean equals(Object ref)
{
if (ref == this)
{
return true;
}
if (!(ref instanceof characterConstant))
{
if (ref instanceof character)
{
// these instances can't 'escape' outside the cache - they need to be assigned immediately.
throw new IllegalStateException("Can not compare character with characer constant!");
}
return false;
}
characterConstant cc = (characterConstant) ref;
return cc.unknown == this.unknown && cc.value.equals(this.value);
}
}
/**
* Command line interface which provides a test harness to regression
* test this class. No parameters are necessary.
*/
public static void main(String[] args)
{
// if still true after all tests, everything passed
boolean flag = true;
TestCase.unixEscapes = true; // the TestCase constructor uses it
TestCase linuxTests[] =
{
new TestCase(" hello " , "hello" , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase("hello" , "hello " , false, true , false, false,
false, true , true , false,
false, true , false, false,
false, true , true , false),
new TestCase("hello " , "hello" , true , true , false, false,
false, true , true , false,
true , true , false, false,
false, true , true , false),
new TestCase("hELLo " , "hello" , true , true , false, false,
false, true , true , false,
false, false, true , false,
true , false, true , false),
new TestCase("hello " , "heLLo" , true , true , false, false,
false, true , true , false,
false, false, true , true ,
false, true , false, false),
new TestCase(" hello " , ".ell." , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" hello " , ".*ell*" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" hello~hel", "..ell.\\*" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" hel~lo " , " hell*" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true ),
new TestCase(" ajaj\\aj " , "*aj*" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj\\aj " , "*aj\\aj " , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj\\aj " , "*ajaj " , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj\\\\aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj\\\\\\aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj\\\\aj " , "*aj\\aj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj\\\\aj ", "*aj\\\\aj ", false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj\\\\aj ", " ajaj\\\\aj ", true , true , false, false,
false, true , true , false,
true , true , false, false,
false, true , true , false),
new TestCase(" ajaj~aj " , "*aj*" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj~aj " , "*aj~~aj " , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj~aj " , "*ajaj " , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj~~aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj~~~aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj~~aj " , "*aj~aj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj~~aj " , "*aj~~aj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj~~aj " , " ajaj~~aj ", true , true , false, false,
false, true , true , false,
true , true , false, false,
false, true , true , false),
new TestCase("a\\a" , "a~~a" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase("a\\a" , "a\\\\a" , false, false, true , false,
true , false, true , true ,
false, false, true , true ,
false, true , false, true ),
new TestCase("a\\\\a" , "a\\\\\\\\a", false, false, true , false,
true , false, true , true ,
false, false, true , true ,
false, true , false, true ),
new TestCase("a~~a" , "a~~~~a" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase("*a.b" , "~~*a~~.b" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase("*a.b" , "~*a~.b" , true , true , false, false,
false, true , true , true ,
true , true , false, false,
false, true , true , true ),
new TestCase("*a.b" , "*a.b" , true , true , false, false,
false, true , true , true ,
true , true , false, false,
false, true , true , true ),
new TestCase("*axb" , "~~*a~~.b" , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase("*axb" , "~*a~.b" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true ),
new TestCase("*axb" , "*a.b" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true ),
new TestCase("xa.b" , "~~*a~~.b" , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase("xa.b" , "~*a~.b" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true ),
new TestCase("xa.b" , "*a.b" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true )
};
TestCase.unixEscapes = false; // the TestCase constructor uses it
TestCase windowsTests[] =
{
new TestCase(" hello " , "hello" , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase("hello" , "hello " , false, true , false, false,
false, true , true , false,
false, true , false, false,
false, true , true , false),
new TestCase("hello " , "hello" , true , true , false, false,
false, true , true , false,
true , true , false, false,
false, true , true , false),
new TestCase("hELLo " , "hello" , true , true , false, false,
false, true , true , false,
false, false, true , false,
true , false, true , false),
new TestCase("hello " , "heLLo" , true , true , false, false,
false, true , true , false,
false, false, true , true ,
false, true , false, false),
new TestCase(" hello " , ".ell." , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" hello " , ".*ell*" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" hello~hel", "..ell.\\*" , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" hel~lo " , " hell*" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true ),
new TestCase(" ajaj\\aj " , "*aj*" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj\\aj " , "*aj\\aj " , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj\\aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj\\\\aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj\\\\\\aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj\\\\aj " , "*aj\\aj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj\\\\aj ", "*aj\\\\aj ", false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj\\\\aj ", " ajaj\\\\aj ", true , true , false, false,
false, true , true , true ,
true , true , false, false,
false, true , true , true ),
new TestCase(" ajaj~aj " , "*aj*" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj~aj " , "*aj~~aj " , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj~aj " , "*ajaj " , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase(" ajaj~~aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj~~~aj " , "*ajaj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj~~aj " , "*aj~aj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj~~aj " , "*aj~~aj " , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase(" ajaj~~aj " , " ajaj~~aj ", true , true , false, false,
false, true , true , false,
true , true , false, false,
false, true , true , false),
new TestCase("a\\a" , "a~~a" , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase("a\\a" , "a\\\\a" , false, false, true , false,
true , false, true , false,
false, false, true , true ,
false, true , false, false),
new TestCase("a\\\\a" , "a\\\\\\\\a", false, false, true , false,
true , false, true , false,
false, false, true , true ,
false, true , false, false),
new TestCase("a~~a" , "a~~~~a" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase("*a.b" , "~~*a~~.b" , false, false, true , false,
true , false, true , true ,
false, false, true , false,
true , false, true , true ),
new TestCase("*a.b" , "~*a~.b" , true , true , false, false,
false, true , true , true ,
true , true , false, false,
false, true , true , true ),
new TestCase("*a.b" , "*a.b" , true , true , false, false,
false, true , true , true ,
true , true , false, false,
false, true , true , true ),
new TestCase("*axb" , "~~*a~~.b" , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase("*axb" , "~*a~.b" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true ),
new TestCase("*axb" , "*a.b" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true ),
new TestCase("xa.b" , "~~*a~~.b" , false, false, true , false,
true , false, true , false,
false, false, true , false,
true , false, true , false),
new TestCase("xa.b" , "~*a~.b" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true ),
new TestCase("xa.b" , "*a.b" , false, false, true , true ,
false, true , false, true ,
false, false, true , true ,
false, true , false, true )
};
// run the tests
TestCase.unixEscapes = false;
System.out.println("Unix escapes: " + TestCase.unixEscapes);
for (TestCase aTest : windowsTests)
{
if (!aTest.test())
{
flag = false;
}
}
TestCase.unixEscapes = true;
System.out.println("Unix escapes: " + TestCase.unixEscapes);
for (TestCase aTest : linuxTests)
{
if (!aTest.test())
{
flag = false;
}
}
if (flag)
{
System.out.println("All tests passed successfully.");
}
System.out.println(TextOps.matchesList("abc,def", "ABc") + " yes");
System.out.println(TextOps.matchesList("abc,def", "dEF") + " yes");
System.out.println(TextOps.matchesList("abc,def", "xyz") + " no ");
System.out.println(TextOps.matchesList("!abc,def", "ABc") + " no");
System.out.println(TextOps.matchesList("!abc,def", "dEF") + " yes");
System.out.println(TextOps.matchesList("abc*,def", "ABc") + " yes");
System.out.println(TextOps.matchesList("ab*,def", "ABc") + " yes");
System.out.println(TextOps.matchesList("a*b*c,def", "AxBc") + " yes");
System.out.println(TextOps.matchesList("a*b*c,def", "AxByc") + " yes");
System.out.println(TextOps.matchesList("!a*b*c,def", "AxBc") + " no ");
System.out.println(TextOps.matchesList("!a*b*c,def", "AxByc") + " no ");
System.out.println(TextOps.matchesList("a!bc,def", "A!Bc") + " yes");
System.out.println(TextOps.matchesList("abc,d!ef", "d!EF") + " yes");
System.out.println(TextOps.matchesList(",abc,def", "") + " yes");
System.out.println(TextOps.matchesList(" ,abc,def", "") + " no ");
System.out.println(TextOps.matchesList(" ,abc,def", " ") + " yes");
System.out.println(TextOps.matchesList("abc,def,", "") + " yes");
System.out.println(TextOps.matchesList("abc,def,", " ") + " no ");
System.out.println(TextOps.matchesList("abc,def, ", " ") + " yes");
System.out.println(TextOps.matchesList("", " ") + " no ");
System.out.println(TextOps.matchesList("", "") + " yes");
System.out.println(TextOps.matchesList(new character(), " ") + " ? ");
System.out.println(TextOps.matchesList(" ", new character()) + " ? ");
System.out.println(TextOps.matchesList(new character(), new character()) + " ? ");
System.out.println(TextOps.matchesList(new character()) + " exception");
}
// This is the Progress 4GL program that was used to generate the test
// case results. As output it generates the Java statements to initialize
// the tests[] array in main(), HOWEVER due to the natural conversion of
// string literals by Progress (on input), the corresponding output
// strings are missing tildes and backslashes... so you will have to fix
// up these strings to include the missing data. In addition, since Java
// modifies string literals at compile time, you will need to double up
// every backslash to ensure that the resulting number of backslashes
// match what the Progress program used. So if there were 2 backslashes
// in the Progress version of a string, the Java version will need 4.
// Once these fixups occur in the main() method, then the tests can be
// run to determine the level of compatibility between this class and
// Progress string comparison operations.
/*
def var op1 as char extent 38.
def var op1_ as char extent 38 case-sensitive.
def var op2 as char extent 38.
def var res as logical format "true/false".
def var i as int.
def var str as char.
op1[1] = " hello ". op2[1] = "hello".
op1[2] = "hello". op2[2] = "hello ".
op1[3] = "hello ". op2[3] = "hello".
op1[4] = "hELLo ". op2[4] = "hello".
op1[5] = "hello ". op2[5] = "heLLo".
op1[6] = " hello ". op2[6] = ".ell.".
op1[7] = " hello ". op2[7] = ".*ell*".
op1[8] = " hello~hel". op2[8] = "..ell.\*".
op1[9] = " hel~lo ". op2[9] = " hell*".
op1[10] = " ajaj\aj ". op2[10] = "*aj*".
op1[11] = " ajaj\aj ". op2[11] = "*aj\aj ".
op1[12] = " ajaj\aj ". op2[12] = "*ajaj ".
op1[13] = " ajaj\\aj ". op2[13] = "*ajaj ".
op1[14] = " ajaj\\\aj ". op2[14] = "*ajaj ".
op1[15] = " ajaj\\aj ". op2[15] = "*aj\aj ".
op1[16] = " ajaj\\aj ". op2[16] = "*aj\\aj ".
op1[17] = " ajaj\\aj ". op2[17] = " ajaj\\aj ".
op1[18] = " ajaj~aj ". op2[18] = "*aj*".
op1[19] = " ajaj~aj ". op2[19] = "*aj~aj ".
op1[20] = " ajaj~aj ". op2[20] = "*ajaj ".
op1[21] = " ajaj~~aj ". op2[21] = "*ajaj ".
op1[22] = " ajaj~~~aj ". op2[22] = "*ajaj ".
op1[23] = " ajaj~~aj ". op2[23] = "*aj~aj ".
op1[24] = " ajaj~~aj ". op2[24] = "*aj~~aj ".
op1[25] = " ajaj~~aj ". op2[25] = " ajaj~~aj ".
op1[26] = "a\a". op2[26] = "a~~a".
op1[27] = "a\a". op2[27] = "a\\a".
op1[28] = "a\\a". op2[28] = "a\\\\a".
op1[29] = "a~~a". op2[29] = "a~~~~a".
op1[30] = "*a.b". op2[30] = "~~*a~~.b".
op1[31] = "*a.b". op2[31] = "~*a~.b".
op1[32] = "*a.b". op2[32] = "*a.b".
op1[33] = "*axb". op2[33] = "~~*a~~.b".
op1[34] = "*axb". op2[34] = "~*a~.b".
op1[35] = "*axb". op2[35] = "*a.b".
op1[36] = "xa.b". op2[36] = "~~*a~~.b".
op1[37] = "xa.b". op2[37] = "~*a~.b".
op1[38] = "xa.b". op2[38] = "*a.b".
output to value("listing.txt").
do i = 1 to 38.
str = " new TestCase(\"" + op1[i] + "\"" +
fill(" ", 11 - length(op1[i])) + ", \"" + op2[i] + "\"" +
fill(" ", 11 - length(op2[i])) + ", ".
put str format "x(52)".
res = op1[i] begins op2[i].
put res ", ".
res = op1[i] eq op2[i].
put res ", ".
res = op1[i] ne op2[i].
put res ", ".
res = op1[i] gt op2[i].
put res ",~n".
put fill(" ", 52) format "x(52)".
res = op1[i] lt op2[i].
put res ", ".
res = op1[i] ge op2[i].
put res ", ".
res = op1[i] le op2[i].
put res ", ".
res = op1[i] matches op2[i].
put res ",~n".
put fill(" ", 52) format "x(52)".
op1_[i] = op1[i].
res = op1_[i] begins op2[i].
put res ", ".
res = op1_[i] eq op2[i].
put res ", ".
res = op1_[i] ne op2[i].
put res ", ".
res = op1_[i] gt op2[i].
put res ",~n".
put fill(" ", 52) format "x(52)".
res = op1_[i] lt op2[i].
put res ", ".
res = op1_[i] ge op2[i].
put res ", ".
res = op1_[i] le op2[i].
put res ", ".
res = op1_[i] matches op2[i].
put res "),~n".
end.
output close.
*/
}