File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\engine\FunctionAlias.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.engine;
8
9 import java.lang.reflect.Array;
10 import java.lang.reflect.InvocationTargetException;
11 import java.lang.reflect.Method;
12 import java.lang.reflect.Modifier;
13 import java.sql.Connection;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import org.h2.Driver;
17 import org.h2.command.Parser;
18 import org.h2.constant.ErrorCode;
19 import org.h2.constant.SysProperties;
20 import org.h2.expression.Expression;
21 import org.h2.message.DbException;
22 import org.h2.message.Trace;
23 import org.h2.schema.Schema;
24 import org.h2.schema.SchemaObjectBase;
25 import org.h2.table.Table;
26 import org.h2.util.New;
27 import org.h2.util.SourceCompiler;
28 import org.h2.util.StatementBuilder;
29 import org.h2.util.StringUtils;
30 import org.h2.util.Utils;
31 import org.h2.value.DataType;
32 import org.h2.value.Value;
33 import org.h2.value.ValueArray;
34 import org.h2.value.ValueNull;
35
36 /**
37 * Represents a user-defined function, or alias.
38 *
39 * @author Thomas Mueller
40 * @author Gary Tong
41 */
42 public class FunctionAlias extends SchemaObjectBase {
43
44 private String className;
45 private String methodName;
46 private String source;
47 private JavaMethod[] javaMethods;
48 private boolean deterministic;
49
50 private FunctionAlias(Schema schema, int id, String name) {
51 initSchemaObjectBase(schema, id, name, Trace.FUNCTION);
52 }
53
54 /**
55 * Create a new alias based on a method name.
56 *
57 * @param schema the schema
58 * @param id the id
59 * @param name the name
60 * @param javaClassMethod the class and method name
61 * @param force create the object even if the class or method does not exist
62 * @return the database object
63 */
64 public static FunctionAlias newInstance(Schema schema, int id, String name, String javaClassMethod, boolean force) {
65 FunctionAlias alias = new FunctionAlias(schema, id, name);
66 int paren = javaClassMethod.indexOf('(');
67 int lastDot = javaClassMethod.lastIndexOf('.', paren < 0 ? javaClassMethod.length() : paren);
68 if (lastDot < 0) {
69 throw DbException.get(ErrorCode.SYNTAX_ERROR_1, javaClassMethod);
70 }
71 alias.className = javaClassMethod.substring(0, lastDot);
72 alias.methodName = javaClassMethod.substring(lastDot + 1);
73 alias.init(force);
74 return alias;
75 }
76
77 /**
78 * Create a new alias based on source code.
79 *
80 * @param schema the schema
81 * @param id the id
82 * @param name the name
83 * @param source the source code
84 * @param force create the object even if the class or method does not exist
85 * @return the database object
86 */
87 public static FunctionAlias newInstanceFromSource(Schema schema, int id, String name, String source, boolean force) {
88 FunctionAlias alias = new FunctionAlias(schema, id, name);
89 alias.source = source;
90 alias.init(force);
91 return alias;
92 }
93
94 private void init(boolean force) {
95 try {
96 // at least try to compile the class, otherwise the data type is not
97 // initialized if it could be
98 load();
99 } catch (DbException e) {
100 if (!force) {
101 throw e;
102 }
103 }
104 }
105
106 private synchronized void load() {
107 if (javaMethods != null) {
108 return;
109 }
110 if (source != null) {
111 loadFromSource();
112 } else {
113 loadClass();
114 }
115 }
116
117 private void loadFromSource() {
118 SourceCompiler compiler = database.getCompiler();
119 synchronized (compiler) {
120 String fullClassName = Constants.USER_PACKAGE + "." + getName();
121 compiler.setSource(fullClassName, source);
122 try {
123 Method m = compiler.getMethod(fullClassName);
124 JavaMethod method = new JavaMethod(m, 0);
125 javaMethods = new JavaMethod[] {
126 method
127 };
128 } catch (DbException e) {
129 throw e;
130 } catch (Exception e) {
131 throw DbException.get(ErrorCode.SYNTAX_ERROR_1, e, source);
132 }
133 }
134 }
135
136 private void loadClass() {
137 Class<?> javaClass = Utils.loadUserClass(className);
138 Method[] methods = javaClass.getMethods();
139 ArrayList<JavaMethod> list = New.arrayList();
140 for (int i = 0, len = methods.length; i < len; i++) {
141 Method m = methods[i];
142 if (!Modifier.isStatic(m.getModifiers())) {
143 continue;
144 }
145 if (m.getName().equals(methodName) || getMethodSignature(m).equals(methodName)) {
146 JavaMethod javaMethod = new JavaMethod(m, i);
147 for (JavaMethod old : list) {
148 if (old.getParameterCount() == javaMethod.getParameterCount()) {
149 throw DbException.get(
150 ErrorCode.METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2,
151 old.toString(), javaMethod.toString()
152 );
153 }
154 }
155 list.add(javaMethod);
156 }
157 }
158 if (list.size() == 0) {
159 throw DbException.get(ErrorCode.PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1, methodName + " (" + className + ")");
160 }
161 javaMethods = new JavaMethod[list.size()];
162 list.toArray(javaMethods);
163 // Sort elements. Methods with a variable number of arguments must be at
164 // the end. Reason: there could be one method without parameters and one
165 // with a variable number. The one without parameters needs to be used
166 // if no parameters are given.
167 Arrays.sort(javaMethods);
168 }
169
170 private static String getMethodSignature(Method m) {
171 StatementBuilder buff = new StatementBuilder(m.getName());
172 buff.append('(');
173 for (Class<?> p : m.getParameterTypes()) {
174 // do not use a space here, because spaces are removed
175 // in CreateFunctionAlias.setJavaClassMethod()
176 buff.appendExceptFirst(",");
177 if (p.isArray()) {
178 buff.append(p.getComponentType().getName()).append("[]");
179 } else {
180 buff.append(p.getName());
181 }
182 }
183 return buff.append(')').toString();
184 }
185
186 public String getCreateSQLForCopy(Table table, String quotedName) {
187 throw DbException.throwInternalError();
188 }
189
190 public String getDropSQL() {
191 return "DROP ALIAS IF EXISTS " + getSQL();
192 }
193
194 public String getSQL() {
195 // TODO can remove this method once FUNCTIONS_IN_SCHEMA is enabled
196 if (database.getSettings().functionsInSchema || !getSchema().getName().equals(Constants.SCHEMA_MAIN)) {
197 return super.getSQL();
198 }
199 return Parser.quoteIdentifier(getName());
200 }
201
202 public String getCreateSQL() {
203 StringBuilder buff = new StringBuilder("CREATE FORCE ALIAS ");
204 buff.append(getSQL());
205 if (deterministic) {
206 buff.append(" DETERMINISTIC");
207 }
208 if (source != null) {
209 buff.append(" AS ").append(StringUtils.quoteStringSQL(source));
210 } else {
211 buff.append(" FOR ").append(Parser.quoteIdentifier(className + "." + methodName));
212 }
213 return buff.toString();
214 }
215
216 public int getType() {
217 return DbObject.FUNCTION_ALIAS;
218 }
219
220 public synchronized void removeChildrenAndResources(Session session) {
221 database.removeMeta(session, getId());
222 className = null;
223 methodName = null;
224 javaMethods = null;
225 invalidate();
226 }
227
228 public void checkRename() {
229 throw DbException.getUnsupportedException("RENAME");
230 }
231
232 /**
233 * Find the Java method that matches the arguments.
234 *
235 * @param args the argument list
236 * @return the Java method
237 * @throws DbException if no matching method could be found
238 */
239 public JavaMethod findJavaMethod(Expression[] args) {
240 load();
241 int parameterCount = args.length;
242 for (JavaMethod m : javaMethods) {
243 int count = m.getParameterCount();
244 if (count == parameterCount || (m.isVarArgs() && count <= parameterCount + 1)) {
245 return m;
246 }
247 }
248 throw DbException.get(ErrorCode.METHOD_NOT_FOUND_1,
249 methodName + " (" + className + ", parameter count: " + parameterCount + ")");
250 }
251
252 public String getJavaClassName() {
253 return this.className;
254 }
255
256 public String getJavaMethodName() {
257 return this.methodName;
258 }
259
260 /**
261 * Get the Java methods mapped by this function.
262 *
263 * @return the Java methods.
264 */
265 public JavaMethod[] getJavaMethods() {
266 load();
267 return javaMethods;
268 }
269
270 /**
271 * There may be multiple Java methods that match a function name.
272 * Each method must have a different number of parameters however.
273 * This helper class represents one such method.
274 */
275 public static class JavaMethod implements Comparable<JavaMethod> {
276 private final int id;
277 private final Method method;
278 private final int dataType;
279 private boolean hasConnectionParam;
280 private boolean varArgs;
281 private Class<?> varArgClass;
282 private int paramCount;
283
284 JavaMethod(Method method, int id) {
285 this.method = method;
286 this.id = id;
287 Class<?>[] paramClasses = method.getParameterTypes();
288 paramCount = paramClasses.length;
289 if (paramCount > 0) {
290 Class<?> paramClass = paramClasses[0];
291 if (Connection.class.isAssignableFrom(paramClass)) {
292 hasConnectionParam = true;
293 paramCount--;
294 }
295 }
296 if (paramCount > 0) {
297 Class<?> lastArg = paramClasses[paramClasses.length - 1];
298 if (lastArg.isArray() && FunctionAlias.isVarArgs(method)) {
299 varArgs = true;
300 varArgClass = lastArg.getComponentType();
301 }
302 }
303 Class<?> returnClass = method.getReturnType();
304 dataType = DataType.getTypeFromClass(returnClass);
305 }
306
307 public String toString() {
308 return method.toString();
309 }
310
311 /**
312 * Check if this function requires a database connection.
313 *
314 * @return if the function requires a connection
315 */
316 public boolean hasConnectionParam() {
317 return this.hasConnectionParam;
318 }
319
320 /**
321 * Call the user-defined function and return the value.
322 *
323 * @param session the session
324 * @param args the argument list
325 * @param columnList true if the function should only return the column list
326 * @return the value
327 */
328 public Value getValue(Session session, Expression[] args, boolean columnList) {
329 Class<?>[] paramClasses = method.getParameterTypes();
330 Object[] params = new Object[paramClasses.length];
331 int p = 0;
332 if (hasConnectionParam && params.length > 0) {
333 params[p++] = session.createConnection(columnList);
334 }
335
336 // allocate array for varArgs parameters
337 Object varArg = null;
338 if (varArgs) {
339 int len = args.length - params.length + 1 + (hasConnectionParam ? 1 : 0);
340 varArg = Array.newInstance(varArgClass, len);
341 params[params.length - 1] = varArg;
342 }
343
344 for (int a = 0, len = args.length; a < len; a++, p++) {
345 boolean currentIsVarArg = varArgs && p >= paramClasses.length - 1;
346 Class<?> paramClass;
347 if (currentIsVarArg) {
348 paramClass = varArgClass;
349 } else {
350 paramClass = paramClasses[p];
351 }
352 int type = DataType.getTypeFromClass(paramClass);
353 Value v = args[a].getValue(session);
354 Object o;
355 if (Value.class.isAssignableFrom(paramClass)) {
356 o = v;
357 } else if (v.getType() == Value.ARRAY && paramClass.isArray() && paramClass.getComponentType() != Object.class) {
358 Value[] array = ((ValueArray) v).getList();
359 Object[] objArray = (Object[]) Array.newInstance(paramClass.getComponentType(), array.length);
360 int componentType = DataType.getTypeFromClass(paramClass.getComponentType());
361 for (int i = 0; i < objArray.length; i++) {
362 objArray[i] = array[i].convertTo(componentType).getObject();
363 }
364 o = objArray;
365 } else {
366 v = v.convertTo(type);
367 o = v.getObject();
368 }
369 if (o == null) {
370 if (paramClass.isPrimitive()) {
371 if (columnList) {
372 // if the column list is requested, the parameters may
373 // be null
374 // need to set to default value,
375 // otherwise the function can't be called at all
376 o = DataType.getDefaultForPrimitiveType(paramClass);
377 } else {
378 // NULL for a java primitive: return NULL
379 return ValueNull.INSTANCE;
380 }
381 }
382 } else {
383 if (!paramClass.isAssignableFrom(o.getClass()) && !paramClass.isPrimitive()) {
384 o = DataType.convertTo(session.createConnection(false), v, paramClass);
385 }
386 }
387 if (currentIsVarArg) {
388 Array.set(varArg, p - params.length + 1, o);
389 } else {
390 params[p] = o;
391 }
392 }
393 boolean old = session.getAutoCommit();
394 Value identity = session.getLastScopeIdentity();
395 boolean defaultConnection = session.getDatabase().getSettings().defaultConnection;
396 try {
397 session.setAutoCommit(false);
398 Object returnValue;
399 try {
400 if (defaultConnection) {
401 Driver.setDefaultConnection(session.createConnection(columnList));
402 }
403 returnValue = method.invoke(null, params);
404 if (returnValue == null) {
405 return ValueNull.INSTANCE;
406 }
407 } catch (InvocationTargetException e) {
408 StatementBuilder buff = new StatementBuilder(method.getName());
409 buff.append('(');
410 for (Object o : params) {
411 buff.appendExceptFirst(", ");
412 buff.append(o == null ? "null" : o.toString());
413 }
414 buff.append(')');
415 throw DbException.convertInvocation(e, buff.toString());
416 } catch (Exception e) {
417 throw DbException.convert(e);
418 }
419 if (Value.class.isAssignableFrom(method.getReturnType())) {
420 return (Value) returnValue;
421 }
422 Value ret = DataType.convertToValue(session, returnValue, dataType);
423 return ret.convertTo(dataType);
424 } finally {
425 session.setLastScopeIdentity(identity);
426 session.setAutoCommit(old);
427 if (defaultConnection) {
428 Driver.setDefaultConnection(null);
429 }
430 }
431 }
432
433 public Class<?>[] getColumnClasses() {
434 return method.getParameterTypes();
435 }
436
437 public int getDataType() {
438 return dataType;
439 }
440
441 public int getParameterCount() {
442 return paramCount;
443 }
444
445 public boolean isVarArgs() {
446 return varArgs;
447 }
448
449 public int compareTo(JavaMethod m) {
450 if (varArgs != m.varArgs) {
451 return varArgs ? 1 : -1;
452 }
453 if (paramCount != m.paramCount) {
454 return paramCount - m.paramCount;
455 }
456 if (hasConnectionParam != m.hasConnectionParam) {
457 return hasConnectionParam ? 1 : -1;
458 }
459 return id - m.id;
460 }
461
462 }
463
464 public void setDeterministic(boolean deterministic) {
465 this.deterministic = deterministic;
466 }
467
468 public boolean isDeterministic() {
469 return deterministic;
470 }
471
472 public String getSource() {
473 return source;
474 }
475
476 /**
477 * Checks if the given method takes a variable number of arguments. For Java
478 * 1.4 and older, false is returned. Example:
479 * <pre>
480 * public static double mean(double... values)
481 * </pre>
482 *
483 * @param m the method to test
484 * @return true if the method takes a variable number of arguments.
485 */
486 static boolean isVarArgs(Method m) {
487 if ("1.5".compareTo(SysProperties.JAVA_SPECIFICATION_VERSION) > 0) {
488 return false;
489 }
490 try {
491 Method isVarArgs = m.getClass().getMethod("isVarArgs");
492 Boolean result = (Boolean) isVarArgs.invoke(m);
493 return result.booleanValue();
494 } catch (Exception e) {
495 return false;
496 }
497 }
498
499 }
500