001: /*
002:
003: Derby - Class com.ihost.cs.IdUtil
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.util;
023:
024: import org.apache.derby.iapi.reference.Attribute;
025: import org.apache.derby.iapi.reference.SQLState;
026: import org.apache.derby.iapi.reference.Property;
027: import org.apache.derby.iapi.error.StandardException;
028: import java.io.IOException;
029: import java.io.StringReader;
030: import java.util.Vector;
031: import java.util.HashSet;
032: import java.util.Properties;
033:
034: /**
035: Utility class for parsing and producing string representations of
036: ids. This class supports both delimited and un-delimited ids.
037:
038: <P>The syntax for an id follows.
039: <PRE>
040: id := delim-id | unDelim-id
041:
042: delim-id := "[""|[any char but quote]]+"
043: undelim-id := (a-z|A-Z|anyunicodeletter)[a-z|A-Z|_|0-9|anyunicodeletter|anyunicodedigit]*
044:
045: In the syntax braces show grouping. '*' means repeat 0 or more times.
046: '|' means or. '+' means repeat 1 or more times.
047: </PRE>
048:
049: <P>In addition this class provides support for qualified names. A qualified name
050: is a dot (.) separated list of ids.
051:
052: <P>Limitations:
053: <OL>
054: <LI>Unicode escape sequences in ids are not supported.
055: <LI>Escape sequences (\n...) are not supported.
056: </OL>
057: */
058: public abstract class IdUtil {
059: /**
060: Delimit the identifier provided.
061: @return the delimited identifier.
062: */
063: public static String delimitId(String id) {
064: StringBuffer quotedBuffer = new StringBuffer();
065: quotedBuffer.append('\"');
066: char[] charArray = id.toCharArray();
067:
068: for (int ix = 0; ix < charArray.length; ix++) {
069: char currentChar = charArray[ix];
070: quotedBuffer.append(currentChar);
071: if (currentChar == '\"')
072: quotedBuffer.append('\"');
073: }
074: quotedBuffer.append('\"');
075:
076: return quotedBuffer.toString();
077: }
078:
079: /**
080: Produce a delimited two part qualified name from two
081: un-delimited identifiers.
082: @return the result.
083: */
084: public static String mkQualifiedName(String id1, String id2) {
085: if (null == id1)
086: return delimitId(id2);
087: return delimitId(id1) + "." + delimitId(id2);
088: }
089:
090: /**
091: Make a string form of a qualified name from the array of ids provided.
092: */
093: public static String mkQualifiedName(String[] ids) {
094: StringBuffer sb = new StringBuffer();
095: for (int ix = 0; ix < ids.length; ix++) {
096: if (ix != 0)
097: sb.append(".");
098: sb.append(delimitId(ids[ix]));
099: }
100: return sb.toString();
101: }
102:
103: /**
104: Scan a qualified name from the String provided. Raise an excepion
105: if the string does not contain a qualified name.
106:
107: @param s The string to be parsed
108: @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).
109: @return An array of strings made by breaking the input string at its dots, '.'.
110: @exception StandardException Oops
111: */
112: public static String[] parseQualifiedName(String s,
113: boolean normalizeToUpper) throws StandardException {
114: StringReader r = new StringReader(s);
115: String[] qName = parseQualifiedName(r, normalizeToUpper);
116: verifyEmpty(r);
117: return qName;
118: }
119:
120: /**
121: Scan a qualified name from a StringReader. Return an array
122: of Strings with 1 entry per name scanned. Raise an exception
123: if the StringReader does not contain a valid qualified name.
124:
125: @param r A StringReader for the string to be parsed
126: @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).
127: @return An array of strings made by breaking the input string at its dots, '.'.
128: @exception StandardException Oops
129: */
130: public static String[] parseQualifiedName(StringReader r,
131: boolean normalizeToUpper) throws StandardException {
132: Vector v = new Vector();
133: while (true) {
134: String this Id = parseId(r, true, normalizeToUpper);
135: v.addElement(this Id);
136: int dot;
137:
138: try {
139: r.mark(0);
140: dot = r.read();
141: if (dot != '.') {
142: if (dot != -1)
143: r.reset();
144: break;
145: }
146: }
147:
148: catch (IOException ioe) {
149: throw StandardException.newException(
150: SQLState.ID_PARSE_ERROR, ioe);
151: }
152: }
153: String[] result = new String[v.size()];
154: v.copyInto(result);
155: return result;
156: }
157:
158: /**
159: Convert the String provided to an ID. Throw an exception
160: iff the string does not contain only a valid external form
161: for an id. This is a convenience routine that simply
162: uses getId(StringReader) to do the work.
163:
164: <P> See the header for getId below for restrictions.
165:
166: @exception StandardException Oops
167: */
168: public static String parseId(String s) throws StandardException {
169: StringReader r = new StringReader(s);
170: String id = parseId(r, true, true);
171: verifyEmpty(r);
172: return id;
173: }
174:
175: /**
176: Read an id from the StringReader provided.
177:
178:
179: @param normalize true means return ids in nomral form, false means
180: return them as they were entered.
181:
182: <P>
183: Raise an exception if the first thing in the StringReader
184: is not a valid id.
185:
186: @exception StandardException Ooops.
187: */
188: public static String parseId(StringReader r, boolean normalize,
189: boolean normalizeToUpper) throws StandardException {
190: try {
191: r.mark(0);
192: int c = r.read();
193: if (c == -1) //id can't be 0-length
194: throw StandardException
195: .newException(SQLState.ID_PARSE_ERROR);
196: r.reset();
197: if (c == '"')
198: return parseQId(r, normalize);
199: else
200: return parseUnQId(r, normalize, normalizeToUpper);
201: }
202:
203: catch (IOException ioe) {
204: throw StandardException.newException(
205: SQLState.ID_PARSE_ERROR, ioe);
206: }
207: }
208:
209: private static String parseUnQId(StringReader r, boolean normalize,
210: boolean normalizeToUpper) throws IOException,
211: StandardException {
212: StringBuffer b = new StringBuffer();
213: int c;
214: boolean first;
215: //
216: for (first = true;; first = false) {
217: r.mark(0);
218: if (idChar(first, c = r.read()))
219: b.append((char) c);
220: else
221: break;
222: }
223: if (c != -1)
224: r.reset();
225:
226: if (normalize)
227: return normalizeToUpper ? StringUtil.SQLToUpperCase(b
228: .toString()) : StringUtil.SQLToLowerCase(b
229: .toString());
230: else
231: return b.toString();
232: }
233:
234: private static boolean idChar(boolean first, int c) {
235: if (((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
236: || (!first && (c >= '0' && c <= '9'))
237: || (!first && c == '_'))
238: return true;
239: else if (Character.isLetter((char) c))
240: return true;
241: else if (!first && Character.isDigit((char) c))
242: return true;
243: return false;
244: }
245:
246: private static String parseQId(StringReader r, boolean normalize)
247: throws IOException, StandardException {
248: StringBuffer b = new StringBuffer();
249: int c = r.read();
250: if (c != '"')
251: throw StandardException
252: .newException(SQLState.ID_PARSE_ERROR);
253: while (true) {
254: c = r.read();
255: if (c == '"') {
256: r.mark(0);
257: int c2 = r.read();
258: if (c2 != '"') {
259: if (c2 != -1)
260: r.reset();
261: break;
262: }
263: } else if (c == -1)
264: throw StandardException
265: .newException(SQLState.ID_PARSE_ERROR);
266:
267: b.append((char) c);
268: }
269:
270: if (b.length() == 0) //id can't be 0-length
271: throw StandardException
272: .newException(SQLState.ID_PARSE_ERROR);
273:
274: if (normalize)
275: return b.toString();
276: else
277: return delimitId(b.toString()); //Put the quotes back.
278: }
279:
280: private static void verifyEmpty(java.io.Reader r)
281: throws StandardException {
282: try {
283: if (r.read() != -1)
284: throw StandardException
285: .newException(SQLState.ID_PARSE_ERROR);
286: }
287:
288: catch (IOException ioe) {
289: throw StandardException.newException(
290: SQLState.ID_PARSE_ERROR, ioe);
291: }
292: }
293:
294: /**Index of the schema name in a jar name on a db classpath*/
295: public static final int DBCP_SCHEMA_NAME = 0;
296: /**Index of the sql jar name in a jar name on a db classpath*/
297: public static final int DBCP_SQL_JAR_NAME = 1;
298:
299: /**
300: Scan a database classpath from the string provided. This returns
301: an array with one qualified name per entry on the classpath. The
302: constants above describe the content of the returned names. This
303: raises an an exception if the string does not contain a valid database
304: class path.
305: <PRE>
306: classpath := item[:item]*
307: item := id.id
308:
309: In the syntax braces ([]) show grouping. '*' means repeat 0 or more times.
310: The syntax for id is defined in IdUtil.
311: </PRE>
312: <BR>
313: Classpath returned is a two part name. <BR>
314: If the class path is empty then this returns an array
315: of zero length.
316:
317: @exception StandardException Oops
318: */
319: public static String[][] parseDbClassPath(String input,
320: boolean normalizeToUpper) throws StandardException {
321: //As a special case we accept a zero length dbclasspath.
322: if (input.length() == 0)
323: return new String[0][];
324:
325: Vector v = new Vector();
326: java.io.StringReader r = new java.io.StringReader(input);
327: //
328: while (true) {
329: try {
330: String[] this QName = IdUtil.parseQualifiedName(r,
331: normalizeToUpper);
332: if (this QName.length != 2)
333: throw StandardException.newException(
334: SQLState.DB_CLASS_PATH_PARSE_ERROR, input);
335:
336: v.addElement(this QName);
337: int delim = r.read();
338: if (delim != ':') {
339: if (delim != -1)
340: throw StandardException.newException(
341: SQLState.DB_CLASS_PATH_PARSE_ERROR,
342: input);
343: break;
344: }
345: }
346:
347: catch (StandardException se) {
348: if (se.getMessageId().equals(SQLState.ID_PARSE_ERROR))
349: throw StandardException.newException(
350: SQLState.DB_CLASS_PATH_PARSE_ERROR, se,
351: input);
352: else
353: throw se;
354: }
355:
356: catch (IOException ioe) {
357: throw StandardException.newException(
358: SQLState.DB_CLASS_PATH_PARSE_ERROR, ioe, input);
359: }
360: }
361: String[][] result = new String[v.size()][];
362: v.copyInto(result);
363: return result;
364: }
365:
366: /*
367: ** Methods that operate on lists of identifiers.
368: */
369:
370: /**
371: Scan a list of ids from the string provided. This returns
372: an array with id per entry. This raises an an exception if
373: the string does not contain a valid list of names.
374:
375: @exception StandardException Oops
376: */
377: public static String[] parseIdList(String p)
378: throws StandardException {
379: if (p == null)
380: return null;
381: StringReader r = new StringReader(p);
382: String[] result = parseIdList(r, true);
383: verifyListEmpty(r);
384: return result;
385: }
386:
387: /**
388: Parse an idList.
389:
390: @param normalize true means return ids in nomral form, false means
391: return them as they were entered.
392:
393: @exception StandardException Oops
394: */
395: private static String[] parseIdList(StringReader r,
396: boolean normalize) throws StandardException {
397: Vector v = new Vector();
398: while (true) {
399: int delim;
400: try {
401: String this Id = IdUtil.parseId(r, normalize, true);
402: v.addElement(this Id);
403: r.mark(0);
404: delim = r.read();
405: if (delim != ',') {
406: if (delim != -1)
407: r.reset();
408: break;
409: }
410: }
411:
412: catch (StandardException se) {
413: if (se.getMessageId().equals(
414: SQLState.ID_LIST_PARSE_ERROR))
415: throw StandardException.newException(
416: SQLState.ID_LIST_PARSE_ERROR, se);
417: else
418: throw se;
419: }
420:
421: catch (IOException ioe) {
422: throw StandardException.newException(
423: SQLState.ID_LIST_PARSE_ERROR, ioe);
424: }
425: }
426: if (v.size() == 0)
427: return null;
428: String[] result = new String[v.size()];
429: v.copyInto(result);
430: return result;
431: }
432:
433: /**
434: Return an IdList with all the ids that in l1 and l2
435: or null if not ids are on both lists.
436:
437: @param l1 An array of ids in normal form
438: @param l2 An array of ids in nomral form
439: */
440: public static String intersect(String[] l1, String[] l2) {
441: if (l1 == null || l2 == null)
442: return null;
443: HashSet h = new HashSet();
444: for (int ix = 0; ix < l2.length; ix++)
445: h.add(l2[ix]);
446: Vector v = new Vector();
447: for (int ix = 0; ix < l1.length; ix++)
448: if (h.contains(l1[ix]))
449: v.addElement(l1[ix]);
450: return vectorToIdList(v, true);
451: }
452:
453: /**
454: Return an idList in external form with one id for every
455: element of v. If v has no elements, return null.
456:
457: @param normal True means the ids in v are in normal form
458: and false means they are in external form.
459: */
460: private static String vectorToIdList(Vector v, boolean normal) {
461: if (v.size() == 0)
462: return null;
463: String[] a = new String[v.size()];
464: v.copyInto(a);
465: if (normal)
466: return mkIdList(a);
467: else
468: return mkIdListAsEntered(a);
469: }
470:
471: /**
472: * Map userName to authorizationId
473: *
474: * @exception StandardException on error
475: */
476: public static String getUserAuthorizationId(String userName)
477: throws StandardException {
478: try {
479: return parseId(userName);
480: } catch (StandardException se) {
481: throw StandardException.newException(
482: SQLState.AUTH_INVALID_USER_NAME, userName);
483: }
484: }
485:
486: /**
487: * Get user name from URL properties. Handles the case of "" user.
488: *
489: * @exception StandardException on error
490: */
491: public static String getUserNameFromURLProps(Properties params) {
492: String userName = params.getProperty(Attribute.USERNAME_ATTR,
493: Property.DEFAULT_USER_NAME);
494: if (userName.equals(""))
495: userName = Property.DEFAULT_USER_NAME;
496:
497: return userName;
498: }
499:
500: /**
501: Return an IdList with all the ids that are repeated
502: in l.
503:
504: @param l a list of ids in normal form.
505: */
506: public static String dups(String[] l) {
507: if (l == null)
508: return null;
509: HashSet h = new HashSet();
510: Vector v = new Vector();
511: for (int ix = 0; ix < l.length; ix++) {
512: if (!h.contains(l[ix]))
513: h.add(l[ix]);
514: else
515: v.addElement(l[ix]);
516: }
517: return vectorToIdList(v, true);
518: }
519:
520: /**
521: Return an IdList with all the duplicate ids removed
522: @param l a list of ids in external form.
523: @exception StandardException Oops.
524: */
525: public static String pruneDups(String l) throws StandardException {
526: if (l == null)
527: return null;
528: String[] normal_a = parseIdList(l);
529: StringReader r = new StringReader(l);
530: String[] external_a = parseIdList(r, false);
531: HashSet h = new HashSet();
532: Vector v = new Vector();
533: for (int ix = 0; ix < normal_a.length; ix++) {
534: if (!h.contains(normal_a[ix])) {
535: h.add(normal_a[ix]);
536: v.addElement(external_a[ix]);
537: }
538: }
539: return vectorToIdList(v, false);
540: }
541:
542: /**
543: Produce a string form of an idList from an array of
544: normalized ids.
545: */
546: public static String mkIdList(String[] ids) {
547: StringBuffer sb = new StringBuffer();
548: for (int ix = 0; ix < ids.length; ix++) {
549: if (ix != 0)
550: sb.append(",");
551: sb.append(IdUtil.delimitId(ids[ix]));
552: }
553: return sb.toString();
554: }
555:
556: /**
557: Produce an id list from an array of ids in external form
558: */
559: private static String mkIdListAsEntered(String[] externalIds) {
560: StringBuffer sb = new StringBuffer();
561: for (int ix = 0; ix < externalIds.length; ix++) {
562: if (ix != 0)
563: sb.append(",");
564: sb.append(externalIds[ix]);
565: }
566: return sb.toString();
567: }
568:
569: private static void verifyListEmpty(StringReader r)
570: throws StandardException {
571: try {
572: if (r.read() != -1)
573: throw StandardException
574: .newException(SQLState.ID_LIST_PARSE_ERROR);
575: }
576:
577: catch (IOException ioe) {
578: throw StandardException.newException(
579: SQLState.ID_LIST_PARSE_ERROR, ioe);
580: }
581:
582: }
583:
584: /**
585: Return true if the id provided is on the list provided.
586: @param id an id in normal form
587: @param list a list of ids in external form.
588: @exception StandardException oops.
589: */
590: public static boolean idOnList(String id, String list)
591: throws StandardException {
592: if (list == null)
593: return false;
594: String[] list_a = parseIdList(list);
595: for (int ix = 0; ix < list_a.length; ix++)
596: if (id.equals(list_a[ix]))
597: return true;
598: return false;
599: }
600:
601: /**
602: Delete an id from a list of ids.
603: @param id an id in normal form (quotes removed, upshifted)
604: @param list a comma separated list of ids in external
605: form (possibly delmited or not upshifted).
606: @return the list with the id deleted or null if the
607: resulting list has no ids. If 'id' is not on 'list'
608: this returns list unchanged.
609:
610: @exception StandardException oops.
611: */
612: public static String deleteId(String id, String list)
613: throws StandardException {
614: if (list == null)
615: return null;
616: Vector v = new Vector();
617: StringReader r = new StringReader(list);
618: String[] enteredList_a = parseIdList(r, false);
619: //
620: //Loop through enteredList element by element
621: //removing elements that match id. Before we
622: //compare we parse each id in list to convert
623: //to normal form.
624: for (int ix = 0; ix < enteredList_a.length; ix++)
625: if (!id.equals(IdUtil.parseId(enteredList_a[ix])))
626: v.addElement(enteredList_a[ix]);
627: if (v.size() == 0)
628: return null;
629: else
630: return vectorToIdList(v, false);
631: }
632:
633: /**
634: Append an id in external form.
635: @return the list with the id appended.
636: @exception StandardException oops
637: */
638: public static String appendId(String id, String list)
639: throws StandardException {
640: if (list == null)
641: return id;
642: else
643: return list + "," + id;
644: }
645: }
|