net.hydromatic.optiq.impl.java.ReflectiveSchema.java Source code

Java tutorial

Introduction

Here is the source code for net.hydromatic.optiq.impl.java.ReflectiveSchema.java

Source

/*
// 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.Table;
import net.hydromatic.optiq.impl.*;

import org.eigenbase.reltype.RelDataType;
import org.eigenbase.reltype.RelDataTypeFactory;

import com.google.common.collect.*;

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 AbstractSchema {
    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
     */
    public ReflectiveSchema(SchemaPlus parentSchema, String name, Object target) {
        super(parentSchema, name);
        this.clazz = target.getClass();
        this.target = target;
    }

    @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#REFLECTIVE_SCHEMA_GET_TARGET}.) */
    public Object getTarget() {
        return target;
    }

    @Override
    protected Map<String, Table> getTableMap() {
        final ImmutableMap.Builder<String, Table> builder = ImmutableMap.builder();
        for (Field field : clazz.getFields()) {
            final String fieldName = field.getName();
            final Table table = fieldRelation(field);
            if (table == null) {
                continue;
            }
            builder.put(fieldName, table);
        }
        return builder.build();
    }

    @Override
    protected Multimap<String, TableFunction> getTableFunctionMultimap() {
        final ImmutableMultimap.Builder<String, TableFunction> builder = ImmutableMultimap.builder();
        for (Method method : clazz.getMethods()) {
            final String methodName = method.getName();
            if (method.getDeclaringClass() == Object.class || methodName.equals("toString")) {
                continue;
            }
            final TableFunction tableFunction = methodMember(method);
            builder.put(methodName, tableFunction);
        }
        return builder.build();
    }

    private TableFunction methodMember(final Method method) {
        final Type elementType = getElementType(method.getReturnType());
        final Class<?>[] parameterTypes = method.getParameterTypes();
        return new MethodTableFunction(this, method, elementType, parameterTypes);
    }

    /** Returns an expression for the object wrapped by this schema (not the
     * schema itself). */
    Expression getTargetExpression() {
        return Types.castIfNecessary(target.getClass(),
                Expressions.call(Schemas.unwrap(getExpression(), ReflectiveSchema.class),
                        BuiltinMethod.REFLECTIVE_SCHEMA_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 fieldRelation(final Field field) {
        final Type elementType = getElementType(field.getType());
        if (elementType == null) {
            return null;
        }
        Object o;
        try {
            o = field.get(target);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Error while accessing field " + field, e);
        }
        @SuppressWarnings("unchecked")
        final Enumerable<T> enumerable = toEnumerable(o);
        return new FieldTable<T>(field, elementType, enumerable);
    }

    /** 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");
    }

    /** Table that is implemented by reading from a Java object. */
    private static class ReflectiveTable extends AbstractQueryableTable implements Table {
        private final Type elementType;
        private final Enumerable enumerable;

        public ReflectiveTable(Type elementType, Enumerable enumerable) {
            super(elementType);
            this.elementType = elementType;
            this.enumerable = enumerable;
        }

        public RelDataType getRowType(RelDataTypeFactory typeFactory) {
            return ((JavaTypeFactory) typeFactory).createType(elementType);
        }

        public Statistic getStatistic() {
            return Statistics.UNKNOWN;
        }

        public <T> Queryable<T> asQueryable(QueryProvider queryProvider, SchemaPlus schema, String tableName) {
            return new AbstractTableQueryable<T>(queryProvider, schema, this, tableName) {
                @SuppressWarnings("unchecked")
                public Enumerator<T> enumerator() {
                    return (Enumerator<T>) enumerable.enumerator();
                }
            };
        }
    }

    /** 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(SchemaPlus parentSchema, 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 new ReflectiveSchema(parentSchema, name, target);
        }
    }

    /** Table function based on a Java method. */
    private static class MethodTableFunction implements TableFunction {
        private final ReflectiveSchema schema;
        private final Method method;
        private final Type elementType;
        private final Class<?>[] parameterTypes;

        public MethodTableFunction(ReflectiveSchema schema, Method method, Type elementType,
                Class<?>[] parameterTypes) {
            this.schema = schema;
            this.method = method;
            this.elementType = elementType;
            this.parameterTypes = parameterTypes;
        }

        public String toString() {
            return "Member {method=" + method + "}";
        }

        public RelDataType getRowType(RelDataTypeFactory typeFactory) {
            return ((JavaTypeFactory) typeFactory).createType(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(RelDataTypeFactory typeFactory) {
                            return typeFactory.createJavaType(parameterTypes[index]);
                        }
                    };
                }

                public int size() {
                    return parameterTypes.length;
                }
            };
        }

        public Table 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());
                @SuppressWarnings("unchecked")
                final Enumerable enumerable = toEnumerable(o);
                return new ReflectiveTable(elementType, enumerable) {
                    @Override
                    public Expression getExpression(SchemaPlus schema, String tableName, Class clazz) {
                        return Expressions.call(((ReflectiveSchema) schema).getTargetExpression(), method, list);
                    }
                };
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /** Table based on a Java field. */
    private static class FieldTable<T> extends ReflectiveTable {
        private final Field field;

        public FieldTable(Field field, Type elementType, Enumerable<T> enumerable) {
            super(elementType, enumerable);
            this.field = field;
        }

        public String toString() {
            return "Relation {field=" + field.getName() + "}";
        }

        @Override
        public Expression getExpression(SchemaPlus schema, String tableName, Class clazz) {
            return Expressions.field(schema.unwrap(ReflectiveSchema.class).getTargetExpression(), field);
        }
    }
}

// End ReflectiveSchema.java