KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > internetcds > jdbc > tds > EscapeProcessor


1 //
2
// Copyright 1998 CDS Networks, Inc., Medford Oregon
3
//
4
// All rights reserved.
5
//
6
// Redistribution and use in source and binary forms, with or without
7
// modification, are permitted provided that the following conditions are met:
8
// 1. Redistributions of source code must retain the above copyright
9
// notice, this list of conditions and the following disclaimer.
10
// 2. Redistributions in binary form must reproduce the above copyright
11
// notice, this list of conditions and the following disclaimer in the
12
// documentation and/or other materials provided with the distribution.
13
// 3. All advertising materials mentioning features or use of this software
14
// must display the following acknowledgement:
15
// This product includes software developed by CDS Networks, Inc.
16
// 4. The name of CDS Networks, Inc. may not be used to endorse or promote
17
// products derived from this software without specific prior
18
// written permission.
19
//
20
// THIS SOFTWARE IS PROVIDED BY CDS NETWORKS, INC. ``AS IS'' AND
21
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
// ARE DISCLAIMED. IN NO EVENT SHALL CDS NETWORKS, INC. BE LIABLE
24
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30
// SUCH DAMAGE.
31
//
32

33
34 package com.internetcds.jdbc.tds;
35
36 import java.sql.*;
37
38
39 abstract public class EscapeProcessor
40 {
41    public static final String JavaDoc cvsVersion = "$Id: EscapeProcessor.java,v 1.1 2006/06/23 10:39:04 sinisa Exp $";
42
43    String JavaDoc input;
44
45    public EscapeProcessor(String JavaDoc sql)
46    {
47       input = sql;
48    } // EscapeProcessor()
49

50    abstract public String JavaDoc expandDBSpecificFunction(String JavaDoc escapeSequence)
51       throws SQLException;
52
53    /**
54     * is the string made up only of digits?
55     * <p>
56     * Note- Leading/trailing spaces or signs are not considered digits.
57     *
58     * @return true if the string has only digits, false otherwise.
59     */

60    private static boolean validDigits(String JavaDoc str)
61    {
62       boolean result = true;
63       int i;
64
65       for(i=0, result = true; result && i<str.length(); i++)
66       {
67          result = result && Character.isDigit(str.charAt(i));
68       }
69       return result;
70    } // validDigits()
71

72    /**
73     * Given a string and an index into that string return the index
74     * of the next non-whitespace character.
75     *
76     * @return index of next non-whitespace character.
77     */

78    private static int skipWhitespace(String JavaDoc str, int i)
79    {
80       while(i<str.length() && Character.isWhitespace(str.charAt(i)))
81       {
82          i++;
83       }
84       return i;
85    } // skipWhitespace()
86

87
88    /**
89     * Given a string and an index into that string, advanvance the index
90     * iff it is on a quote character.
91     *
92     * @return
93     */

94    private static int skipQuote(String JavaDoc str, int i)
95    {
96
97       // skip over the leading quote if it exists
98
if (i<str.length() && (str.charAt(i)=='\'' || str.charAt(i)=='"'))
99       {
100          // XXX Note- The spec appears to prohibit the quote character,
101
// but many drivers allow it. We should probably control this
102
// with a flag.
103
i++;
104       }
105       return i;
106    } // skipQuote()
107

108    /**
109     * Convert a JDBC SQL escape date sequence into a datestring recognized
110     * by SQLServer.
111     *
112     */

113    private static String JavaDoc getDate(String JavaDoc str)
114       throws SQLException
115    {
116       int i;
117
118       // skip over the "d "
119
i = 2;
120       
121       // skip any additional spaces
122
i = skipWhitespace(str, i);
123
124       i = skipQuote(str, i);
125
126       // i is now up to the point where the date had better start.
127
if (((str.length()-i) < 10)
128           || str.charAt(i+4)!='-' || str.charAt(i+7)!='-')
129       {
130          throw new SQLException("Malformed date");
131       }
132
133       String JavaDoc year = str.substring(i, i+4);
134       String JavaDoc month = str.substring(i+5, i+5+2);
135       String JavaDoc day = str.substring(i+5+3, i+5+3+2);
136
137       // Make sure the year, month, and day are numeric
138
if (!validDigits(year) || !validDigits(month) || !validDigits(day))
139       {
140          throw new SQLException("Malformed date");
141       }
142
143       // Make sure there isn't any garbage after the date
144
i = i+10;
145       i = skipWhitespace(str, i);
146       i = skipQuote(str, i);
147       i = skipWhitespace(str, i);
148
149       if (i<str.length())
150       {
151          throw new SQLException("Malformed date");
152       }
153       
154       return "'" + year + month + day + "'";
155    } // getDate()
156

157
158    /**
159     * Convert a JDBC SQL escape time sequence into a time string recognized
160     * by SQLServer.
161     *
162     */

163    private static String JavaDoc getTime(String JavaDoc str)
164       throws SQLException
165    {
166       int i;
167
168       // skip over the "t "
169
i = 2;
170       
171       // skip any additional spaces
172
i = skipWhitespace(str, i);
173
174       i = skipQuote(str, i);
175
176       // i is now up to the point where the date had better start.
177
if (((str.length()-i) < 8)
178           || str.charAt(i+2)!=':' || str.charAt(i+5)!=':')
179       {
180          throw new SQLException("Malformed time");
181       }
182
183       String JavaDoc hour = str.substring(i, i+2);
184       String JavaDoc minute = str.substring(i+3, i+3+2);
185       String JavaDoc second = str.substring(i+3+3, i+3+3+2);
186
187       // Make sure the year, month, and day are numeric
188
if (!validDigits(hour) || !validDigits(minute) || !validDigits(second))
189       {
190          throw new SQLException("Malformed time");
191       }
192
193       // Make sure there isn't any garbage after the time
194
i = i+8;
195       i = skipWhitespace(str, i);
196       i = skipQuote(str, i);
197       i = skipWhitespace(str, i);
198
199       if (i<str.length())
200       {
201          throw new SQLException("Malformed time");
202       }
203       
204       return "'" + hour + ":" + minute + ":" + second + "'";
205    } // getTime()
206

207
208    /**
209     * Convert a JDBC SQL escape timestamp sequence into a date-time string
210     * by SQLServer.
211     *
212     */

213    private static String JavaDoc getTimestamp(String JavaDoc str)
214       throws SQLException
215    {
216       int i;
217
218       // skip over the "d "
219
i = 2;
220       
221       // skip any additional spaces
222
i = skipWhitespace(str, i);
223
224       i = skipQuote(str, i);
225
226       // i is now up to the point where the date had better start.
227
if (((str.length()-i) < 19)
228           || str.charAt(i+4)!='-' || str.charAt(i+7)!='-')
229       {
230          throw new SQLException("Malformed date");
231       }
232
233       String JavaDoc year = str.substring(i, i+4);
234       String JavaDoc month = str.substring(i+5, i+5+2);
235       String JavaDoc day = str.substring(i+5+3, i+5+3+2);
236
237       // Make sure the year, month, and day are numeric
238
if (!validDigits(year) || !validDigits(month) || !validDigits(day))
239       {
240          throw new SQLException("Malformed date");
241       }
242
243       // Make sure there is at least one space between date and time
244
i = i+10;
245       if (!Character.isWhitespace(str.charAt(i)))
246       {
247          throw new SQLException("Malformed date");
248       }
249       
250       // skip the whitespace
251
i = skipWhitespace(str, i);
252
253       // see if it could be a time
254
if (((str.length()-i) < 8)
255           || str.charAt(i+2)!=':' || str.charAt(i+5)!=':')
256       {
257          throw new SQLException("Malformed time");
258       }
259       String JavaDoc hour = str.substring(i, i+2);
260       String JavaDoc minute = str.substring(i+3, i+3+2);
261       String JavaDoc second = str.substring(i+3+3, i+3+3+2);
262       String JavaDoc fraction = "000";
263       i = i+8;
264       if (str.length()>i && str.charAt(i)=='.')
265       {
266          fraction = "";
267          i++;
268          while(str.length()>i && validDigits(str.substring(i,i+1)))
269          {
270             fraction = fraction + str.substring(i,i+1);
271             i++;
272          }
273          if (fraction.length()>3)
274          {
275             fraction = fraction.substring(0,3);
276          }
277          else
278          {
279             while(fraction.length()<3)
280             {
281                fraction = fraction + "0";
282             }
283          }
284       }
285
286       
287       // Make sure there isn't any garbage after the time
288
i = skipWhitespace(str, i);
289       i = skipQuote(str, i);
290       i = skipWhitespace(str, i);
291
292       if (i<str.length())
293       {
294          throw new SQLException("Malformed date");
295       }
296       
297       return ("'" + year + month + day + " "
298               + hour + ":" + minute + ":" + second + "." + fraction + "'");
299    } // getTimestamp()
300

301
302    public String JavaDoc expandEscape(String JavaDoc escapeSequence)
303       throws SQLException
304    {
305       String JavaDoc str = new String JavaDoc(escapeSequence);
306       String JavaDoc result = null;
307
308       // XXX Is it always okay to trim leading and trailing blanks?
309
str = str.trim();
310
311       if (str.startsWith("fn "))
312       {
313          str = str.substring(3);
314
315          result = expandCommonFunction(str);
316          if (result == null)
317          {
318             result = expandDBSpecificFunction(str);
319          }
320       }
321       else if (str.startsWith("call ")
322                || (str.startsWith("?=")
323                    && str.substring(2).trim().startsWith("call ")))
324       {
325          throw new SQLException("Not implemented yet");
326       }
327       else if (str.startsWith("d "))
328       {
329          result = getDate(str);
330       }
331       else if (str.startsWith("t "))
332       {
333          result = getTime(str);
334       }
335       else if (str.startsWith("ts "))
336       {
337          result = getTimestamp(str);
338       }
339       else if (str.startsWith("oj "))
340       {
341          throw new SQLException("Not implemented yet");
342       }
343       else
344       {
345          throw new SQLException("Unrecognized escape sequence-\n" +
346                                 escapeSequence);
347       }
348       
349       return result;
350    } // expandEscape()
351

352
353    /**
354     * Expand functions that are common to both SQLServer and Sybase
355     *
356     */

357    public String JavaDoc expandCommonFunction(String JavaDoc str)
358    {
359       String JavaDoc result = null;
360
361       
362       if (str.equalsIgnoreCase("user()"))
363       {
364          result = " user_name() ";
365       }
366       else if (str.equalsIgnoreCase("now()"))
367       {
368          result = " getdate() ";
369       }
370       return result;
371    } // expandCommonFunction()
372

373       
374    public String JavaDoc nativeString()
375       throws SQLException
376    {
377       return nativeString(input, '\\');
378    } // nativeString()
379

380    private String JavaDoc nativeString(String JavaDoc sql, char escapeCharacter)
381       throws SQLException
382    {
383       String JavaDoc result = "";
384
385       String JavaDoc escape = "";
386       int i;
387
388      
389       // Simple finite state machine. Bonehead, but it works.
390
final int normal = 0;
391
392       final int inString = 1;
393       final int inStringWithBackquote = 2;
394
395       final int inEscape = 3;
396       final int inEscapeInString = 4;
397       final int inEscapeInStringWithBackquote = 5;
398
399       int state = normal;
400       char ch;
401
402
403       int escapeStartedAt = -1;
404       i = 0;
405       while(i<sql.length())
406       {
407          ch = sql.charAt(i);
408          switch(state)
409          {
410             case normal:
411             {
412                if (ch == '{')
413                {
414                   escapeStartedAt = i;
415                   state = inEscape;
416                   escape = "";
417                }
418                else
419                {
420                   result = result + ch;
421                   
422                   if (ch == '\'') state = inString;
423                }
424                break;
425             }
426             case inString:
427             case inStringWithBackquote:
428             {
429                if ((i+1)<sql.length()
430                    && ch == escapeCharacter
431                    && (sql.charAt(i+1)=='_'
432                        || sql.charAt(i+1)=='%'))
433                {
434                   i++;
435                   ch = sql.charAt(i);
436                   result = result + '\\' + ch;
437                }
438                else
439                {
440                   result = result + ch;
441                   if (state == inStringWithBackquote)
442                   {
443                      state = inString;
444                   }
445                   else
446                   {
447                      if (ch == '\\') state = inStringWithBackquote;
448                      if (ch == '\'') state = normal;
449                   }
450                }
451                break;
452             }
453             case inEscape:
454             {
455                if (ch == '}')
456                {
457                   // At this point there are a couple of things to
458
// consider. First, if the escape is of the form
459
// "{escape 'c'} but it is not at the end of the SQL
460
// we consider that a malformed SQL string. If it
461
// is the "{escape 'c'}" clause and it is at the end
462
// of the string then we have to go through and
463
// reparse this whole thing again, this time with an
464
// escape character. Any other escape is handled in
465
// the expandEscape method()
466

467                   if (escape.startsWith("escape "))
468                   {
469                      char c;
470
471                      // make sure it is the last thing in the sql
472
if (i+1!=sql.length())
473                      {
474                         throw new SQLException("Malformed statement. " +
475                                                "escape clause must be at " +
476                                                "the end of the query");
477                      }
478
479
480                      // parse the sql again, this time without the
481
// ending string but with the escape character
482
// set
483

484                      c = findEscapeCharacter(sql.substring(escapeStartedAt));
485
486                      result = nativeString(sql.substring(0, escapeStartedAt),
487                                            c);
488                      state = normal;
489                   }
490                   else
491                   {
492                      state = normal;
493                      result = result + expandEscape(escape);
494                      escapeStartedAt = -1;
495                   }
496                }
497                else
498                {
499                   escape = escape + ch;
500                   if (ch == '\'')
501                   {
502                      state = inEscapeInString;
503                   }
504                }
505                break;
506             }
507             case inEscapeInString:
508             case inEscapeInStringWithBackquote:
509             {
510                escape = escape + ch;
511                if (state == inEscapeInStringWithBackquote)
512                {
513                   state = inEscapeInString;
514                }
515                else
516                {
517                   if (ch == '\\') state = inEscapeInStringWithBackquote;
518                   if (ch == '\'') state = inEscape;
519                }
520                break;
521             }
522             default:
523             {
524                throw new SQLException("Internal error. Unknown state in FSM");
525             }
526          }
527          i++;
528       }
529       
530       if (state!=normal && state!=inString)
531       {
532          throw new SQLException("Syntax error in SQL escape syntax");
533       }
534       return result;
535    } // nativeString()
536

537    static char findEscapeCharacter(String JavaDoc original_str)
538       throws SQLException
539    {
540       String JavaDoc str = new String JavaDoc(original_str);
541       
542        str = str.trim();
543       if (str.charAt(0)!='{' || str.charAt(str.length()-1)!='}'
544          || str.length()<12)
545       {
546          throw new SQLException("Internal Error");
547       }
548       
549       str = str.substring(1, str.length()-1);
550       str = str.trim();
551
552       if (! str.startsWith("escape"))
553       {
554          throw new SQLException("Internal Error");
555       }
556
557       str = str.substring(6);
558       str = str.trim();
559       if (str.length()!=3 || str.charAt(0)!='\'' || str.charAt(2)!='\'')
560       {
561          throw new SQLException("Malformed escape clause- |" +
562                                 original_str + "|");
563       }
564
565       return str.charAt(1);
566    } // findEscapeCharacter()
567
}
568
Popular Tags