001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import java.io.Serializable;
034: import java.lang.reflect.Method;
035:
036: import org.hsqldb.lib.HashMap;
037: import org.hsqldb.lib.HsqlArrayList;
038: import org.hsqldb.resources.BundleHandler;
039: import org.hsqldb.store.ValuePool;
040: import org.hsqldb.types.Binary;
041:
042: /**@todo fredt - move Trace.doAssert() literals to Trace*/
043:
044: /**
045: * Provides information about HSQLDB SQL-invoked routines and SQL functions. <p>
046: *
047: * In particular, this class provides information about Java Methods in a form
048: * compatible with presentation via the related HSQLDB system tables,
049: * SYSTEM_PROCEDURES and SYSTEM_PROCEDURECOLUMNS, involved in the production of
050: * the DatabaseMetaData getProcedures and getProcedureColumns result sets.
051: *
052: * @author boucherb@users
053: * @version 1.7.2
054: * @since 1.7.2
055: */
056: final class DIProcedureInfo {
057:
058: // java.sql dependencies mostly removed
059: static final String conClsName = "java.sql.Connection";
060: static final int procedureResultUnknown = 0;
061: static final int procedureNoResult = 1;
062: static final int procedureReturnsResult = 2;
063: static final int procedureColumnUnknown = 0;
064: static final int procedureColumnIn = 1;
065: static final int procedureColumnInOut = 2;
066: static final int procedureColumnResult = 3;
067: static final int procedureColumnOut = 4;
068: static final int procedureColumnReturn = 5;
069: static final int procedureNoNulls = 0;
070: static final int procedureNullable = 1;
071: static final int procedureNullableUnknown = 2;
072: private Class clazz;
073: private Class[] colClasses;
074: private int[] colTypes;
075: private int colOffset;
076: private int colCount;
077: private boolean colsResolved;
078: private String fqn;
079: private String specificName;
080: private int hnd_remarks;
081: private Method method;
082: private String sig;
083: private DINameSpace nameSpace;
084: private final HashMap typeMap = new HashMap();
085:
086: public DIProcedureInfo(DINameSpace ns) throws HsqlException {
087: setNameSpace(ns);
088: }
089:
090: private int colOffset() {
091:
092: if (!colsResolved) {
093: resolveCols();
094: }
095:
096: return colOffset;
097: }
098:
099: HsqlArrayList getAliases() {
100: return (HsqlArrayList) nameSpace.getInverseAliasMap().get(
101: getFQN());
102: }
103:
104: Class getColClass(int i) {
105: return colClasses[i + colOffset()];
106: }
107:
108: int getColCount() {
109:
110: if (!colsResolved) {
111: resolveCols();
112: }
113:
114: return colCount;
115: }
116:
117: Integer getColDataType(int i) {
118: return ValuePool.getInt(getColTypeCode(i));
119: }
120:
121: Integer getColLen(int i) {
122:
123: int size;
124: int type;
125:
126: type = getColTypeCode(i);
127:
128: switch (type) {
129:
130: case Types.BINARY:
131: case Types.LONGVARBINARY:
132: case Types.VARBINARY: {
133: size = Integer.MAX_VALUE;
134:
135: break;
136: }
137: case Types.BIGINT:
138: case Types.DOUBLE:
139: case Types.DATE:
140: case Types.FLOAT:
141: case Types.TIME: {
142: size = 8;
143:
144: break;
145: }
146: case Types.TIMESTAMP: {
147: size = 12;
148:
149: break;
150: }
151: case Types.REAL:
152: case Types.INTEGER: {
153: size = 4;
154:
155: break;
156: }
157: case Types.SMALLINT: {
158: size = 2;
159:
160: break;
161: }
162: case Types.TINYINT:
163: case Types.BOOLEAN: {
164: size = 1;
165:
166: break;
167: }
168: default:
169: size = 0;
170: }
171:
172: return (size == 0) ? null : ValuePool.getInt(size);
173: }
174:
175: String getColName(int i) {
176: return CompiledStatement.PCOL_PREFIX + (i + colOffset());
177: }
178:
179: Integer getColNullability(int i) {
180:
181: int cn;
182:
183: cn = getColClass(i).isPrimitive() ? procedureNoNulls
184: : procedureNullable;
185:
186: return ValuePool.getInt(cn);
187: }
188:
189: String getColRemark(int i) {
190:
191: String key;
192: StringBuffer sb;
193:
194: sb = new StringBuffer(getSignature());
195: key = sb.append('@').append(i + colOffset()).toString();
196:
197: return BundleHandler.getString(hnd_remarks, key);
198: }
199:
200: // JDBC sort-contract:
201: // out return value column, then in/in out/out parameter columns
202: // in formal order, then result columns in column order
203: //
204: // Currently, we materialize the java method return value, if
205: // any, as a result column, not as an OUT return value column, so
206: // it should actually appear _after_ the other procedure columns
207: // in the row order returned by the JDBC getProcedureColumns() method
208: int getColSequence(int i) {
209:
210: // colOffset has the side-effect of setting colCount properly
211: return (i + colOffset() == 0) ? colCount : i;
212: }
213:
214: int getColTypeCode(int i) {
215:
216: i += colOffset();
217:
218: return colTypes[i];
219: }
220:
221: Integer getColUsage(int i) {
222:
223: switch (i + colOffset()) {
224:
225: case 0: {
226:
227: // Currently, we materialize the java method return value, if
228: // any, as a result column, not as an OUT return column
229: return ValuePool.getInt(procedureColumnResult);
230: }
231:
232: // todo: registration and reporting on result columns for routines
233: // that generate real" result sets
234: default: {
235:
236: // We could get religious here and maybe report IN OUT
237: // for newRow of before update for each row trigger methods,
238: // but there's not really any added value
239: return ValuePool.getInt(procedureColumnIn);
240: }
241: }
242: }
243:
244: Class getDeclaringClass() {
245: return this .clazz;
246: }
247:
248: String getFQN() {
249:
250: StringBuffer sb;
251:
252: if (fqn == null) {
253: sb = new StringBuffer();
254: fqn = sb.append(clazz.getName()).append('.').append(
255: method.getName()).toString();
256: }
257:
258: return fqn;
259: }
260:
261: String getSpecificName() {
262:
263: if (specificName == null) {
264: specificName = clazz.getName() + "." + getSignature();
265: }
266:
267: return specificName;
268: }
269:
270: Integer getInputParmCount() {
271: return ValuePool.getInt(method.getParameterTypes().length);
272: }
273:
274: Method getMethod() {
275: return this .method;
276: }
277:
278: String getOrigin(String srcType) {
279: return (nameSpace.isBuiltin(clazz) ? "BUILTIN "
280: : "USER DEFINED ")
281: + srcType;
282: }
283:
284: Integer getOutputParmCount() {
285:
286: // no support for IN OUT or OUT columns yet
287: return ValuePool.getInt(0);
288: }
289:
290: String getRemark() {
291: return BundleHandler.getString(hnd_remarks, getSignature());
292: }
293:
294: Integer getResultSetCount() {
295: return (method.getReturnType() == Void.TYPE) ? ValuePool
296: .getInt(0) : ValuePool.getInt(1);
297: }
298:
299: Integer getResultType(String origin) {
300:
301: int type;
302:
303: type = !"ROUTINE".equals(origin) ? procedureResultUnknown
304: : method.getReturnType() == Void.TYPE ? procedureNoResult
305: : procedureReturnsResult;
306:
307: return ValuePool.getInt(type);
308: }
309:
310: String getSignature() {
311:
312: if (sig == null) {
313: sig = DINameSpace.getSignature(method);
314: }
315:
316: return sig;
317: }
318:
319: /**
320: * Retrieves the specific name of the given Method object. <p>
321: *
322: * @param m The Method object for which to retreive the specific name
323: * @return the specific name of the specified Method object.
324: */
325: static String getMethodSpecificName(Method m) {
326:
327: return m == null ? null : m.getDeclaringClass().getName() + '.'
328: + DINameSpace.getSignature(m);
329: }
330:
331: DINameSpace getNameSpace() {
332: return nameSpace;
333: }
334:
335: void setNameSpace(DINameSpace ns) throws HsqlException {
336:
337: nameSpace = ns;
338:
339: Class c;
340: Integer type;
341:
342: // can only speed up test significantly for java.lang.Object,
343: // final classes, primitive types and hierachy parents.
344: // Must still check later if assignable from candidate classes, where
345: // hierarchy parent is not final.
346: //ARRAY
347: try {
348: c = nameSpace.classForName("org.hsqldb.jdbc.jdbcArray");
349:
350: typeMap.put(c, ValuePool.getInt(Types.ARRAY));
351: } catch (Exception e) {
352: }
353:
354: // BIGINT
355: type = ValuePool.getInt(Types.BIGINT);
356:
357: typeMap.put(Long.TYPE, type);
358: typeMap.put(Long.class, type);
359:
360: // BOOLEAN
361: type = ValuePool.getInt(Types.BOOLEAN);
362:
363: typeMap.put(Boolean.TYPE, type);
364: typeMap.put(Boolean.class, type);
365:
366: // BLOB
367: type = ValuePool.getInt(Types.BLOB);
368:
369: try {
370: c = nameSpace.classForName("org.hsqldb.jdbc.jdbcBlob");
371:
372: typeMap.put(c, type);
373: } catch (Exception e) {
374: }
375:
376: // CHAR
377: type = ValuePool.getInt(Types.CHAR);
378:
379: typeMap.put(Character.TYPE, type);
380: typeMap.put(Character.class, type);
381: typeMap.put(Character[].class, type);
382: typeMap.put(char[].class, type);
383:
384: // CLOB
385: type = ValuePool.getInt(Types.CLOB);
386:
387: try {
388: c = nameSpace.classForName("org.hsqldb.jdbc.jdbcClob");
389:
390: typeMap.put(c, type);
391: } catch (Exception e) {
392: }
393:
394: // DATALINK
395: type = ValuePool.getInt(Types.DATALINK);
396:
397: typeMap.put(java.net.URL.class, type);
398:
399: // DATE
400: type = ValuePool.getInt(Types.DATE);
401:
402: typeMap.put(java.util.Date.class, type);
403: typeMap.put(java.sql.Date.class, type);
404:
405: // DECIMAL
406: type = ValuePool.getInt(Types.DECIMAL);
407:
408: try {
409: c = nameSpace.classForName("java.math.BigDecimal");
410:
411: typeMap.put(c, type);
412: } catch (Exception e) {
413: }
414:
415: // DISTINCT
416: try {
417: c = nameSpace.classForName("org.hsqldb.jdbc.jdbcDistinct");
418:
419: typeMap.put(c, ValuePool.getInt(Types.DISTINCT));
420: } catch (Exception e) {
421: }
422:
423: // DOUBLE
424: type = ValuePool.getInt(Types.DOUBLE);
425:
426: typeMap.put(Double.TYPE, type);
427: typeMap.put(Double.class, type);
428:
429: // FLOAT : Not actually a legal IN parameter type yet
430: type = ValuePool.getInt(Types.FLOAT);
431:
432: typeMap.put(Float.TYPE, type);
433: typeMap.put(Float.class, type);
434:
435: // INTEGER
436: type = ValuePool.getInt(Types.INTEGER);
437:
438: typeMap.put(Integer.TYPE, type);
439: typeMap.put(Integer.class, type);
440:
441: // JAVA_OBJECT
442: type = ValuePool.getInt(Types.JAVA_OBJECT);
443:
444: typeMap.put(Object.class, type);
445:
446: // LONGVARBINARY
447: type = ValuePool.getInt(Types.LONGVARBINARY);
448:
449: typeMap.put(byte[].class, type);
450: typeMap.put(Binary.class, type);
451:
452: // LONGVARCHAR
453: type = ValuePool.getInt(Types.LONGVARCHAR);
454:
455: typeMap.put(String.class, type);
456:
457: // NULL
458: type = ValuePool.getInt(Types.NULL);
459:
460: typeMap.put(Void.TYPE, type);
461: typeMap.put(Void.class, type);
462:
463: // REF
464: type = ValuePool.getInt(Types.REF);
465:
466: try {
467: c = nameSpace.classForName("org.hsqldb.jdbc.jdbcRef");
468:
469: typeMap.put(c, type);
470: } catch (Exception e) {
471: }
472:
473: // SMALLINT : Not actually a legal IN parameter type yet
474: type = ValuePool.getInt(Types.SMALLINT);
475:
476: typeMap.put(Short.TYPE, type);
477: typeMap.put(Short.class, type);
478:
479: // STRUCT :
480: type = ValuePool.getInt(Types.STRUCT);
481:
482: try {
483: c = nameSpace.classForName("org.hsqldb.jdbc.jdbcStruct");
484:
485: typeMap.put(c, type);
486: } catch (Exception e) {
487: }
488:
489: // TIME
490: type = ValuePool.getInt(Types.TIME);
491:
492: typeMap.put(java.sql.Time.class, type);
493:
494: // TIMESTAMP
495: type = ValuePool.getInt(Types.TIMESTAMP);
496:
497: typeMap.put(java.sql.Timestamp.class, type);
498:
499: // TINYINT : Not actually a legal IN parameter type yet
500: type = ValuePool.getInt(Types.TINYINT);
501:
502: typeMap.put(Byte.TYPE, type);
503: typeMap.put(Byte.class, type);
504:
505: // XML : Not actually a legal IN parameter type yet
506: type = ValuePool.getInt(Types.XML);
507:
508: try {
509: c = nameSpace.classForName("org.w3c.dom.Document");
510:
511: typeMap.put(c, type);
512:
513: c = nameSpace.classForName("org.w3c.dom.DocumentFragment");
514:
515: typeMap.put(c, type);
516: } catch (Exception e) {
517: }
518: }
519:
520: private void resolveCols() {
521:
522: Class rType;
523: Class[] pTypes;
524: Class clazz;
525: int ptlen;
526: int pclen;
527: boolean isFPCON;
528:
529: rType = method.getReturnType();
530: pTypes = method.getParameterTypes();
531: ptlen = pTypes.length;
532: isFPCON = ptlen > 0 && pTypes[0].getName().equals(conClsName);
533: pclen = 1 + ptlen - (isFPCON ? 1 : 0);
534: colClasses = new Class[pclen];
535: colTypes = new int[pclen];
536: colClasses[0] = rType;
537: colTypes[0] = typeForClass(rType);
538:
539: for (int i = isFPCON ? 1 : 0, idx = 1; i < ptlen; i++, idx++) {
540: clazz = pTypes[i];
541: colClasses[idx] = clazz;
542: colTypes[idx] = typeForClass(clazz);
543: }
544:
545: colOffset = rType == Void.TYPE ? 1 : 0;
546: colCount = pclen - colOffset;
547: }
548:
549: /**
550: * This requires the following properties files:
551: *
552: * org_hsqldb_Library.properties
553: * java_math.properties
554: */
555: void setMethod(Method m) {
556:
557: String remarkKey;
558:
559: method = m;
560: clazz = method.getDeclaringClass();
561: fqn = null;
562: specificName = null;
563: sig = null;
564: colsResolved = false;
565: remarkKey = clazz.getName().replace('.', '_');
566: hnd_remarks = BundleHandler.getBundleHandle(remarkKey, null);
567: }
568:
569: int typeForClass(Class c) {
570:
571: Class to;
572: Integer type = (Integer) typeMap.get(c);
573:
574: if (type != null) {
575: return type.intValue();
576: }
577:
578: // ARRAY (dimension 1)
579: // HSQLDB does not yet support ARRAY for SQL, but
580: // Trigger.fire takes Object[] row, which we report.
581: // Also, it's just friendly to show what "would"
582: // be required if/when we support ARRAY in a broader
583: // sense
584: if (c.isArray() && !c.getComponentType().isArray()) {
585: return Types.ARRAY;
586: }
587:
588: try {
589: to = Class.forName("java.sql.Array");
590:
591: if (to.isAssignableFrom(c)) {
592: return Types.ARRAY;
593: }
594: } catch (Exception e) {
595: }
596:
597: // NUMERIC
598: // All java.lang.Number impls and BigDecimal have
599: // already been covered by lookup in typeMap.
600: // They are all final, so this is OK.
601: if (Number.class.isAssignableFrom(c)) {
602: return Types.NUMERIC;
603: }
604:
605: // TIMESTAMP
606: try {
607: to = Class.forName("java.sql.Timestamp");
608:
609: if (to.isAssignableFrom(c)) {
610: return Types.TIMESTAMP;
611: }
612: } catch (Exception e) {
613: }
614:
615: // TIME
616: try {
617: to = Class.forName("java.sql.Time");
618:
619: if (to.isAssignableFrom(c)) {
620: return Types.TIMESTAMP;
621: }
622: } catch (Exception e) {
623: }
624:
625: // DATE
626: try {
627: to = Class.forName("java.sql.Date");
628:
629: if (to.isAssignableFrom(c)) {
630: return Types.DATE;
631: }
632: } catch (Exception e) {
633: }
634:
635: // BLOB
636: try {
637: to = Class.forName("java.sql.Blob");
638:
639: if (to.isAssignableFrom(c)) {
640: return Types.BLOB;
641: }
642: } catch (Exception e) {
643: }
644:
645: // CLOB
646: try {
647: to = Class.forName("java.sql.Clob");
648:
649: if (to.isAssignableFrom(c)) {
650: return Types.CLOB;
651: }
652: } catch (Exception e) {
653: }
654:
655: // REF
656: try {
657: to = Class.forName("java.sql.Ref");
658:
659: if (to.isAssignableFrom(c)) {
660: return Types.REF;
661: }
662: } catch (Exception e) {
663: }
664:
665: // STRUCT
666: try {
667: to = Class.forName("java.sql.Struct");
668:
669: if (to.isAssignableFrom(c)) {
670: return Types.STRUCT;
671: }
672: } catch (Exception e) {
673: }
674:
675: // LONGVARBINARY : org.hsqldb.Binary is not final
676: if (Binary.class.isAssignableFrom(c)) {
677: return Types.LONGVARBINARY;
678: }
679:
680: // LONGVARCHAR : really OTHER at this point
681: try {
682:
683: // @since JDK1.4
684: to = Class.forName("java.lang.CharSequence");
685:
686: if (to.isAssignableFrom(c)) {
687: return Types.LONGVARCHAR;
688: }
689: } catch (Exception e) {
690: }
691:
692: // we have no standard mapping for the specified class
693: // at this point...is it even storable?
694: if (Serializable.class.isAssignableFrom(c)) {
695:
696: // Yes: it is storable, as an OTHER.
697: return Types.OTHER;
698: }
699:
700: // It may (in future, say using bean contract) or may not be storable
701: // (by HSQLDB)...
702: // but then it may be possible to pass to an in-process routine,
703: // so be lenient and just return the most generic type.
704: return Types.JAVA_OBJECT;
705: }
706: }
|