blob: 3f29e87f8bcc5f0b76118d9b495a9805a76cec1a [file] [log] [blame]
/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
package net.hydromatic.optiq.impl.java;
import net.hydromatic.linq4j.*;
import net.hydromatic.linq4j.expressions.*;
import net.hydromatic.optiq.*;
import net.hydromatic.optiq.Parameter;
import net.hydromatic.optiq.impl.TableFunctionInSchemaImpl;
import net.hydromatic.optiq.impl.TableInSchemaImpl;
import org.eigenbase.reltype.RelDataType;
import java.lang.reflect.*;
import java.util.*;
/**
* Implementation of {@link net.hydromatic.optiq.Schema} that exposes the public
* fields and methods in a Java object.
*/
public class ReflectiveSchema
extends MapSchema {
final Class clazz;
private Object target;
/**
* Creates a ReflectiveSchema.
*
* @param parentSchema Parent schema
* @param name Name
* @param target Object whose fields will be sub-objects of the schema
* @param expression Expression for schema
*/
public ReflectiveSchema(
Schema parentSchema,
String name,
Object target,
Expression expression) {
super(
parentSchema,
parentSchema.getQueryProvider(),
parentSchema.getTypeFactory(),
name,
expression);
this.clazz = target.getClass();
this.target = target;
for (Field field : clazz.getFields()) {
final String fieldName = field.getName();
final Table<Object> table = fieldRelation(field);
if (table == null) {
continue;
}
tableMap.put(
fieldName,
new TableInSchemaImpl(
this, fieldName, TableType.TABLE, table));
}
for (Method method : clazz.getMethods()) {
final String methodName = method.getName();
if (method.getDeclaringClass() == Object.class
|| methodName.equals("toString")) {
continue;
}
final TableFunction tableFunction = methodMember(method);
membersMap.put(methodName,
new TableFunctionInSchemaImpl(this, methodName, tableFunction));
}
}
/**
* Creates a ReflectiveSchema within another schema.
*
* @param parentSchema Parent schema
* @param name Name of new schema
* @param target Object whose fields become the tables of the schema
* @return New ReflectiveSchema
*/
public static ReflectiveSchema create(
MutableSchema parentSchema,
String name,
Object target) {
ReflectiveSchema schema =
new ReflectiveSchema(
parentSchema,
name,
target,
parentSchema.getSubSchemaExpression(
name, ReflectiveSchema.class));
parentSchema.addSchema(name, schema);
return schema;
}
@Override
public String toString() {
return "ReflectiveSchema(target=" + target + ")";
}
/** Returns the wrapped object. (May not appear to be used, but is used in
* generated code via {@link BuiltinMethod#GET_TARGET}.) */
public Object getTarget() {
return target;
}
public <T> TableFunction<T> methodMember(final Method method) {
final ReflectiveSchema schema = this;
final Type elementType = getElementType(method.getReturnType());
final RelDataType relDataType = typeFactory.createType(elementType);
final Class<?>[] parameterTypes = method.getParameterTypes();
return new TableFunction<T>() {
public String toString() {
return "Member {method=" + method + "}";
}
public Type getElementType() {
return elementType;
}
public List<Parameter> getParameters() {
return new AbstractList<Parameter>() {
public Parameter get(final int index) {
return new Parameter() {
public int getOrdinal() {
return index;
}
public String getName() {
return "arg" + index;
}
public RelDataType getType() {
return typeFactory.createJavaType(
parameterTypes[index]);
}
};
}
public int size() {
return parameterTypes.length;
}
};
}
public Table<T> apply(final List<Object> arguments) {
final List<Expression> list = new ArrayList<Expression>();
for (Object argument : arguments) {
list.add(Expressions.constant(argument));
}
try {
final Object o = method.invoke(schema, arguments.toArray());
return new ReflectiveTable<T>(
schema,
elementType,
relDataType,
Expressions.call(
schema.getTargetExpression(),
method,
list)) {
public Enumerator<T> enumerator() {
@SuppressWarnings("unchecked")
final Enumerable<T> enumerable = toEnumerable(o);
return enumerable.enumerator();
}
};
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
};
}
/** Returns an expression for the object wrapped by this schema (not the
* schema itself). */
Expression getTargetExpression() {
return Types.castIfNecessary(
target.getClass(),
Expressions.call(
Types.castIfNecessary(
ReflectiveSchema.class,
getExpression()),
BuiltinMethod.GET_TARGET.method));
}
/** Returns a table based on a particular field of this schema. If the
* field is not of the right type to be a relation, returns null. */
private <T> Table<T> fieldRelation(final Field field) {
final Type elementType = getElementType(field.getType());
if (elementType == null) {
return null;
}
final RelDataType relDataType = typeFactory.createType(elementType);
return new ReflectiveTable<T>(
this,
elementType,
relDataType,
Expressions.field(
ReflectiveSchema.this.getTargetExpression(),
field)) {
public String toString() {
return "Relation {field=" + field.getName() + "}";
}
public Enumerator<T> enumerator() {
try {
Object o = field.get(target);
@SuppressWarnings("unchecked")
Enumerable<T> enumerable1 = toEnumerable(o);
return enumerable1.enumerator();
} catch (IllegalAccessException e) {
throw new RuntimeException(
"Error while accessing field " + field, e);
}
}
};
}
/** Deduces the element type of a collection;
* same logic as {@link #toEnumerable} */
private static Type getElementType(Class clazz) {
if (clazz.isArray()) {
return clazz.getComponentType();
}
if (Iterable.class.isAssignableFrom(clazz)) {
return Object.class;
}
return null; // not a collection/array/iterable
}
private static Enumerable toEnumerable(final Object o) {
if (o.getClass().isArray()) {
if (o instanceof Object[]) {
return Linq4j.asEnumerable((Object[]) o);
} else {
return Linq4j.asEnumerable(Primitive.asList(o));
}
}
if (o instanceof Iterable) {
return Linq4j.asEnumerable((Iterable) o);
}
throw new RuntimeException(
"Cannot convert " + o.getClass() + " into a Enumerable");
}
private static abstract class ReflectiveTable<T>
extends BaseQueryable<T>
implements Table<T> {
private final ReflectiveSchema schema;
private final RelDataType relDataType;
public ReflectiveTable(
ReflectiveSchema schema,
Type elementType,
RelDataType relDataType,
Expression expression) {
super(schema.getQueryProvider(), elementType, expression);
this.schema = schema;
this.relDataType = relDataType;
}
public DataContext getDataContext() {
return schema;
}
public RelDataType getRowType() {
return relDataType;
}
public Statistic getStatistic() {
return Statistics.UNKNOWN;
}
}
/** Factory that creates a schema by instantiating an object and looking at
* its public fields.
*
* <p>The following example instantiates a {@code FoodMart} object as a schema
* that contains tables called {@code EMPS} and {@code DEPTS} based on the
* object's fields.</p>
*
* <pre>
* {@code schemas: [
* {
* name: "foodmart",
* type: "custom",
* factory: "net.hydromatic.optiq.impl.java.ReflectiveSchema.Factory",
* operand: {
* class: "com.acme.FoodMart",
* staticMethod: "instance"
* }
* }
* ]
*
* class FoodMart {
* public static final FoodMart instance() {
* return new FoodMart();
* }
*
* Employee[] EMPS;
* Department[] DEPTS;
* }
* }</pre>
* */
public static class Factory implements SchemaFactory {
public Schema create(MutableSchema schema, String name,
Map<String, Object> operand) {
Class clazz;
Object target;
final Object className = operand.get("class");
if (className != null) {
try {
clazz = Class.forName((String) className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Error loading class " + className, e);
}
} else {
throw new RuntimeException("Operand 'class' is required");
}
final Object methodName = operand.get("staticMethod");
if (methodName != null) {
try {
//noinspection unchecked
Method method = clazz.getMethod((String) methodName);
target = method.invoke(null);
} catch (Exception e) {
throw new RuntimeException("Error invoking method " + methodName, e);
}
} else {
try {
target = clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("Error instantiating class " + className,
e);
}
}
return ReflectiveSchema.create(schema, name, target);
}
}
}
// End ReflectiveSchema.java