--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.iapi.types.Like\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.iapi.types;\r
+\r
+// RESOLVE: MOVE THIS CLASS TO PROTOCOL (See LikeOperatorNode)\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import java.text.CollationElementIterator;\r
+import java.text.Collator;\r
+import java.text.RuleBasedCollator;\r
+import java.util.Locale;\r
+\r
+/**\r
+ Like matching algorithm. Not too speedy for %s.\r
+\r
+ SQL92 says the escape character can only and must be followed\r
+ by itself, %, or _. So if you choose % or _ as the escape character,\r
+ you can no longer do that sort of matching.\r
+\r
+ Not the most recent Like -- missing the unit tests\r
+\r
+ */\r
+public class Like {\r
+ private static final char anyChar = '_';\r
+ private static final char anyString = '%';\r
+\r
+ private static final String SUPER_STRING = "\uffff";\r
+\r
+ private Like() { // do not instantiate\r
+ }\r
+\r
+ /**\r
+ \r
+ This method gets called for UCS_BASIC and territory based character\r
+ string types to look for a pattern in a value string. It also deals\r
+ with escape character if user has provided one.\r
+ \r
+ @param val value to compare. if null, result is null.\r
+ @param valLength length of val\r
+ @param pat pattern to compare. if null, result is null.\r
+ @param patLength length of pat\r
+ @param escape escape character. Must be 1 char long.\r
+ if null, no escape character is used.\r
+ @param escapeLength length of escape\r
+ @param collator null if we are dealing with UCS_BASIC \r
+ character string types. If not null, then we use it to \r
+ get collation elements for characters in val and \r
+ non-metacharacters in pat to do the comparison.\r
+\r
+ @return null if val or pat null, otherwise true if match\r
+ and false if not.\r
+ @exception StandardException thrown if data invalid\r
+ */\r
+ public static Boolean like\r
+ (\r
+ char[] val, \r
+ int valLength, \r
+ char[] pat, \r
+ int patLength, \r
+ char[] escape,\r
+ int escapeLength,\r
+ RuleBasedCollator collator\r
+ ) throws StandardException \r
+ {\r
+ return like(val, 0, valLength, pat, 0, patLength, escape, \r
+ escapeLength, collator);\r
+ }\r
+\r
+ /* For character string types with UCS_BASIC and territory based\r
+ * collation. */\r
+ private static Boolean like\r
+ (\r
+ char[] val, \r
+ int vLoc, // start at val[vLoc]\r
+ int vEnd, // end at val[vEnd]\r
+ char[] pat, \r
+ int pLoc, // start at pat[pLoc]\r
+ int pEnd, // end at pat[pEnd]\r
+ char[] escape,\r
+ int escapeLength,\r
+ RuleBasedCollator collator\r
+ ) throws StandardException \r
+ {\r
+ char escChar = ' ';\r
+ boolean haveEsc = true;\r
+ \r
+ if (val == null) return null;\r
+ if (pat == null) return null;\r
+\r
+ if (escape == null)\r
+ {\r
+ haveEsc = false;\r
+ }\r
+ else\r
+ {\r
+ escChar = escape[0];\r
+ }\r
+\r
+ Boolean result;\r
+\r
+ while (true) {\r
+\r
+ if ((result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd)) != null) \r
+ {\r
+ return result;\r
+ }\r
+\r
+ // go until we get a special char in the pattern or hit EOS\r
+ while (pat[pLoc] != anyChar && pat[pLoc] != anyString &&\r
+ ((! haveEsc) || pat[pLoc] != escChar)) {\r
+ if (checkEquality(val, vLoc, pat, pLoc, collator)) {\r
+ vLoc++; pLoc++;\r
+ \r
+ result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd);\r
+ if (result != null) \r
+ return result;\r
+ } else\r
+ return Boolean.FALSE;\r
+ }\r
+\r
+ // deal with escChar first, as it can be escaping a special char\r
+ // and can be a special char itself.\r
+ if (haveEsc && pat[pLoc] == escChar) {\r
+ pLoc++;\r
+ if (pLoc == pEnd) {\r
+ throw StandardException.newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);\r
+ }\r
+ if (pat[pLoc] != escChar &&\r
+ pat[pLoc] != anyChar &&\r
+ pat[pLoc] != anyString) {\r
+ throw StandardException.newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);\r
+ }\r
+ // regardless of the char in pat, it must match exactly:\r
+ if (checkEquality(val, vLoc, pat, pLoc, collator)) {\r
+ vLoc++; pLoc++;\r
+ \r
+ result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd);\r
+ if (result != null) \r
+ return result;\r
+ }\r
+ else return Boolean.FALSE;\r
+ }\r
+ else if (pat[pLoc] == anyChar) {\r
+ // regardless of the char, it matches\r
+ vLoc++; pLoc++;\r
+ \r
+ result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd);\r
+ if (result != null) \r
+ return result;\r
+ }\r
+ else if (pat[pLoc] == anyString) {\r
+ // catch the simple cases -- end of the pattern or of the string\r
+ if (pLoc+1 == pEnd)\r
+ return Boolean.TRUE;\r
+\r
+ // would return true, but caught in checkLengths above\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(vLoc!=vEnd, \r
+ "Should have been found already");\r
+\r
+ //if (vLoc == vEnd) // caught in checkLengths\r
+ //return Boolean.TRUE;\r
+ // check if remainder of pattern is anyString's\r
+ // if escChar == anyString, we couldn't be here\r
+ boolean anys = true;\r
+ for (int i=pLoc+1;i<pEnd;i++)\r
+ if (pat[i]!=anyString) {\r
+ anys=false;\r
+ break;\r
+ }\r
+ if (anys) return Boolean.TRUE;\r
+\r
+ // pattern can match 0 or more chars in value.\r
+ // to test that, we take the remainder of pattern and\r
+ // apply it to ever-shorter remainders of value until\r
+ // we hit a match.\r
+\r
+ // the loop never continues from this point -- we will\r
+ // always generate an answer here.\r
+\r
+ // REMIND: there are smarter ways to pick the remainders\r
+ // and do this matching.\r
+\r
+ // num chars left in value includes current char\r
+ int vRem = vEnd - vLoc;\r
+\r
+ int n=0;\r
+\r
+ // num chars left in pattern excludes the anychar\r
+ int minLen = getMinLen(pat, pLoc+1, pEnd, haveEsc, escChar);\r
+ for (int i=vRem; i>=minLen; i--) \r
+ {\r
+ Boolean restResult = Like.like(val, vLoc+n, vLoc+n+i, pat,\r
+ pLoc+1, pEnd, escape, escapeLength, collator);\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (restResult == null)\r
+ {\r
+ String vStr = new String(val,vLoc+n,i);\r
+ String pStr = new String(pat,pLoc+1,pEnd-(pLoc+1));\r
+ SanityManager.THROWASSERT("null result on like(value = "+vStr+", pat = "+pStr+")");\r
+ }\r
+ }\r
+ if (restResult.booleanValue())\r
+ return restResult;\r
+\r
+ n++;\r
+ }\r
+ // none of the possibilities worked \r
+ return Boolean.FALSE;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * If the character in val matches the character in pat, then it does not\r
+ * matter if the database is UCS_BASIC or territory based, we simply return \r
+ * TRUE from the method. \r
+ * If the characters do not match and we are running with UCS_BASIC \r
+ * collation, then we will return FALSE. \r
+ * But if the database is territory based, then we want to use the Collator \r
+ * for the territory to determine if the Collator treats the 2 characters \r
+ * as equal (ie if their collation elements match, then the 2 characters \r
+ * are equal even if they are not the same character).\r
+ * \r
+ * @param val value to compare.\r
+ * @param vLoc character position in val.\r
+ * @param pat pattern to look for in val.\r
+ * @param pLoc character position in pat.\r
+ * @param collator null if we are dealing with UCS_BASIC character string\r
+ * types. If not null, then we use it to determine the equality of the\r
+ * 2 characters in pat and val if they are not same.\r
+ * @return TRUE if the character in val and vLoc match based on straight\r
+ * equality or collation element based equality. Otherwise we will \r
+ * return FALSE.\r
+ */\r
+ private static boolean checkEquality(char[] val, int vLoc,\r
+ char[] pat, int pLoc, RuleBasedCollator collator) {\r
+\r
+ if (val[vLoc] == pat[pLoc]) { \r
+ // same character, so two strings consisting of this \r
+ // single character must be equal regardless of territory \r
+ return true; \r
+ } else if (collator == null) { \r
+ // not same character, must be unequal in UCS_BASIC \r
+ return false; \r
+ } \r
+\r
+ //Check if the Collator for this database's territory considers these\r
+ //2 characters as equal based on their collation elements\r
+ String s1 = new String(val, vLoc, 1); \r
+ String s2 = new String(pat, pLoc, 1); \r
+\r
+ return collator.compare(s1, s2) == 0; \r
+ }\r
+\r
+ /**\r
+ Calculate the shortest length string that could match this pattern\r
+ */\r
+ static int getMinLen(char[] pattern, int pStart, int pEnd, boolean haveEsc, char escChar) \r
+ {\r
+ int m=0;\r
+ for (int l = pStart; l<pEnd; ) \r
+ {\r
+ if (haveEsc && pattern[l] == escChar) { // need one char\r
+ l+=2;\r
+ m++;\r
+ }\r
+ else if (pattern[l] == anyString) {\r
+ l++; // anyString, nothing needed\r
+ }\r
+ else { // anyChar or other chars, need one char\r
+ l++; m++;\r
+ }\r
+ }\r
+ return m;\r
+ }\r
+\r
+ /**\r
+ * checkLengths \r
+ *\r
+ * Returns null if we are not done.\r
+ * Returns true if we are at the end of our value and pattern\r
+ * Returns false if there is more pattern left but out of input value\r
+ *\r
+ * @param vLoc current index into char[] val\r
+ * @param vEnd end index or our value\r
+ * @param pLoc current index into our char[] pattern\r
+ * @param pat pattern char []\r
+ * @param pEnd end index of our pattern []\r
+ */\r
+\r
+ static Boolean checkLengths(int vLoc, int vEnd,\r
+ int pLoc, char[] pat, int pEnd) \r
+ {\r
+ if (vLoc == vEnd) \r
+ {\r
+ if (pLoc == pEnd) \r
+ {\r
+ return Boolean.TRUE;\r
+ }\r
+ else \r
+ {\r
+ // if remainder of pattern is anyString chars, ok\r
+ for (int i=pLoc; i<pEnd; i++) \r
+ {\r
+ if (pat[i] != anyString)\r
+ {\r
+ return Boolean.FALSE; // more to match\r
+ }\r
+ }\r
+ return Boolean.TRUE;\r
+ }\r
+ }\r
+ else if (pLoc == pEnd)\r
+ {\r
+ return Boolean.FALSE; // ran out of pattern\r
+ }\r
+ else return null; // still have strings to match, not done\r
+ }\r
+\r
+ /**\r
+ * matchSpecial\r
+ *\r
+ * check the pattern against the various special character arrays.\r
+ * The array can be anyStringInts, anyCharInts or anyEscChars (always 1)\r
+ */\r
+\r
+ private static boolean matchSpecial(int[] pat, int patStart, int patEnd, int[] specialInts)\r
+ {\r
+ //\r
+ // multi-collation units per char can exceed the pattern length\r
+ // and we fall around the 2nd if statement and falsely return true.\r
+ //\r
+ if (specialInts.length > patEnd - patStart)\r
+ return false;\r
+ if (specialInts.length <= patEnd - patStart)\r
+ {\r
+ for (int index = 0; index < specialInts.length; index++)\r
+ {\r
+ if (pat[patStart + index] != specialInts[index])\r
+ {\r
+ return false; // more to match\r
+ }\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /*\r
+ Most typical interface for character string types with UCS_BASIC and \r
+ territory based collation.\r
+ */\r
+ public static Boolean like(char[] value, int valueLength, char[] pattern, \r
+ int patternLength, RuleBasedCollator collator) \r
+ throws StandardException { \r
+ if (value == null || pattern == null) return null;\r
+ return like(value, valueLength, pattern, patternLength, null, 0, \r
+ collator);\r
+ }\r
+\r
+ // Methods for LIKE transformation at preprocess time:\r
+\r
+ /**\r
+ * Determine whether or not this LIKE can be transformed into optimizable\r
+ * clauses. It can if the pattern is non-null and if the length == 0 or\r
+ * the first character is not a wild card.\r
+ *\r
+ * @param pattern The right side of the LIKE\r
+ *\r
+ * @return Whether or not the LIKE can be transformed\r
+ */\r
+\r
+ public static boolean isOptimizable(String pattern)\r
+ {\r
+ if (pattern == null)\r
+ {\r
+ return false;\r
+ }\r
+\r
+ if (pattern.length() == 0) {\r
+ return true;\r
+ }\r
+\r
+ // if we have pattern matching at start of string, no optimization\r
+ char firstChar = pattern.charAt(0);\r
+\r
+ return (firstChar != anyChar && firstChar != anyString);\r
+ }\r
+\r
+ public static String greaterEqualStringFromParameter(String pattern, int maxWidth)\r
+ throws StandardException {\r
+\r
+ if (pattern == null)\r
+ return null;\r
+\r
+ return greaterEqualString(pattern, (String) null, maxWidth);\r
+ }\r
+\r
+ public static String greaterEqualStringFromParameterWithEsc(String pattern, String escape, int maxWidth)\r
+ throws StandardException {\r
+\r
+ if (pattern == null)\r
+ return null;\r
+\r
+ return greaterEqualString(pattern, escape, maxWidth);\r
+ }\r
+\r
+ /**\r
+ * Return the substring from the pattern for the optimization >= clause.\r
+ *\r
+ * @param pattern The right side of the LIKE\r
+ * @param escape The escape clause\r
+ * @param maxWidth Maximum length of column, for null padding\r
+ *\r
+ * @return The String for the >= clause\r
+ */\r
+ public static String greaterEqualString(String pattern, String escape, int maxWidth)\r
+ throws StandardException\r
+ {\r
+\r
+ int firstAnyChar = pattern.indexOf(anyChar);\r
+ int firstAnyString = pattern.indexOf(anyString);\r
+\r
+ // \r
+ // For Escape we don't utilize any of the stylish code\r
+ // below but brute force walk the pattern to find out\r
+ // what is there, while stripping escapes\r
+ //\r
+\r
+ if ((escape != null) && (escape.length() != 0))\r
+ {\r
+ char escChar = escape.charAt(0);\r
+ if (pattern.indexOf(escChar) != -1)\r
+ {\r
+ // we return a string stripping out the escape char\r
+ // leaving the _? in place as normal chars.\r
+ \r
+ return padWithNulls(greaterEqualString(pattern, escChar), maxWidth);\r
+ }\r
+ // drop through if no escape found\r
+ }\r
+\r
+ if (firstAnyChar == -1)\r
+ {\r
+ if (firstAnyString != -1) // no _, found %\r
+ {\r
+ pattern = pattern.substring(0, firstAnyString);\r
+ }\r
+ }\r
+ else if (firstAnyString == -1)\r
+ {\r
+ pattern = pattern.substring(0, firstAnyChar);\r
+ }\r
+ else\r
+ {\r
+ pattern = pattern.substring(0, (firstAnyChar > firstAnyString) ? \r
+ firstAnyString :\r
+ firstAnyChar);\r
+ }\r
+ return padWithNulls(pattern, maxWidth);\r
+ }\r
+\r
+ /** \r
+ * greaterEqualString -- for Escape clause only\r
+ * \r
+ * Walk the pattern character by character\r
+ * @param pattern like pattern to build from\r
+ * @param escChar the escape character in the pattern\r
+ */\r
+\r
+ private static String greaterEqualString(String pattern, char escChar)\r
+ throws StandardException\r
+ {\r
+ int patternLen = pattern.length();\r
+ char[] patternChars = new char[patternLen];\r
+ char[] result = new char[patternLen];\r
+ pattern.getChars(0, patternLen, patternChars, 0);\r
+\r
+ int r = 0;\r
+ for (int p = 0; p < patternLen && r < patternLen; p++)\r
+ {\r
+ char c = patternChars[p];\r
+ if (c == escChar)\r
+ {\r
+ p++; // don't copy the escape char\r
+\r
+ // run out?\r
+ if (p >= patternLen)\r
+ throw StandardException.newException(\r
+ SQLState.LANG_INVALID_ESCAPE_SEQUENCE);\r
+ result[r++] = patternChars[p];\r
+ continue;\r
+ }\r
+\r
+ // stop on first pattern matching char\r
+ if (c == anyChar || c == anyString)\r
+ {\r
+ return new String(result, 0, r);\r
+ }\r
+\r
+ result[r++] = patternChars[p];\r
+ }\r
+\r
+ // no pattern chars\r
+ return new String(result, 0, r);\r
+ }\r
+\r
+ /**\r
+ * stripEscapesNoPatternChars\r
+ *\r
+ * @param pattern pattern String to search\r
+ * @param escChar the escape character\r
+ *\r
+ * @return a stripped of ESC char string if no pattern chars, null otherwise\r
+ * @exception StandardException thrown if data invalid\r
+ */\r
+\r
+ public static String\r
+ stripEscapesNoPatternChars(String pattern, char escChar)\r
+ throws StandardException\r
+ {\r
+ int patternLen = pattern.length();\r
+ char[] patternChars = new char[patternLen];\r
+ char[] result = new char[patternLen];\r
+ pattern.getChars(0, patternLen, patternChars, 0);\r
+\r
+ int r = 0;\r
+ for (int p = 0; p < patternLen && r < patternLen; p++)\r
+ {\r
+ char c = pattern.charAt(p);\r
+ if (c == escChar)\r
+ {\r
+ p++; // don't copy the escape char\r
+\r
+ // run out?\r
+ if (p >= patternLen)\r
+ throw StandardException.newException(\r
+ SQLState.LANG_INVALID_ESCAPE_SEQUENCE);\r
+ result[r++] = patternChars[p];\r
+ continue;\r
+ }\r
+\r
+ // die on first pattern matching char\r
+ if (c == anyChar || c == anyString)\r
+ {\r
+ return null;\r
+ }\r
+\r
+ result[r++] = patternChars[p];\r
+ }\r
+ return new String(result, 0, r);\r
+ }\r
+\r
+ public static String lessThanStringFromParameter(String pattern, int maxWidth)\r
+ throws StandardException \r
+ {\r
+ if (pattern == null)\r
+ return null;\r
+ return lessThanString(pattern, null, maxWidth);\r
+ }\r
+\r
+ public static String lessThanStringFromParameterWithEsc(String pattern, String escape, int maxWidth)\r
+ throws StandardException\r
+ {\r
+ if (pattern == null)\r
+ return null;\r
+ return lessThanString(pattern, escape, maxWidth);\r
+ }\r
+\r
+ /**\r
+ * Return the substring from the pattern for the < clause.\r
+ *\r
+ * @param pattern The right side of the LIKE\r
+ * @param escape The escape clause\r
+ * @param maxWidth Maximum length of column, for null padding\r
+ *\r
+ * @return The String for the < clause\r
+ * @exception StandardException thrown if data invalid\r
+ */\r
+ public static String lessThanString(String pattern, String escape, int maxWidth)\r
+ throws StandardException\r
+ {\r
+ int lastUsableChar;\r
+ char oldLastChar;\r
+ char newLastChar;\r
+ final int escChar;\r
+\r
+ if ((escape != null) && (escape.length() !=0))\r
+ {\r
+ escChar = escape.charAt(0);\r
+ }\r
+ else {\r
+ // Set escape character to a value outside the char range,\r
+ // so that comparison with a char always evaluates to false.\r
+ escChar = -1;\r
+ }\r
+\r
+ /* Find the last non-wildcard character in the pattern\r
+ * and increment it. In the most common case,\r
+ * "asdf%" becomes "asdg". However, we need to \r
+ * handle the following:\r
+ *\r
+ * pattern return\r
+ * ------- ------\r
+ * "" SUPER_STRING (match against super string)\r
+ * "%..." SUPER_STRING (match against super string)\r
+ * "_..." SUPER_STRING (match against super string)\r
+ * "asdf%" "asdg"\r
+ */\r
+\r
+ StringBuffer upperLimit = new StringBuffer(maxWidth);\r
+\r
+ // Extract the string leading up to the first wildcard.\r
+ for (int i = 0; i < pattern.length(); i++) {\r
+ char c = pattern.charAt(i);\r
+ if (c == escChar) {\r
+ if (++i >= pattern.length()) {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_INVALID_ESCAPE_SEQUENCE);\r
+ }\r
+ c = pattern.charAt(i);\r
+ } else if (c == anyChar || c == anyString) {\r
+ break;\r
+ }\r
+ upperLimit.append(c);\r
+ }\r
+\r
+ // Pattern is empty or starts with wildcard.\r
+ if (upperLimit.length() == 0) {\r
+ return SUPER_STRING;\r
+ }\r
+\r
+ // Increment the last non-wildcard character.\r
+ lastUsableChar = upperLimit.length() - 1;\r
+ oldLastChar = upperLimit.charAt(lastUsableChar);\r
+ newLastChar = oldLastChar;\r
+ newLastChar++;\r
+\r
+ // Check for degenerate roll over\r
+ if (newLastChar < oldLastChar)\r
+ {\r
+ return SUPER_STRING;\r
+ }\r
+\r
+ upperLimit.setCharAt(lastUsableChar, newLastChar);\r
+\r
+ // Pad the string with nulls.\r
+ if (upperLimit.length() < maxWidth) {\r
+ upperLimit.setLength(maxWidth);\r
+ }\r
+\r
+ return upperLimit.toString();\r
+ }\r
+ \r
+ /**\r
+ * Return whether or not the like comparison is still needed after\r
+ * performing the like transformation on a constant string. The\r
+ * comparison is not needed if the constant string is of the form:\r
+ * CONSTANT% (constant followed by a trailing %)\r
+ *\r
+ * @param pattern The right side of the LIKE\r
+ *\r
+ * @return Whether or not the like comparison is still needed.\r
+ */\r
+ public static boolean isLikeComparisonNeeded(String pattern)\r
+ {\r
+ int firstAnyChar = pattern.indexOf(anyChar);\r
+ int firstAnyString = pattern.indexOf(anyString);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(pattern.length() != 0,\r
+ "pattern expected to be non-zero length");\r
+ }\r
+\r
+ // if no pattern matching characters, no LIKE needed\r
+ if (firstAnyChar == -1 && firstAnyString == -1)\r
+ return false;\r
+\r
+ /* Needed if string containts anyChar */\r
+ if (firstAnyChar != -1)\r
+ {\r
+ return true;\r
+ }\r
+\r
+ /* Needed if string contains and anyString in any place\r
+ * other than the last character.\r
+ */\r
+ if (firstAnyString != pattern.length() - 1)\r
+ {\r
+ return true;\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Pad a string with null characters, in order to make it > and <\r
+ * comparable with SQLChar.\r
+ * \r
+ * @param string The string to pad\r
+ * @param len Max number of characters to pad to\r
+ * @return the string padded with 0s up to the given length\r
+ */\r
+ private static String padWithNulls(String string, int len) \r
+ {\r
+ if(string.length() >= len)\r
+ return string;\r
+\r
+ StringBuffer buf = new StringBuffer(len).append(string);\r
+ buf.setLength(len);\r
+ \r
+ return buf.toString();\r
+ }\r
+}\r