SQLHelper.java
/*
** Module : SQLHelper.java
** Abstract : Static class that gather utility methods for string processing related to SQL.
**
** Copyright (c) 2014-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description---------------------------------
** 001 OM 20050517 Created initial version by refactoring methods from character class.
** 002 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 003 CA 20230322 Cache the pattern used by 'convertToSQLContains'.
** 004 CA 20240512 Added POLY versions for convertToSQLLike and convertToSQLBegins.
*/
/*
** 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.util.regex.*;
/**
* Static class that exposes utility methods for processing strings that are fed to SQL queries as
* part of <code>WHERE</code> clauses.
* <p>
* All <code>convertToSQL___</code> methods assume that the input value is not encoded in any way
* (does not contain escape characters of Progress, Java or SQL). The only allowed escapes are for
* DB side Postgres patterns for <code>MATCHES</code> and <code>CONTAINS</code> that will be
* converted to their respective SQL equivalents. <br>
* The result of these methods will be SQL encoded but not Java encoded. This way, they can be
* further processed (rtrim or uppercase) at either conversion or runtime. To be written to java
* source file at conversion time, the literal must be java-escaped using
* {@link character#encodeToJavaSource(java.lang.String)}.
* <p>
* If the literals to be executed by inlined queries on server side, <code>convertToSQL___</code>
* methods automatically escapes them. Otherwise, they need to be SQL encoded at runtime, when
* they are inlined automatically or by the SQL sriver when they are sent as SQL parameters.
*
*/
public class SQLHelper
{
/** The pattern used by {@link #convertToSQLContains}. */
private static final Pattern SQL_CONTAINS = Pattern.compile("[&\\(\\)\\^\\|\\!]");
/**
* Converts a second operand of the "op1 MATCHES op2" function into corresponding SQL LIKE
* expression. This processing is meant to be used ONLY for argument in the left-side of
* a LIKE SQL operator in a WHERE clause.
* <p>
* The method assumes the input string (<code>str</code>) is a in-memory string without any
* escapes (neither of Progress, HQL/SQL or Java). The string can be obtained form either of:
* <ul>
* <li>read from a P4GL source file with
* {@link character#parseProgressCharLiteral(String, boolean)}
* that will process all Progress escape encoding (used at conversion time, when the
* input is known and result is written as the <code>where</code> parameter of a
* <code>P2JQuery</code> in the java source file.
* <li>the value of a {@link character} P4GL variable, field or any
* other character expression (at runtime, as one of the arguments of the where clause
* of a <code>P2JQuery</code> in the java source file.
* </ul>
* <p>
* The following conversions will be performed:
* <table border='1' summary="">
* <tr><th>P4GL</th><th>to</th><th>Notes</th></tr>
* <tr><td>_</td><td>\_</td><td>SQL 'single character' wildcard must be escaped because it
* does not have any meaning in P4GL</td></tr>
* <tr><td>%</td><td>\%</td><td>SQL 'zero or more characters' wildcard must be escaped as it
* does not have any meaning in P4GL</td></tr>
* <tr><td>'</td><td>''</td><td>single quote is used to quote string literals in SQL. We double
* it in <code>inline</code> mode for correct encoding in SQL string literal.</td></tr>
* <tr><td>\</td><td>\\ or escape</td><td>P4GL in Windows: normal character. In SQL backslash
* is used to remove other character's meaning. We double it for correct encoding in SQL
* string literal.<br>
* P4GL in Linux: escape character (like ~). If this is the first occurrence, then handle it
* as escape and the following character has no meaning in P4GL. If this character was
* already escaped, we double it for correct encoding in SQL string literal.</td></tr>
* <tr><td>*</td><td>%</td><td>Conversion from P4GL to SQL of the 'zero or more characters'
* wildcard. Unless it was not escaped.</td></tr>
* <tr><td>.</td><td>_</td><td>Conversion from P4GL to SQL of the 'single character'
* wildcard. Unless it was not escaped.</td></tr>
* </table>
* <p>
* Depending on <code>inline</code> parameter, the resulting string is HQL/SQL escaped or not.
* If the value is meant to be inlined at conversion time, the single quotes are automatically
* doubled (sql escaped). Otherwise, the result can be further processed and will be SQL
* escaped when inlined at runtime or automatically as query parameter.
* <p>
* If the method is called at conversion time and the result will be written to disk as a java
* literal, it must be escaped accordingly (see {@link character#encodeToJavaSource(String)}).
*
* @param pattern
* Progress 4GL matching pattern.
* @param windows
* how backslash should be handled (on Windows it's a normal character, in Linux it's
* an escape character similar to tilde (~))
*
* @return the pattern converted SQL LIKE expression. Optionally SQL escaped.
*/
public static character convertToSQLLike(character pattern, boolean windows)
{
if (pattern.isUnknown())
{
return new character();
}
return new character(convertToSQLLike(pattern.getValue(), windows, false));
}
/**
* Converts a second operand of the "op1 MATCHES op2" function into corresponding SQL LIKE
* expression. This processing is meant to be used ONLY for argument in the left-side of
* a LIKE SQL operator in a WHERE clause.
* <p>
* The method assumes the input string (<code>str</code>) is a in-memory string without any
* escapes (neither of Progress, HQL/SQL or Java). The string can be obtained form either of:
* <ul>
* <li>read from a P4GL source file with
* {@link character#parseProgressCharLiteral(String, boolean)}
* that will process all Progress escape encoding (used at conversion time, when the
* input is known and result is written as the <code>where</code> parameter of a
* <code>P2JQuery</code> in the java source file.
* <li>the value of a {@link character} P4GL variable, field or any
* other character expression (at runtime, as one of the arguments of the where clause
* of a <code>P2JQuery</code> in the java source file.
* </ul>
* <p>
* The following conversions will be performed:
* <table border='1' summary="">
* <tr><th>P4GL</th><th>to</th><th>Notes</th></tr>
* <tr><td>_</td><td>\_</td><td>SQL 'single character' wildcard must be escaped because it
* does not have any meaning in P4GL</td></tr>
* <tr><td>%</td><td>\%</td><td>SQL 'zero or more characters' wildcard must be escaped as it
* does not have any meaning in P4GL</td></tr>
* <tr><td>'</td><td>''</td><td>single quote is used to quote string literals in SQL. We double
* it in <code>inline</code> mode for correct encoding in SQL string literal.</td></tr>
* <tr><td>\</td><td>\\ or escape</td><td>P4GL in Windows: normal character. In SQL backslash
* is used to remove other character's meaning. We double it for correct encoding in SQL
* string literal.<br>
* P4GL in Linux: escape character (like ~). If this is the first occurrence, then handle it
* as escape and the following character has no meaning in P4GL. If this character was
* already escaped, we double it for correct encoding in SQL string literal.</td></tr>
* <tr><td>*</td><td>%</td><td>Conversion from P4GL to SQL of the 'zero or more characters'
* wildcard. Unless it was not escaped.</td></tr>
* <tr><td>.</td><td>_</td><td>Conversion from P4GL to SQL of the 'single character'
* wildcard. Unless it was not escaped.</td></tr>
* </table>
* <p>
* Depending on <code>inline</code> parameter, the resulting string is HQL/SQL escaped or not.
* If the value is meant to be inlined at conversion time, the single quotes are automatically
* doubled (sql escaped). Otherwise, the result can be further processed and will be SQL
* escaped when inlined at runtime or automatically as query parameter.
* <p>
* If the method is called at conversion time and the result will be written to disk as a java
* literal, it must be escaped accordingly (see {@link character#encodeToJavaSource(String)}).
*
* @param pattern
* Progress 4GL matching pattern.
* @param windows
* how backslash should be handled (on Windows it's a normal character, in Linux it's
* an escape character similar to tilde (~))
*
* @return the pattern converted SQL LIKE expression. Optionally SQL escaped.
*/
public static character convertToSQLLike(BaseDataType pattern, boolean windows)
{
if (pattern.isUnknown())
{
return new character();
}
return new character(convertToSQLLike(pattern.toStringMessage(), windows, false));
}
/**
* Converts a second operand of the "op1 MATCHES op2" function into corresponding SQL LIKE
* expression. This processing is meant to be used ONLY for argument in the left-side of
* a LIKE SQL operator in a WHERE clause.
* <p>
* The method assumes the input string (<code>str</code>) is a in-memory string without any
* escapes (neither of Progress, HQL/SQL or Java). The string can be obtained form either of:
* <ul>
* <li>read from a P4GL source file with
* {@link character#parseProgressCharLiteral(String, boolean)}
* that will process all Progress escape encoding (used at conversion time, when the
* input is known and result is written as the <code>where</code> parameter of a
* <code>P2JQuery</code> in the java source file.
* <li>the value of a {@link character} P4GL variable, field or any
* other character expression (at runtime, as one of the arguments of the where clause
* of a <code>P2JQuery</code> in the java source file.
* </ul>
* <p>
* The following conversions will be performed:
* <table border='1' summary="">
* <tr><th>P4GL</th><th>to</th><th>Notes</th></tr>
* <tr><td>_</td><td>\_</td><td>SQL 'single character' wildcard must be escaped because it
* does not have any meaning in P4GL</td></tr>
* <tr><td>%</td><td>\%</td><td>SQL 'zero or more characters' wildcard must be escaped as it
* does not have any meaning in P4GL</td></tr>
* <tr><td>'</td><td>''</td><td>single quote is used to quote string literals in SQL. We double
* it in <code>inline</code> mode for correct encoding in SQL string literal.</td></tr>
* <tr><td>\</td><td>\\ or escape</td><td>P4GL in Windows: normal character. In SQL backslash
* is used to remove other character's meaning. We double it for correct encoding in SQL
* string literal.<br>
* P4GL in Linux: escape character (like ~). If this is the first occurrence, then handle it
* as escape and the following character has no meaning in P4GL. If this character was
* already escaped, we double it for correct encoding in SQL string literal.</td></tr>
* <tr><td>*</td><td>%</td><td>Conversion from P4GL to SQL of the 'zero or more characters'
* wildcard. Unless it was not escaped.</td></tr>
* <tr><td>.</td><td>_</td><td>Conversion from P4GL to SQL of the 'single character'
* wildcard. Unless it was not escaped.</td></tr>
* </table>
* <p>
* Depending on <code>inline</code> parameter, the resulting string is HQL/SQL escaped or not.
* If the value is meant to be inlined at conversion time, the single quotes are automatically
* doubled (sql escaped). Otherwise, the result can be further processed and will be SQL
* escaped when inlined at runtime or automatically as query parameter.
* <p>
* If the method is called at conversion time and the result will be written to disk as a java
* literal, it must be escaped accordingly (see {@link character#encodeToJavaSource(String)}).
*
* @param pattern
* Progress 4GL matching pattern.
* @param windows
* how backslash should be handled (on Windows it's a normal character, in Linux it's
* an escape character similar to tilde (~))
*
* @return the pattern converted SQL LIKE expression. Optionally SQL escaped.
*/
public static String convertToSQLLike(String pattern, boolean windows, boolean inline)
{
StringBuilder sb = new StringBuilder("");
int end = pattern.length();
// convert the contents into the equivalent Java form
for (int i = 0; i < end; i++)
{
char current = pattern.charAt(i);
switch (current)
{
case '*':
sb.append("%");
break;
case '.':
sb.append("_");
break;
case '\'':
sb.append(inline ? "\'\'" : "\'");
break;
case '%':
sb.append("\\%");
break;
case '_':
sb.append("\\_");
break;
case '\\':
if (windows)
{
//sb.append(current);
sb.append("\\\\");
break;
}
// else fall trough next case '~', on non-windows \ is also escape character
case '~':
// There must always be a following character that will be escaped.
// Note that the Progress Language escaping chars were already replaced.
// This is only an escape for MATCHES operator wildcard so that the next
// character has no special meaning. In Progress, if the next was already
// a normal character, the escape char is simply discarded.
char next = pattern.charAt(i + 1);
// look ahead one character and if must be processed now, consume it (i++)
switch (next)
{
case '*':
case '.':
// when escaped, they have no special meaning in Progress
i++;
sb.append(next);
break;
// we dropped its Progress meaning, but it must be hql escaped
case '\\':
i++;
sb.append("\\\\");
break;
default:
// any other character will be processed in next iteration, SQL escaping them
// if they have a special role in SQL.
break;
}
break;
default:
// not an escaped char or special case
sb.append(current);
}
}
return sb.toString();
}
/**
* Converts a second operand of the "op1 BEGINS op2" function into corresponding SQL LIKE
* expression. This processing is meant to be used ONLY for argument in the left-side of
* a LIKE SQL operator in a WHERE clause.
* <p>
* The method assumes the input string (<code>str</code>) is a in-memory string without any
* escapes (neither of Progress, HQL/SQL or Java). The string can be obtained form either of:
* <ul>
* <li>read from a P4GL source file with
* {@link character#parseProgressCharLiteral(String, boolean)} that will process all
* Progress escape encoding (used at conversion time, when the input is known and result
* is written as the <code>where</code> parameter of a <code>P2JQuery</code> in the java
* source file.
* <li>the value of a {@link character} P4GL variable, field or any
* other character expression (at runtime, as one of the arguments of the where clause
* of a <code>P2JQuery</code> in the java source file.
* </ul>
* <p>
* The following conversions will be performed:
* <table border='1' summary="">
* <tr><th>P4GL</th><th>to</th><th>Notes</th></tr>
* <tr><td>_</td><td>\_</td><td>SQL 'single character' wildcard must be escaped because it
* does not have any meaning in P4GL</td></tr>
* <tr><td>%</td><td>\%</td><td>SQL 'zero or more characters' wildcard must be escaped as it
* does not have any meaning in P4GL</td></tr>
* <tr><td>'</td><td>''</td><td>single quote is used to quote string literals in SQL. We double
* it in <code>inline</code> mode for correct encoding in SQL string literal.</td></tr>
* <tr><td>\</td><td>\\</td><td>in SQL backslash is used to escape other character's meaning.
* We double it for correct encoding in SQL string literal.</td></tr>
* </table>
* <p>
* To implement the begin semantic, a 'zero or more characters' wildcard (%) will be appended
* to the end of the returned string.
* <p>
* Depending on <code>inline</code> parameter, the resulting string is HQL/SQL escaped or not.
* If the value is meant to be inlined at conversion time, the single quotes are automatically
* doubled (sql escaped). Otherwise, the result can be further processed and will be SQL
* escaped when inlined at runtime or automatically as query parameter.
* <p>
* If the method is called at conversion time and the result will be written to disk as a java
* literal, it must be escaped accordingly (see {@link character#encodeToJavaSource(String)}).
*
* @param str
* Represents a second operand of the "op1 BEGINS op2" function.
*
* @return the pattern converted SQL LIKE expression. Optionally SQL escaped.
*/
public static character convertToSQLBegins(character str)
{
if (str.isUnknown())
{
return new character();
}
return new character(convertToSQLBegins(str.getValue(), false));
}
/**
* Converts a second operand of the "op1 BEGINS op2" function into corresponding SQL LIKE
* expression. This processing is meant to be used ONLY for argument in the left-side of
* a LIKE SQL operator in a WHERE clause.
* <p>
* The method assumes the input string (<code>str</code>) is a in-memory string without any
* escapes (neither of Progress, HQL/SQL or Java). The string can be obtained form either of:
* <ul>
* <li>read from a P4GL source file with
* {@link character#parseProgressCharLiteral(String, boolean)} that will process all
* Progress escape encoding (used at conversion time, when the input is known and result
* is written as the <code>where</code> parameter of a <code>P2JQuery</code> in the java
* source file.
* <li>the value of a {@link character} P4GL variable, field or any
* other character expression (at runtime, as one of the arguments of the where clause
* of a <code>P2JQuery</code> in the java source file.
* </ul>
* <p>
* The following conversions will be performed:
* <table border='1' summary="">
* <tr><th>P4GL</th><th>to</th><th>Notes</th></tr>
* <tr><td>_</td><td>\_</td><td>SQL 'single character' wildcard must be escaped because it
* does not have any meaning in P4GL</td></tr>
* <tr><td>%</td><td>\%</td><td>SQL 'zero or more characters' wildcard must be escaped as it
* does not have any meaning in P4GL</td></tr>
* <tr><td>'</td><td>''</td><td>single quote is used to quote string literals in SQL. We double
* it in <code>inline</code> mode for correct encoding in SQL string literal.</td></tr>
* <tr><td>\</td><td>\\</td><td>in SQL backslash is used to escape other character's meaning.
* We double it for correct encoding in SQL string literal.</td></tr>
* </table>
* <p>
* To implement the begin semantic, a 'zero or more characters' wildcard (%) will be appended
* to the end of the returned string.
* <p>
* Depending on <code>inline</code> parameter, the resulting string is HQL/SQL escaped or not.
* If the value is meant to be inlined at conversion time, the single quotes are automatically
* doubled (sql escaped). Otherwise, the result can be further processed and will be SQL
* escaped when inlined at runtime or automatically as query parameter.
* <p>
* If the method is called at conversion time and the result will be written to disk as a java
* literal, it must be escaped accordingly (see {@link character#encodeToJavaSource(String)}).
*
* @param str
* Represents a second operand of the "op1 BEGINS op2" function.
*
* @return the pattern converted SQL LIKE expression. Optionally SQL escaped.
*/
public static character convertToSQLBegins(BaseDataType str)
{
if (str.isUnknown())
{
return new character();
}
return new character(convertToSQLBegins(str.toStringMessage(), false));
}
/**
* Converts a second operand of the "op1 BEGINS op2" function into corresponding SQL LIKE
* expression. This processing is meant to be used ONLY for argument in the left-side of
* a LIKE SQL operator in a WHERE clause.
* <p>
* The method assumes the input string (<code>str</code>) is a in-memory string without any
* escapes (neither of Progress, HQL/SQL or Java). The string can be obtained form either of:
* <ul>
* <li>read from a P4GL source file with
* {@link character#parseProgressCharLiteral(String, boolean)} that will process all
* Progress escape encoding (used at conversion time, when the input is known and result
* is written as the <code>where</code> parameter of a <code>P2JQuery</code> in the java
* source file.
* <li>the value of a {@link character} P4GL variable, field or any
* other character expression (at runtime, as one of the arguments of the where clause
* of a <code>P2JQuery</code> in the java source file.
* </ul>
* <p>
* The following conversions will be performed:
* <table border='1' summary="">
* <tr><th>P4GL</th><th>to</th><th>Notes</th></tr>
* <tr><td>_</td><td>\_</td><td>SQL 'single character' wildcard must be escaped because it
* does not have any meaning in P4GL</td></tr>
* <tr><td>%</td><td>\%</td><td>SQL 'zero or more characters' wildcard must be escaped as it
* does not have any meaning in P4GL</td></tr>
* <tr><td>'</td><td>''</td><td>single quote is used to quote string literals in SQL. We double
* it in <code>inline</code> mode for correct encoding in SQL string literal.</td></tr>
* <tr><td>\</td><td>\\</td><td>in SQL backslash is used to escape other character's meaning.
* We double it for correct encoding in SQL string literal.</td></tr>
* </table>
* <p>
* To implement the begin semantic, a 'zero or more characters' wildcard (%) will be appended
* to the end of the returned string.
* <p>
* Depending on <code>inline</code> parameter, the resulting string is HQL/SQL escaped or not.
* If the value is meant to be inlined at conversion time, the single quotes are automatically
* doubled (sql escaped). Otherwise, the result can be further processed and will be SQL
* escaped when inlined at runtime or automatically as query parameter.
* <p>
* If the method is called at conversion time and the result will be written to disk as a java
* literal, it must be escaped accordingly (see {@link character#encodeToJavaSource(String)}).
*
* @param str
* Represents a second operand of the "op1 BEGINS op2" function.
*
* @return the pattern converted SQL LIKE expression. Optionally SQL escaped.
*/
public static String convertToSQLBegins(String str, boolean inline)
{
StringBuilder sb = new StringBuilder("");
int end = str.length();
for (int i = 0; i < end; i++)
{
char current = str.charAt(i);
switch (current)
{
case '%':
sb.append("\\%");
break;
case '_':
sb.append("\\_");
break;
case '\'':
sb.append(inline ? "\'\'" : "\'");
break;
case '\\':
sb.append("\\\\");
break;
default:
sb.append(current);
}
}
sb.append("%"); // beginning pattern can be followed by anything
return sb.toString();
}
/**
* Converts a second operand of the "op1 CONTAINS op2" function into corresponding SQL LIKE
* expression.
*
* Progress syntax for op2 is some kind of recursive expression like:
* <expr> := word | word* | (<or-expr>)
* <or-expr> := <and-expr> ( <or-op> <and-expr> )*
* <and-expr> := <expr> (<and-op> <expr>)*
* <or-op> is one of: "^" "|" "!"
* <and-op> = "&"
*
* @param str
* Represents a second operand of the "op1 CONTAINS op2" clause.
*
* @return The corresponding SQL LIKE expression.
*/
public static character convertToSQLContains(character str)
{
if (str.isUnknown())
{
return new character();
}
return new character(convertToSQLContains(str.getValue(), false));
}
/**
* Converts a second operand of the "op1 CONTAINS op2" function into
* corresponding SQL LIKE expression.
* <p>
* Progress syntax for op2 is some kind of recursive expression like:
* <pre>
* <expr> := word | word"*" | "(" <or-expr> ")"
* <or-expr> := <and-expr> ( <or-op> <and-expr> )*
* <and-expr> := <expr> (<and-op> <expr>)*
* <or-op> is one of: "^" "|" "!"
* <and-op> = "&"
* </pre>
* Spaces are not allowed.<p>
* However, the DIRTY method only does the following: <ul>
* <li>looks for words
* <li>removes * from the end, if any
* <li>replaces special characters (see {@link #convertToSQLBegins(String, boolean)})
* <li>replaces the word with %word% in the original text
* </ul>
* This way, if no fancy expression is used the correct query is generated. Any more complex
* expression cannot be converted without recursive processing of the expression using the
* above definition.
* <p>
* A better algorithm would convert a clause like:
* <pre>SomeField CONTAINS "word1&(word2*|word3)</pre>
* into the more complex SQL form:
* <pre>
* ((SomeField LIKE "%word1%) AND ((SomeField LIKE "%word2%) OR (SomeField LIKE "%word3%)))
* </pre>
* The correct implementation has to use regular expressions together with SQL clause
* <code>SIMILAR TO</code>. However, I am not aware of how and if hibernate supports it.
*
* @param str
* Represents a second operand of the "op1 CONTAINS op2" clause.
*
* @return A substitution SQL LIKE expression.
*/
public static String convertToSQLContains(String str, boolean inline)
{
StringBuilder sb = new StringBuilder();
Matcher m = SQL_CONTAINS.matcher(str);
String word;
int wordStart = 0;
while (m.find())
{
char tok = m.group().charAt(0); // all tokens are exactly 1 char long
int beg = m.start();
int end = m.end();
word = str.substring(wordStart, beg).trim();
if (!word.isEmpty())
{
if (word.endsWith("*"))
{
word = word.substring(0, word.length() - 1);
}
sb.append('%');
sb.append(convertToSQLBegins(word, inline)); // TODO how to handle this ?
}
if (tok == '(' || tok == ')')
{
sb.append(tok);
}
else
{
sb.append(' ').append(tok).append(' ');
}
wordStart = end;
}
word = str.substring(wordStart).trim();
if (!word.isEmpty())
{
if (word.endsWith("*"))
{
word = word.substring(0, word.length() - 1);
}
sb.append('%');
sb.append(convertToSQLBegins(word, inline)); // TODO how to handle this ?
}
return sb.toString();
}
/**
* Converts a string (from a Progress source file) that represents a Progress 4GL matching
* "pattern" to a traditional regular expression which is suitable for a Java source file.
* This allows a trivial implementation of the matches operator using the
* <code>String.matches</code> method.
* <p>
* In Progress 4GL, there are 2 escape characters '~' (tilde) and '\' backslash. The latter is
* only used in Unix OS, while in windows it does not have any special meaning.
* <p>
* In both Java and in regular expressions, the only escape character is '\'. In addition,
* since these strings come directly from source files, the Progress runtime has not handled
* the escape sequences on input as it normally would. So although the sequences '~*' or '\*'
* are normally the literal asterisk, the string must be coded to appear as '~~*' or '\\*' in
* the source because the escape chars are normally dropped on input. This method is written
* assuming that all of the normal Progress conversions have already occurred by utilizing
* {@link com.goldencode.p2j.util.character#progressToJavaString} and
* {@link com.goldencode.util.StringHelper#processEscapes}.
* <p>
* Once the standard Progress conversions have occurred, then this method can be used and the
* following conversions will be additionally handled:
* <pre>
* Progress Regular Expression
* -------- ------------------
* ~. \\.
* \. \\.
* ~* \\*
* \* \\*
* ~~ ~
* \~ ~
* ~\ \\\\
* \\ \\\\
* * .*
* ~ (removed or converted to \\ if the following char requires)
* \ (removed or converted to \\ if the following char requires)
* </pre>
* <p>
* The following characters in Progress have special meaning in the extended regular expression
* syntax used in Java. Each of these must be properly escaped to ensure that their meaning is
* not accidentally invoked by an input pattern that expects it to be matched literally:
* <p>
* <pre>
* Progress Extended Regular Expression
* -------- ------------------
* ? \\\\?
* + \\\\+
* ^ \\\\^
* $ \\\\$
* | \\\\|
* ( \\\\(
* ) \\\\)
* [ \\\\[
* ] \\\\]
* { \\\\{
* } \\\\}
* </pre>
* The following special Progress pattern constructs actually match those of regular
* expressions and for this reason, these constructs are left alone (not disturbed or
* modified):
* <pre>
* Progress Regular Expression
* -------- ------------------
* . .
* </pre>
* This conversion seems trivial, except that the backslash '\' character must be handled
* properly since it needs to be doubled up for Java to recognise one in a Java string and if
* two are needed in the regular expression then one needs to double them up, twice. This means
* that in order to match a single \ in a regular expression, the Java string will need \\\\
* (4 backslashes). In addition, since the asterisk '*' character is a wildcard (it matches
* any number of any character) in Progress, it must be properly replaced in the regular
* expression (where .* is the unlimited wildcard). Both Progress and regular expressions use
* '.' to mean match any single character.
*
* @param pattern
* Progress 4GL matching pattern.
* @param windows
* How the backslashes will be treated. On Unix, <code>windows = false</code>,
* they are escape characters that remove special meaning of other characters
* (same like tilde ~). On Windows, backslashes are normal characters, the tilde is
* the only escape character.
*
* @return converted regular expression.
*/
public static String convertToRegEx(String pattern, boolean windows)
{
StringBuilder sb = new StringBuilder("");
int end = pattern.length();
// convert the contents into the equivalent Java form
for (int i = 0; i < end; i++)
{
char current = pattern.charAt(i);
char next;
switch (current)
{
case '*':
sb.append(".*");
break;
case '\\':
if (windows)
{
// escape it
sb.append("\\\\");
break;
}
// else, the backslash is treated the same like tilde, fall trough
case '~':
// there must always be a following character
next = pattern.charAt(i + 1);
if (next == '.' || next == '*' || next == '\\')
{
// these have special meanings in P4GL that the escape character will remove
// so they must be handled as normal chars in regexp. Since they have also
// special meaning in regexp, they must be escaped
sb.append('\\').append(next);
++ i;
}
// all the other characters that were not escaped have no special meaning in p4gl
// so they are just processed in next iteration
break;
default:
// certain chars need to have an escape inserted because they have no special
// meaning in Progress but do have a special meaning in Java's extended regular
// expression syntax
if (current == '?' || current == '+' || current == '^' ||
current == '$' || current == '|' || current == '(' ||
current == ')' || current == '{' || current == '}' ||
current == '[' || current == ']')
{
// insert a leading escape char
sb.append("\\");
}
// not an escaped char or special case
sb.append(current);
}
}
return sb.toString();
}
}