001: /*
002:
003: Derby - Class org.apache.derby.iapi.types.Like
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.types;
023:
024: // RESOLVE: MOVE THIS CLASS TO PROTOCOL (See LikeOperatorNode)
025:
026: import org.apache.derby.iapi.services.sanity.SanityManager;
027:
028: import org.apache.derby.iapi.error.StandardException;
029: import org.apache.derby.iapi.reference.SQLState;
030:
031: import java.text.CollationElementIterator;
032: import java.text.Collator;
033: import java.text.RuleBasedCollator;
034: import java.util.Locale;
035:
036: /**
037: Like matching algorithm. Not too speedy for %s.
038:
039: SQL92 says the escape character can only and must be followed
040: by itself, %, or _. So if you choose % or _ as the escape character,
041: you can no longer do that sort of matching.
042:
043: Not the most recent Like -- missing the unit tests
044:
045: @author ames
046: */
047: public class Like {
048: private static final char anyChar = '_';
049: private static final char anyString = '%';
050:
051: private static final String SUPER_STRING = "\uffff";
052:
053: private Like() { // do not instantiate
054: }
055:
056: /**
057: @param val value to compare. if null, result is null.
058: @param valLength length of val
059: @param pat pattern to compare. if null, result is null.
060: @param patLength length of pat
061: @param escape escape character. Must be 1 char long.
062: if null, no escape character is used.
063: @param escapeLength length of escape
064:
065: @return null if val or pat null, otherwise true if match
066: and false if not.
067: @exception StandardException thrown if data invalid
068: */
069: public static Boolean like(char[] val, int valLength, char[] pat,
070: int patLength, char[] escape, int escapeLength)
071: throws StandardException {
072: return like(val, 0, valLength, pat, 0, patLength, escape,
073: escapeLength);
074: }
075:
076: /**
077: For national chars.
078: @param val value to compare. if null, result is null.
079: @param valLength length of val
080: @param pat pattern to compare. if null, result is null.
081: @param patLength length of pat
082: @param escape escape character. Must be 1 char long.
083: if null, no escape character is used.
084: @param escapeLength length of escape
085: @param collator The collator to use.
086:
087: @return null if val or pat null, otherwise true if match
088: and false if not.
089: @exception StandardException thrown if data invalid
090: */
091: public static Boolean like(int[] val, int valLength, int[] pat,
092: int patLength, int[] escape, int escapeLength,
093: RuleBasedCollator collator) throws StandardException {
094: return like(val, 0, valLength, pat, 0, patLength, escape,
095: escapeLength, collator);
096: }
097:
098: /* non-national chars */
099: private static Boolean like(char[] val, int vLoc, // start at val[vLoc]
100: int vEnd, // end at val[vEnd]
101: char[] pat, int pLoc, // start at pat[pLoc]
102: int pEnd, // end at pat[pEnd]
103: char[] escape, int escapeLength) throws StandardException {
104: char escChar = ' ';
105: boolean haveEsc = true;
106:
107: if (val == null)
108: return null;
109: if (pat == null)
110: return null;
111:
112: if (escape == null) {
113: haveEsc = false;
114: } else {
115: escChar = escape[0];
116: }
117:
118: Boolean result;
119:
120: while (true) {
121:
122: if ((result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd)) != null) {
123: return result;
124: }
125:
126: // go until we get a special char in the pattern or hit EOS
127: while (pat[pLoc] != anyChar && pat[pLoc] != anyString
128: && ((!haveEsc) || pat[pLoc] != escChar)) {
129: if (val[vLoc] == pat[pLoc]) {
130: vLoc++;
131: pLoc++;
132:
133: result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd);
134: if (result != null)
135: return result;
136: } else {
137: return Boolean.FALSE;
138: }
139: }
140:
141: // deal with escChar first, as it can be escaping a special char
142: // and can be a special char itself.
143: if (haveEsc && pat[pLoc] == escChar) {
144: pLoc++;
145: if (pLoc == pEnd) {
146: throw StandardException
147: .newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);
148: }
149: if (pat[pLoc] != escChar && pat[pLoc] != anyChar
150: && pat[pLoc] != anyString) {
151: throw StandardException
152: .newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);
153: }
154: // regardless of the char in pat, it must match exactly:
155: if (val[vLoc] == pat[pLoc]) {
156: vLoc++;
157: pLoc++;
158:
159: result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd);
160: if (result != null)
161: return result;
162: } else
163: return Boolean.FALSE;
164: } else if (pat[pLoc] == anyChar) {
165: // regardless of the char, it matches
166: vLoc++;
167: pLoc++;
168:
169: result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd);
170: if (result != null)
171: return result;
172: } else if (pat[pLoc] == anyString) {
173: // catch the simple cases -- end of the pattern or of the string
174: if (pLoc + 1 == pEnd)
175: return Boolean.TRUE;
176:
177: // would return true, but caught in checkLengths above
178: if (SanityManager.DEBUG)
179: SanityManager.ASSERT(vLoc != vEnd,
180: "Should have been found already");
181:
182: //if (vLoc == vEnd) // caught in checkLengths
183: //return Boolean.TRUE;
184: // check if remainder of pattern is anyString's
185: // if escChar == anyString, we couldn't be here
186: boolean anys = true;
187: for (int i = pLoc + 1; i < pEnd; i++)
188: if (pat[i] != anyString) {
189: anys = false;
190: break;
191: }
192: if (anys)
193: return Boolean.TRUE;
194:
195: // pattern can match 0 or more chars in value.
196: // to test that, we take the remainder of pattern and
197: // apply it to ever-shorter remainders of value until
198: // we hit a match.
199:
200: // the loop never continues from this point -- we will
201: // always generate an answer here.
202:
203: // REMIND: there are smarter ways to pick the remainders
204: // and do this matching.
205:
206: // num chars left in value includes current char
207: int vRem = vEnd - vLoc;
208:
209: int n = 0;
210:
211: // num chars left in pattern excludes the anychar
212: int minLen = getMinLen(pat, pLoc + 1, pEnd, haveEsc,
213: escChar);
214: for (int i = vRem; i >= minLen; i--) {
215: Boolean restResult = Like.like(val, vLoc + n, vLoc
216: + n + i, pat, pLoc + 1, pEnd, escape,
217: escapeLength);
218: if (SanityManager.DEBUG) {
219: if (restResult == null) {
220: String vStr = new String(val, vLoc + n, i);
221: String pStr = new String(pat, pLoc + 1,
222: pEnd - (pLoc + 1));
223: SanityManager
224: .THROWASSERT("null result on like(value = "
225: + vStr
226: + ", pat = "
227: + pStr
228: + ")");
229: }
230: }
231: if (restResult.booleanValue())
232: return restResult;
233:
234: n++;
235: }
236: // none of the possibilities worked
237: return Boolean.FALSE;
238: }
239: }
240: }
241:
242: /* national chars */
243: private static Boolean like(int[] val, int vLoc, // start at val[vLoc]
244: int vEnd, // end at val[vEnd]
245: int[] pat, int pLoc, // start at pat[pLoc]
246: int pEnd, // end at pat[pEnd]
247: int[] escape, int escapeLength, RuleBasedCollator collator)
248: throws StandardException {
249: int[] escCharInts = null;
250: boolean haveEsc = true;
251: int[] anyCharInts = new int[1]; // assume only 1 int
252: int[] anyStringInts = new int[1]; // assume only 1 int
253:
254: if (val == null)
255: return null;
256: if (pat == null)
257: return null;
258:
259: if (escape == null) {
260: haveEsc = false;
261: } else {
262: escCharInts = escape;
263: }
264:
265: Boolean result;
266:
267: // get the collation integer representing "_"
268: CollationElementIterator cei = collator
269: .getCollationElementIterator("_");
270: anyCharInts[0] = cei.next();
271: {
272: int nextInt;
273:
274: // There may be multiple ints representing this character
275: while ((nextInt = cei.next()) != CollationElementIterator.NULLORDER) {
276: int[] temp = new int[anyCharInts.length + 1];
277: for (int index = 0; index < anyCharInts.length; index++) {
278: temp[index] = anyCharInts[index];
279: }
280: temp[anyCharInts.length] = nextInt;
281: anyCharInts = temp;
282: }
283: }
284: // get the collation integer representing "%"
285: cei = collator.getCollationElementIterator("%");
286: anyStringInts[0] = cei.next();
287: {
288: int nextInt;
289:
290: // There may be multiple ints representing this character
291: while ((nextInt = cei.next()) != CollationElementIterator.NULLORDER) {
292: int[] temp = new int[anyStringInts.length + 1];
293: for (int index = 0; index < anyStringInts.length; index++) {
294: temp[index] = anyStringInts[index];
295: }
296: temp[anyStringInts.length] = nextInt;
297: anyStringInts = temp;
298: }
299: }
300:
301: while (true) {
302: // returns null if more work to do, otherwise match Boolean
303: result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd,
304: anyStringInts);
305: if (result != null)
306: return result;
307:
308: // go until we get a special char in the pattern or hit EOS
309: while ((!matchSpecial(pat, pLoc, pEnd, anyCharInts))
310: && (!matchSpecial(pat, pLoc, pEnd, anyStringInts))
311: && ((!haveEsc) || (!matchSpecial(pat, pLoc, pEnd,
312: escCharInts)))) {
313: if (val[vLoc] == pat[pLoc]) {
314: vLoc++;
315: pLoc++;
316:
317: result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd,
318: anyStringInts);
319: if (result != null) {
320: return result;
321: }
322: } else {
323: return Boolean.FALSE;
324: }
325: }
326:
327: // deal with escCharInt first, as it can be escaping a special char
328: // and can be a special char itself.
329: if (haveEsc && matchSpecial(pat, pLoc, pEnd, escCharInts)) {
330: pLoc += escCharInts.length;
331: if (pLoc == pEnd) {
332: throw StandardException
333: .newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);
334: }
335:
336: int[] specialInts = null;
337: if (matchSpecial(pat, pLoc, pEnd, escCharInts)) {
338: specialInts = escCharInts;
339: }
340: if (matchSpecial(pat, pLoc, pEnd, anyCharInts)) {
341: specialInts = anyCharInts;
342: }
343: if (matchSpecial(pat, pLoc, pEnd, anyStringInts)) {
344: specialInts = anyStringInts;
345: }
346: if (specialInts == null) {
347: throw StandardException
348: .newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);
349: }
350: // regardless of the char in pat, it must match exactly:
351: for (int index = 0; index < specialInts.length; index++) {
352: if (val[vLoc + index] != pat[pLoc + index]) {
353: return Boolean.FALSE;
354: }
355: }
356:
357: vLoc += specialInts.length;
358: pLoc += specialInts.length;
359:
360: // returns null if more work to do, otherwise match Boolean
361: result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd,
362: anyStringInts);
363:
364: if (result != null)
365: return result;
366: } else if (matchSpecial(pat, pLoc, pEnd, anyCharInts)) {
367: // regardless of the char, it matches
368: vLoc += anyCharInts.length;
369: pLoc += anyCharInts.length;
370:
371: result = checkLengths(vLoc, vEnd, pLoc, pat, pEnd,
372: anyStringInts);
373: if (result != null)
374: return result;
375: } else if (matchSpecial(pat, pLoc, pEnd, anyStringInts)) {
376: // catch the simple cases -- end of the pattern or of the string
377: if (pLoc + 1 == pEnd)
378: return Boolean.TRUE;
379:
380: // would return true, but caught in checkLengths above
381: if (SanityManager.DEBUG)
382: SanityManager.ASSERT(vLoc != vEnd,
383: "Should have been found already");
384:
385: if (vLoc == vEnd)
386: return Boolean.TRUE;
387:
388: // check if remainder of pattern is anyString's
389: // if escChar == anyString, we couldn't be here
390: // If there is an escape in the pattern we break
391: boolean allPercentChars = true;
392: for (int i = pLoc + 1; i < pEnd; i++) {
393: if (!matchSpecial(pat, i, pEnd, anyStringInts)) {
394: allPercentChars = false;
395: break;
396: }
397: }
398: if (allPercentChars)
399: return Boolean.TRUE;
400:
401: // pattern can match 0 or more chars in value.
402: // to test that, we take the remainder of pattern and
403: // apply it to ever-shorter remainders of value until
404: // we hit a match.
405:
406: // the loop never continues from this point -- we will
407: // always generate an answer here.
408:
409: // REMIND: there are smarter ways to pick the remainders
410: // and do this matching.
411:
412: // num chars left in value includes current char
413: int vRem = vEnd - vLoc;
414:
415: int n = 0;
416:
417: // num chars left in pattern excludes the anyString
418: int minLen = getMinLen(pat, pLoc + 1, pEnd, haveEsc,
419: escCharInts, anyStringInts);
420: for (int i = vRem; i >= minLen; i--) {
421: Boolean restResult = Like.like(val, vLoc + n, vLoc
422: + n + i, pat, pLoc + 1, pEnd, escape,
423: escapeLength, collator);
424: if (SanityManager.DEBUG) {
425: if (restResult == null) {
426: SanityManager
427: .THROWASSERT("null result on like(vLoc+n = "
428: + (vLoc + n)
429: + ", i = "
430: + i
431: + ", pLoc+1 = "
432: + (pLoc + 1)
433: + ", pEnd-(pLoc+1) = "
434: + (pEnd - (pLoc + 1)) + ")");
435: }
436: }
437: if (restResult.booleanValue())
438: return restResult;
439:
440: n++;
441: }
442: // none of the possibilities worked
443: return Boolean.FALSE;
444: }
445: }
446: }
447:
448: /**
449: Calculate the shortest length string that could match this pattern for non-national chars
450: */
451: static int getMinLen(char[] pattern, int pStart, int pEnd,
452: boolean haveEsc, char escChar) {
453: int m = 0;
454: for (int l = pStart; l < pEnd;) {
455: if (haveEsc && pattern[l] == escChar) { // need one char
456: l += 2;
457: m++;
458: } else if (pattern[l] == anyString) {
459: l++; // anyString, nothing needed
460: } else { // anyChar or other chars, need one char
461: l++;
462: m++;
463: }
464: }
465: return m;
466: }
467:
468: /**
469: Calculate the shortest length string that could match this pattern for national chars
470: */
471: static int getMinLen(int[] pattern, int pStart, int pEnd,
472: boolean haveEsc, int[] escCharInts, int[] anyStringInts) {
473: int m = 0;
474: for (int l = pStart; l < pEnd;) {
475: if (haveEsc && matchSpecial(pattern, l, pEnd, escCharInts)) {
476: l += escCharInts.length + 1;
477: m += escCharInts.length;
478: } else if (matchSpecial(pattern, l, pEnd, anyStringInts)) {
479: l += anyStringInts.length; // anyString, nothing needed
480: } else { // anyChar or other chars, need one char
481: l++;
482: m++;
483: }
484: }
485: return m;
486: }
487:
488: /**
489: * checkLengths -- non-national chars
490: *
491: * Returns null if we are not done.
492: * Returns true if we are at the end of our value and pattern
493: * Returns false if there is more pattern left but out of input value
494: *
495: * @param vLoc current index into char[] val
496: * @param vEnd end index or our value
497: * @param pLoc current index into our char[] pattern
498: * @param pat pattern char []
499: * @param pEnd end index of our pattern []
500: */
501:
502: static Boolean checkLengths(int vLoc, int vEnd, int pLoc,
503: char[] pat, int pEnd) {
504: if (vLoc == vEnd) {
505: if (pLoc == pEnd) {
506: return Boolean.TRUE;
507: } else {
508: // if remainder of pattern is anyString chars, ok
509: for (int i = pLoc; i < pEnd; i++) {
510: if (pat[i] != anyString) {
511: return Boolean.FALSE; // more to match
512: }
513: }
514: return Boolean.TRUE;
515: }
516: } else if (pLoc == pEnd) {
517: return Boolean.FALSE; // ran out of pattern
518: } else
519: return null; // still have strings to match, not done
520: }
521:
522: /**
523: * checkLengths -- national chars
524: *
525: * Returns null if we are not done.
526: * Returns true if we are at the end of our value and pattern
527: * Returns false if there is more pattern left but out of input value
528: *
529: * @param vLoc current index into int[] val
530: * @param vEnd end index or our value
531: * @param pLoc current index into our int[] pattern
532: * @param pat pattern int []
533: * @param pEnd end index of our pattern []
534: */
535:
536: static Boolean checkLengths(int vLoc, int vEnd, int pLoc,
537: int[] pat, int pEnd, int[] anyStringInts) {
538: if (vLoc == vEnd) {
539: if (pLoc == pEnd) {
540: return Boolean.TRUE;
541: } else {
542: // if remainder of pattern is anyString chars, ok
543: for (int i = pLoc; i < pEnd; i += anyStringInts.length) {
544: if (!matchSpecial(pat, i, pEnd, anyStringInts)) {
545: return Boolean.FALSE;
546: }
547: }
548: return Boolean.TRUE;
549: }
550: } else if (pLoc == pEnd) {
551: return Boolean.FALSE; // ran out of pattern
552: } else
553: return null; // still have strings to match, not done
554: }
555:
556: /**
557: * matchSpecial
558: *
559: * check the pattern against the various special character arrays.
560: * The array can be anyStringInts, anyCharInts or anyEscChars (always 1)
561: */
562:
563: private static boolean matchSpecial(int[] pat, int patStart,
564: int patEnd, int[] specialInts) {
565: //
566: // multi-collation units per char can exceed the pattern length
567: // and we fall around the 2nd if statement and falsely return true.
568: //
569: if (specialInts.length > patEnd - patStart)
570: return false;
571: if (specialInts.length <= patEnd - patStart) {
572: for (int index = 0; index < specialInts.length; index++) {
573: if (pat[patStart + index] != specialInts[index]) {
574: return false; // more to match
575: }
576: }
577: }
578: return true;
579: }
580:
581: /*
582: Most typical interface for non-national chars
583: */
584: public static Boolean like(char[] value, int valueLength,
585: char[] pattern, int patternLength) throws StandardException {
586: if (value == null || pattern == null)
587: return null;
588: return like(value, valueLength, pattern, patternLength, null, 0);
589: }
590:
591: /*
592: Most typical interface for national chars
593: */
594: public static Boolean like(int[] value, int valueLength,
595: int[] pattern, int patternLength, RuleBasedCollator collator)
596: throws StandardException {
597: if (value == null || pattern == null)
598: return null;
599: return like(value, valueLength, pattern, patternLength, null,
600: 0, collator);
601: }
602:
603: // Methods for LIKE transformation at preprocess time:
604:
605: /**
606: * Determine whether or not this LIKE can be transformed into optimizable
607: * clauses. It can if the pattern is non-null and if the length == 0 or
608: * the first character is not a wild card.
609: *
610: * @param pattern The right side of the LIKE
611: *
612: * @return Whether or not the LIKE can be transformed
613: */
614:
615: public static boolean isOptimizable(String pattern) {
616: if (pattern == null) {
617: return false;
618: }
619:
620: if (pattern.length() == 0) {
621: return true;
622: }
623:
624: // if we have pattern matching at start of string, no optimization
625: char firstChar = pattern.charAt(0);
626:
627: return (firstChar != anyChar && firstChar != anyString);
628: }
629:
630: public static String greaterEqualStringFromParameter(
631: String pattern, int maxWidth) throws StandardException {
632:
633: if (pattern == null)
634: return null;
635:
636: return greaterEqualString(pattern, (String) null, maxWidth);
637: }
638:
639: public static String greaterEqualStringFromParameterWithEsc(
640: String pattern, String escape, int maxWidth)
641: throws StandardException {
642:
643: if (pattern == null)
644: return null;
645:
646: return greaterEqualString(pattern, escape, maxWidth);
647: }
648:
649: /**
650: * Return the substring from the pattern for the optimization >= clause.
651: *
652: * @param pattern The right side of the LIKE
653: * @param escape The escape clause
654: * @param maxWidth Maximum length of column, for null padding
655: *
656: * @return The String for the >= clause
657: */
658: public static String greaterEqualString(String pattern,
659: String escape, int maxWidth) throws StandardException {
660:
661: int firstAnyChar = pattern.indexOf(anyChar);
662: int firstAnyString = pattern.indexOf(anyString);
663:
664: //
665: // For Escape we don't utilize any of the stylish code
666: // below but brute force walk the pattern to find out
667: // what is there, while stripping escapes
668: //
669:
670: if ((escape != null) && (escape.length() != 0)) {
671: char escChar = escape.charAt(0);
672: if (pattern.indexOf(escChar) != -1) {
673: // we return a string stripping out the escape char
674: // leaving the _? in place as normal chars.
675:
676: return padWithNulls(
677: greaterEqualString(pattern, escChar), maxWidth);
678: }
679: // drop through if no escape found
680: }
681:
682: if (firstAnyChar == -1) {
683: if (firstAnyString != -1) // no _, found %
684: {
685: pattern = pattern.substring(0, firstAnyString);
686: }
687: } else if (firstAnyString == -1) {
688: pattern = pattern.substring(0, firstAnyChar);
689: } else {
690: pattern = pattern.substring(0,
691: (firstAnyChar > firstAnyString) ? firstAnyString
692: : firstAnyChar);
693: }
694: return padWithNulls(pattern, maxWidth);
695: }
696:
697: /**
698: * greaterEqualString -- for Escape clause only
699: *
700: * Walk the pattern character by character
701: * @param pattern like pattern to build from
702: * @param escChar the escape character in the pattern
703: */
704:
705: private static String greaterEqualString(String pattern,
706: char escChar) throws StandardException {
707: int patternLen = pattern.length();
708: char[] patternChars = new char[patternLen];
709: char[] result = new char[patternLen];
710: pattern.getChars(0, patternLen, patternChars, 0);
711:
712: int r = 0;
713: for (int p = 0; p < patternLen && r < patternLen; p++) {
714: char c = patternChars[p];
715: if (c == escChar) {
716: p++; // don't copy the escape char
717:
718: // run out?
719: if (p >= patternLen)
720: throw StandardException
721: .newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);
722: result[r++] = patternChars[p];
723: continue;
724: }
725:
726: // stop on first pattern matching char
727: if (c == anyChar || c == anyString) {
728: return new String(result, 0, r);
729: }
730:
731: result[r++] = patternChars[p];
732: }
733:
734: // no pattern chars
735: return new String(result, 0, r);
736: }
737:
738: /**
739: * stripEscapesNoPatternChars
740: *
741: * @param pattern pattern String to search
742: * @param escChar the escape character
743: *
744: * @return a stripped of ESC char string if no pattern chars, null otherwise
745: * @exception StandardException thrown if data invalid
746: */
747:
748: public static String stripEscapesNoPatternChars(String pattern,
749: char escChar) throws StandardException {
750: int patternLen = pattern.length();
751: char[] patternChars = new char[patternLen];
752: char[] result = new char[patternLen];
753: pattern.getChars(0, patternLen, patternChars, 0);
754:
755: int r = 0;
756: for (int p = 0; p < patternLen && r < patternLen; p++) {
757: char c = pattern.charAt(p);
758: if (c == escChar) {
759: p++; // don't copy the escape char
760:
761: // run out?
762: if (p >= patternLen)
763: throw StandardException
764: .newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);
765: result[r++] = patternChars[p];
766: continue;
767: }
768:
769: // die on first pattern matching char
770: if (c == anyChar || c == anyString) {
771: return null;
772: }
773:
774: result[r++] = patternChars[p];
775: }
776: return new String(result, 0, r);
777: }
778:
779: public static String lessThanStringFromParameter(String pattern,
780: int maxWidth) throws StandardException {
781: if (pattern == null)
782: return null;
783: return lessThanString(pattern, null, maxWidth);
784: }
785:
786: public static String lessThanStringFromParameterWithEsc(
787: String pattern, String escape, int maxWidth)
788: throws StandardException {
789: if (pattern == null)
790: return null;
791: return lessThanString(pattern, escape, maxWidth);
792: }
793:
794: /**
795: * Return the substring from the pattern for the < clause.
796: *
797: * @param pattern The right side of the LIKE
798: * @param escape The escape clause
799: * @param maxWidth Maximum length of column, for null padding
800: *
801: * @return The String for the < clause
802: * @exception StandardException thrown if data invalid
803: */
804: public static String lessThanString(String pattern, String escape,
805: int maxWidth) throws StandardException {
806: int lastUsableChar;
807: char oldLastChar;
808: char newLastChar;
809: final int escChar;
810:
811: if ((escape != null) && (escape.length() != 0)) {
812: escChar = escape.charAt(0);
813: } else {
814: // Set escape character to a value outside the char range,
815: // so that comparison with a char always evaluates to false.
816: escChar = -1;
817: }
818:
819: /* Find the last non-wildcard character in the pattern
820: * and increment it. In the most common case,
821: * "asdf%" becomes "asdg". However, we need to
822: * handle the following:
823: *
824: * pattern return
825: * ------- ------
826: * "" SUPER_STRING (match against super string)
827: * "%..." SUPER_STRING (match against super string)
828: * "_..." SUPER_STRING (match against super string)
829: * "asdf%" "asdg"
830: */
831:
832: StringBuffer upperLimit = new StringBuffer(maxWidth);
833:
834: // Extract the string leading up to the first wildcard.
835: for (int i = 0; i < pattern.length(); i++) {
836: char c = pattern.charAt(i);
837: if (c == escChar) {
838: if (++i >= pattern.length()) {
839: throw StandardException
840: .newException(SQLState.LANG_INVALID_ESCAPE_SEQUENCE);
841: }
842: c = pattern.charAt(i);
843: } else if (c == anyChar || c == anyString) {
844: break;
845: }
846: upperLimit.append(c);
847: }
848:
849: // Pattern is empty or starts with wildcard.
850: if (upperLimit.length() == 0) {
851: return SUPER_STRING;
852: }
853:
854: // Increment the last non-wildcard character.
855: lastUsableChar = upperLimit.length() - 1;
856: oldLastChar = upperLimit.charAt(lastUsableChar);
857: newLastChar = oldLastChar;
858: newLastChar++;
859:
860: // Check for degenerate roll over
861: if (newLastChar < oldLastChar) {
862: return SUPER_STRING;
863: }
864:
865: upperLimit.setCharAt(lastUsableChar, newLastChar);
866:
867: // Pad the string with nulls.
868: if (upperLimit.length() < maxWidth) {
869: upperLimit.setLength(maxWidth);
870: }
871:
872: return upperLimit.toString();
873: }
874:
875: /**
876: * Return whether or not the like comparison is still needed after
877: * performing the like transformation on a constant string. The
878: * comparison is not needed if the constant string is of the form:
879: * CONSTANT% (constant followed by a trailing %)
880: *
881: * @param pattern The right side of the LIKE
882: *
883: * @return Whether or not the like comparison is still needed.
884: */
885: public static boolean isLikeComparisonNeeded(String pattern) {
886: int firstAnyChar = pattern.indexOf(anyChar);
887: int firstAnyString = pattern.indexOf(anyString);
888:
889: if (SanityManager.DEBUG) {
890: SanityManager.ASSERT(pattern.length() != 0,
891: "pattern expected to be non-zero length");
892: }
893:
894: // if no pattern matching characters, no LIKE needed
895: if (firstAnyChar == -1 && firstAnyString == -1)
896: return false;
897:
898: /* Needed if string containts anyChar */
899: if (firstAnyChar != -1) {
900: return true;
901: }
902:
903: /* Needed if string contains and anyString in any place
904: * other than the last character.
905: */
906: if (firstAnyString != pattern.length() - 1) {
907: return true;
908: }
909:
910: return false;
911: }
912:
913: /**
914: * Pad a string with null characters, in order to make it > and <
915: * comparable with SQLChar.
916: *
917: * @param string The string to pad
918: * @param len Max number of characters to pad to
919: * @return the string padded with 0s up to the given length
920: */
921: private static String padWithNulls(String string, int len) {
922: if (string.length() >= len)
923: return string;
924:
925: StringBuffer buf = new StringBuffer(len).append(string);
926: buf.setLength(len);
927:
928: return buf.toString();
929: }
930: }
|