1 | /* |
2 | |
3 | Derby - Class com.ihost.cs.IdUtil |
4 | |
5 | Copyright 1998, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.iapi.util; |
22 | |
23 | import org.apache.derby.iapi.reference.Attribute; |
24 | import org.apache.derby.iapi.reference.SQLState; |
25 | import org.apache.derby.iapi.reference.Property; |
26 | import org.apache.derby.iapi.error.StandardException; |
27 | import java.io.IOException; |
28 | import java.io.StringReader; |
29 | import java.util.Vector; |
30 | import java.util.HashSet; |
31 | import java.util.Properties; |
32 | |
33 | /** |
34 | Utility class for parsing and producing string representations of |
35 | ids. This class supports both delimited and un-delimited ids. |
36 | |
37 | <P>The syntax for an id follows. |
38 | <PRE> |
39 | id := delim-id | unDelim-id |
40 | |
41 | delim-id := "[""|[any char but quote]]+" |
42 | undelim-id := (a-z|A-Z|anyunicodeletter)[a-z|A-Z|_|0-9|anyunicodeletter|anyunicodedigit]* |
43 | |
44 | In the syntax braces show grouping. '*' means repeat 0 or more times. |
45 | '|' means or. '+' means repeat 1 or more times. |
46 | </PRE> |
47 | |
48 | <P>In addition this class provides support for qualified names. A qualified name |
49 | is a dot (.) separated list of ids. |
50 | |
51 | <P>Limitations: |
52 | <OL> |
53 | <LI>Unicode escape sequences in ids are not supported. |
54 | <LI>Escape sequences (\n...) are not supported. |
55 | </OL> |
56 | */ |
57 | public abstract class IdUtil |
58 | { |
59 | /** |
60 | Delimit the identifier provided. |
61 | @return the delimited identifier. |
62 | */ |
63 | public static String delimitId(String id) |
64 | { |
65 | StringBuffer quotedBuffer = new StringBuffer(); |
66 | quotedBuffer.append('\"'); |
67 | char[] charArray = id.toCharArray(); |
68 | |
69 | for (int ix = 0; ix < charArray.length; ix++){ |
70 | char currentChar = charArray[ix]; |
71 | quotedBuffer.append(currentChar); |
72 | if (currentChar == '\"') |
73 | quotedBuffer.append('\"'); |
74 | } |
75 | quotedBuffer.append('\"'); |
76 | |
77 | return quotedBuffer.toString(); |
78 | } |
79 | |
80 | /** |
81 | Produce a delimited two part qualified name from two |
82 | un-delimited identifiers. |
83 | @return the result. |
84 | */ |
85 | public static String mkQualifiedName(String id1, |
86 | String id2) |
87 | { |
88 | if( null == id1) |
89 | return delimitId(id2); |
90 | return |
91 | delimitId(id1) + |
92 | "." + |
93 | delimitId(id2); |
94 | } |
95 | |
96 | /** |
97 | Make a string form of a qualified name from the array of ids provided. |
98 | */ |
99 | public static String mkQualifiedName(String[] ids) |
100 | { |
101 | StringBuffer sb = new StringBuffer(); |
102 | for (int ix=0; ix < ids.length; ix++) |
103 | { |
104 | if (ix!=0) sb.append("."); |
105 | sb.append(delimitId(ids[ix])); |
106 | } |
107 | return sb.toString(); |
108 | } |
109 | |
110 | /** |
111 | Scan a qualified name from the String provided. Raise an excepion |
112 | if the string does not contain a qualified name. |
113 | |
114 | @param s The string to be parsed |
115 | @param normalizeToUpper If true then undelimited names are converted to upper case (the ANSI standard). If false then undelimited names are converted to lower case (used when the source database is Informix Foundation). |
116 | @return An array of strings made by breaking the input string at its dots, '.'. |
117 | @exception StandardException Oops |
118 | */ |
119 | public static String[] parseQualifiedName(String s, boolean normalizeToUpper) |
120 | throws StandardException |
121 | { |
122 | StringReader r = new StringReader(s); |
123 | String[] qName = parseQualifiedName(r, normalizeToUpper); |
124 | verifyEmpty(r); |
125 | return qName; |
126 | } |
127 | |
128 | /** |
129 | Scan a qualified name from a StringReader. Return an array |
130 | of Strings with 1 entry per name scanned. Raise an exception |
131 | if the StringReader does not contain a valid qualified name. |
132 | |
133 | @param r A StringReader for the string to be parsed |
134 | @param normalizeToUpper If true then undelimited names are converted to upper case (the ANSI standard). If false then undelimited names are converted to lower case (used when the source database is Informix Foundation). |
135 | @return An array of strings made by breaking the input string at its dots, '.'. |
136 | @exception StandardException Oops |
137 | */ |
138 | public static String[] parseQualifiedName(StringReader r, boolean normalizeToUpper) |
139 | throws StandardException |
140 | { |
141 | Vector v = new Vector(); |
142 | while (true) |
143 | { |
144 | String thisId = parseId(r,true, normalizeToUpper); |
145 | v.addElement(thisId); |
146 | int dot; |
147 | |
148 | try { |
149 | r.mark(0); |
150 | dot = r.read(); |
151 | if (dot != '.') |
152 | { |
153 | if (dot!=-1) r.reset(); |
154 | break; |
155 | } |
156 | } |
157 | |
158 | catch (IOException ioe){ |
159 | throw StandardException.newException(SQLState.ID_PARSE_ERROR,ioe); |
160 | } |
161 | } |
162 | String[] result = new String[v.size()]; |
163 | v.copyInto(result); |
164 | return result; |
165 | } |
166 | |
167 | /** |
168 | Convert the String provided to an ID. Throw an exception |
169 | iff the string does not contain only a valid external form |
170 | for an id. This is a convenience routine that simply |
171 | uses getId(StringReader) to do the work. |
172 | |
173 | <P> See the header for getId below for restrictions. |
174 | |
175 | @exception StandardException Oops |
176 | */ |
177 | public static String parseId(String s) |
178 | throws StandardException |
179 | { |
180 | StringReader r = new StringReader(s); |
181 | String id = parseId(r,true, true); |
182 | verifyEmpty(r); |
183 | return id; |
184 | } |
185 | |
186 | /** |
187 | Read an id from the StringReader provided. |
188 | |
189 | |
190 | @param normalize true means return ids in nomral form, false means |
191 | return them as they were entered. |
192 | |
193 | <P> |
194 | Raise an exception if the first thing in the StringReader |
195 | is not a valid id. |
196 | |
197 | @exception StandardException Ooops. |
198 | */ |
199 | public static String parseId(StringReader r, boolean normalize, boolean normalizeToUpper) |
200 | throws StandardException |
201 | { |
202 | try { |
203 | r.mark(0); |
204 | int c = r.read(); |
205 | if (c == -1) //id can't be 0-length |
206 | throw StandardException.newException(SQLState.ID_PARSE_ERROR); |
207 | r.reset(); |
208 | if (c == '"') |
209 | return parseQId(r,normalize); |
210 | else |
211 | return parseUnQId(r,normalize, normalizeToUpper); |
212 | } |
213 | |
214 | catch (IOException ioe){ |
215 | throw StandardException.newException(SQLState.ID_PARSE_ERROR,ioe); |
216 | } |
217 | } |
218 | |
219 | private static String parseUnQId(StringReader r, boolean normalize, boolean normalizeToUpper) |
220 | throws IOException,StandardException |
221 | { |
222 | StringBuffer b = new StringBuffer(); |
223 | int c; |
224 | boolean first; |
225 | // |
226 | for(first = true; ; first=false) |
227 | { |
228 | r.mark(0); |
229 | if (idChar(first,c=r.read())) |
230 | b.append((char)c); |
231 | else |
232 | break; |
233 | } |
234 | if (c != -1) r.reset(); |
235 | |
236 | if (normalize) |
237 | return normalizeToUpper ? StringUtil.SQLToUpperCase(b.toString()) : StringUtil.SQLToLowerCase(b.toString()); |
238 | else |
239 | return b.toString(); |
240 | } |
241 | |
242 | |
243 | private static boolean idChar(boolean first,int c) |
244 | { |
245 | if (((c>='a' && c<='z') || (c>='A' && c<='Z')) || |
246 | (!first &&(c>='0' && c<='9')) || (!first &&c =='_') ) |
247 | return true; |
248 | else if (Character.isLetter((char) c)) |
249 | return true; |
250 | else if (!first && Character.isDigit((char) c)) |
251 | return true; |
252 | return false; |
253 | } |
254 | private static String parseQId(StringReader r,boolean normalize) |
255 | throws IOException,StandardException |
256 | { |
257 | StringBuffer b = new StringBuffer(); |
258 | int c = r.read(); |
259 | if (c != '"') throw StandardException.newException(SQLState.ID_PARSE_ERROR); |
260 | while (true) |
261 | { |
262 | c=r.read(); |
263 | if (c == '"') |
264 | { |
265 | r.mark(0); |
266 | int c2 = r.read(); |
267 | if (c2 != '"') |
268 | { |
269 | if (c2!=-1)r.reset(); |
270 | break; |
271 | } |
272 | } |
273 | else if (c == -1) |
274 | throw StandardException.newException(SQLState.ID_PARSE_ERROR); |
275 | |
276 | b.append((char)c); |
277 | } |
278 | |
279 | if (b.length() == 0) //id can't be 0-length |
280 | throw StandardException.newException(SQLState.ID_PARSE_ERROR); |
281 | |
282 | if (normalize) |
283 | return b.toString(); |
284 | else |
285 | return delimitId(b.toString()); //Put the quotes back. |
286 | } |
287 | |
288 | private static void verifyEmpty(java.io.Reader r) |
289 | throws StandardException |
290 | { |
291 | try { |
292 | if (r.read() != -1) |
293 | throw StandardException.newException(SQLState.ID_PARSE_ERROR); |
294 | } |
295 | |
296 | catch (IOException ioe){ |
297 | throw StandardException.newException(SQLState.ID_PARSE_ERROR,ioe); |
298 | } |
299 | } |
300 | /**Index of the schema name in a jar name on a db classpath*/ |
301 | public static final int DBCP_SCHEMA_NAME = 0; |
302 | /**Index of the sql jar name in a jar name on a db classpath*/ |
303 | public static final int DBCP_SQL_JAR_NAME = 1; |
304 | |
305 | /** |
306 | Scan a database classpath from the string provided. This returns |
307 | an array with one qualified name per entry on the classpath. The |
308 | constants above describe the content of the returned names. This |
309 | raises an an exception if the string does not contain a valid database |
310 | class path. |
311 | <PRE> |
312 | classpath := item[:item]* |
313 | item := id.id |
314 | |
315 | In the syntax braces ([]) show grouping. '*' means repeat 0 or more times. |
316 | The syntax for id is defined in IdUtil. |
317 | </PRE> |
318 | <BR> |
319 | Classpath returned is a two part name. <BR> |
320 | If the class path is empty then this returns an array |
321 | of zero length. |
322 | |
323 | @exception StandardException Oops |
324 | */ |
325 | public static String[][] parseDbClassPath(String input, boolean normalizeToUpper) |
326 | throws StandardException |
327 | { |
328 | //As a special case we accept a zero length dbclasspath. |
329 | if (input.length() == 0) |
330 | return new String[0][]; |
331 | |
332 | Vector v = new Vector(); |
333 | java.io.StringReader r = new java.io.StringReader(input); |
334 | // |
335 | while (true) |
336 | { |
337 | try { |
338 | String[] thisQName = IdUtil.parseQualifiedName(r, normalizeToUpper); |
339 | if (thisQName.length != 2) |
340 | throw StandardException.newException(SQLState.DB_CLASS_PATH_PARSE_ERROR,input); |
341 | |
342 | v.addElement(thisQName); |
343 | int delim = r.read(); |
344 | if (delim != ':') |
345 | { |
346 | if (delim!=-1) |
347 | throw StandardException.newException(SQLState.DB_CLASS_PATH_PARSE_ERROR,input); |
348 | break; |
349 | } |
350 | } |
351 | |
352 | catch (StandardException se){ |
353 | if (se.getMessageId().equals(SQLState.ID_PARSE_ERROR)) |
354 | throw StandardException.newException(SQLState.DB_CLASS_PATH_PARSE_ERROR, |
355 | se,input); |
356 | else |
357 | throw se; |
358 | } |
359 | |
360 | catch (IOException ioe){ |
361 | throw StandardException.newException(SQLState.DB_CLASS_PATH_PARSE_ERROR,ioe,input); |
362 | } |
363 | } |
364 | String[][] result = new String[v.size()][]; |
365 | v.copyInto(result); |
366 | return result; |
367 | } |
368 | |
369 | |
370 | /* |
371 | ** Methods that operate on lists of identifiers. |
372 | */ |
373 | |
374 | |
375 | /** |
376 | Scan a list of ids from the string provided. This returns |
377 | an array with id per entry. This raises an an exception if |
378 | the string does not contain a valid list of names. |
379 | |
380 | @exception StandardException Oops |
381 | */ |
382 | public static String[] parseIdList(String p) |
383 | throws StandardException |
384 | { |
385 | if (p==null) return null; |
386 | StringReader r = new StringReader(p); |
387 | String[] result = parseIdList(r, true); |
388 | verifyListEmpty(r); |
389 | return result; |
390 | } |
391 | |
392 | |
393 | /** |
394 | Parse an idList. |
395 | |
396 | @param normalize true means return ids in nomral form, false means |
397 | return them as they were entered. |
398 | |
399 | @exception StandardException Oops |
400 | */ |
401 | private static String[] parseIdList(StringReader r, boolean normalize) |
402 | throws StandardException |
403 | { |
404 | Vector v = new Vector(); |
405 | while (true) |
406 | { |
407 | int delim; |
408 | try { |
409 | String thisId = IdUtil.parseId(r,normalize, true); |
410 | v.addElement(thisId); |
411 | r.mark(0); |
412 | delim = r.read(); |
413 | if (delim != ',') |
414 | { |
415 | if (delim!=-1) r.reset(); |
416 | break; |
417 | } |
418 | } |
419 | |
420 | catch (StandardException se){ |
421 | if (se.getMessageId().equals(SQLState.ID_LIST_PARSE_ERROR)) |
422 | throw StandardException.newException(SQLState.ID_LIST_PARSE_ERROR,se); |
423 | else |
424 | throw se; |
425 | } |
426 | |
427 | catch (IOException ioe){ |
428 | throw StandardException.newException(SQLState.ID_LIST_PARSE_ERROR,ioe); |
429 | } |
430 | } |
431 | if (v.size() == 0) return null; |
432 | String[] result = new String[v.size()]; |
433 | v.copyInto(result); |
434 | return result; |
435 | } |
436 | |
437 | /** |
438 | Return an IdList with all the ids that in l1 and l2 |
439 | or null if not ids are on both lists. |
440 | |
441 | @param l1 An array of ids in normal form |
442 | @param l2 An array of ids in nomral form |
443 | */ |
444 | public static String intersect(String[] l1, String[] l2) |
445 | { |
446 | if (l1 == null || l2 == null) return null; |
447 | HashSet h = new HashSet(); |
448 | for(int ix=0;ix<l2.length;ix++) h.add(l2[ix]); |
449 | Vector v = new Vector(); |
450 | for(int ix=0;ix<l1.length;ix++) if (h.contains(l1[ix])) v.addElement(l1[ix]); |
451 | return vectorToIdList(v,true); |
452 | } |
453 | |
454 | /** |
455 | Return an idList in external form with one id for every |
456 | element of v. If v has no elements, return null. |
457 | |
458 | @param normal True means the ids in v are in normal form |
459 | and false means they are in external form. |
460 | */ |
461 | private static String vectorToIdList(Vector v,boolean normal) |
462 | { |
463 | if (v.size() == 0) return null; |
464 | String[] a = new String[v.size()]; |
465 | v.copyInto(a); |
466 | if (normal) |
467 | return mkIdList(a); |
468 | else |
469 | return mkIdListAsEntered(a); |
470 | } |
471 | |
472 | /** |
473 | * Map userName to authorizationId |
474 | * |
475 | * @exception StandardException on error |
476 | */ |
477 | public static String getUserAuthorizationId(String userName) throws StandardException |
478 | { |
479 | try { |
480 | return parseId(userName); |
481 | } |
482 | catch (StandardException se) { |
483 | throw StandardException.newException(SQLState.AUTH_INVALID_USER_NAME, userName); |
484 | } |
485 | } |
486 | |
487 | /** |
488 | * Get user name from URL properties. Handles the case of "" user. |
489 | * |
490 | * @exception StandardException on error |
491 | */ |
492 | public static String getUserNameFromURLProps(Properties params) |
493 | { |
494 | String userName = params.getProperty(Attribute.USERNAME_ATTR, |
495 | Property.DEFAULT_USER_NAME); |
496 | if (userName.equals("")) |
497 | userName = Property.DEFAULT_USER_NAME; |
498 | |
499 | return userName; |
500 | } |
501 | |
502 | /** |
503 | Return an IdList with all the ids that are repeated |
504 | in l. |
505 | |
506 | @param l a list of ids in normal form. |
507 | */ |
508 | public static String dups(String[] l) |
509 | { |
510 | if (l == null) return null; |
511 | HashSet h = new HashSet(); |
512 | Vector v = new Vector(); |
513 | for(int ix=0;ix<l.length;ix++) |
514 | { |
515 | if (!h.contains(l[ix])) |
516 | h.add(l[ix]); |
517 | else |
518 | v.addElement(l[ix]); |
519 | } |
520 | return vectorToIdList(v,true); |
521 | } |
522 | |
523 | /** |
524 | Return an IdList with all the duplicate ids removed |
525 | @param l a list of ids in external form. |
526 | @exception StandardException Oops. |
527 | */ |
528 | public static String pruneDups(String l) throws StandardException |
529 | { |
530 | if (l == null) return null; |
531 | String[] normal_a = parseIdList(l); |
532 | StringReader r = new StringReader(l); |
533 | String[] external_a = parseIdList(r,false); |
534 | HashSet h = new HashSet(); |
535 | Vector v = new Vector(); |
536 | for(int ix=0;ix<normal_a.length;ix++) |
537 | { |
538 | if (!h.contains(normal_a[ix])) |
539 | { |
540 | h.add(normal_a[ix]); |
541 | v.addElement(external_a[ix]); |
542 | } |
543 | } |
544 | return vectorToIdList(v,false); |
545 | } |
546 | |
547 | /** |
548 | Produce a string form of an idList from an array of |
549 | normalized ids. |
550 | */ |
551 | public static String mkIdList(String[] ids) |
552 | { |
553 | StringBuffer sb = new StringBuffer(); |
554 | for (int ix=0;ix<ids.length; ix++) |
555 | { |
556 | if (ix != 0) sb.append(","); |
557 | sb.append(IdUtil.delimitId(ids[ix])); |
558 | } |
559 | return sb.toString(); |
560 | } |
561 | |
562 | /** |
563 | Produce an id list from an array of ids in external form |
564 | */ |
565 | private static String mkIdListAsEntered(String[] externalIds ) |
566 | { |
567 | StringBuffer sb = new StringBuffer(); |
568 | for (int ix=0;ix<externalIds.length; ix++) |
569 | { |
570 | if (ix != 0) sb.append(","); |
571 | sb.append(externalIds[ix]); |
572 | } |
573 | return sb.toString(); |
574 | } |
575 | |
576 | private static void verifyListEmpty(StringReader r) |
577 | throws StandardException |
578 | { |
579 | try { |
580 | if (r.read() != -1) |
581 | throw StandardException.newException(SQLState.ID_LIST_PARSE_ERROR); |
582 | } |
583 | |
584 | catch (IOException ioe){ |
585 | throw StandardException.newException(SQLState.ID_LIST_PARSE_ERROR,ioe); |
586 | } |
587 | |
588 | |
589 | } |
590 | |
591 | /** |
592 | Return true if the id provided is on the list provided. |
593 | @param id an id in normal form |
594 | @param list a list of ids in external form. |
595 | @exception StandardException oops. |
596 | */ |
597 | public static boolean idOnList(String id, String list) |
598 | throws StandardException |
599 | { |
600 | if (list==null) return false; |
601 | String[] list_a = parseIdList(list); |
602 | for (int ix=0; ix < list_a.length; ix++) |
603 | if (id.equals(list_a[ix])) return true; |
604 | return false; |
605 | } |
606 | |
607 | /** |
608 | Delete an id from a list of ids. |
609 | @param id an id in normal form (quotes removed, upshifted) |
610 | @param list a comma separated list of ids in external |
611 | form (possibly delmited or not upshifted). |
612 | @return the list with the id deleted or null if the |
613 | resulting list has no ids. If 'id' is not on 'list' |
614 | this returns list unchanged. |
615 | |
616 | @exception StandardException oops. |
617 | */ |
618 | public static String deleteId(String id, String list) |
619 | throws StandardException |
620 | { |
621 | if (list==null) return null; |
622 | Vector v = new Vector(); |
623 | StringReader r = new StringReader(list); |
624 | String[] enteredList_a = parseIdList(r,false); |
625 | // |
626 | //Loop through enteredList element by element |
627 | //removing elements that match id. Before we |
628 | //compare we parse each id in list to convert |
629 | //to normal form. |
630 | for (int ix=0; ix < enteredList_a.length; ix++) |
631 | if (!id.equals(IdUtil.parseId(enteredList_a[ix]))) |
632 | v.addElement(enteredList_a[ix]); |
633 | if (v.size() == 0) |
634 | return null; |
635 | else |
636 | return vectorToIdList(v,false); |
637 | } |
638 | |
639 | |
640 | /** |
641 | Append an id in external form. |
642 | @return the list with the id appended. |
643 | @exception StandardException oops |
644 | */ |
645 | public static String appendId(String id, String list) |
646 | throws StandardException |
647 | { |
648 | if (list==null) |
649 | return id; |
650 | else |
651 | return list+","+id; |
652 | } |
653 | } |