Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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 org.apache.calcite.adapter.java; import org.apache.calcite.DataContext; import org.apache.calcite.linq4j.Enumerable; import org.apache.calcite.linq4j.Enumerator; import org.apache.calcite.linq4j.Linq4j; import org.apache.calcite.linq4j.QueryProvider; import org.apache.calcite.linq4j.Queryable; import org.apache.calcite.linq4j.function.Function1; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; import org.apache.calcite.linq4j.tree.Primitive; import org.apache.calcite.linq4j.tree.Types; import org.apache.calcite.rel.RelReferentialConstraint; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.schema.Function; import org.apache.calcite.schema.ScannableTable; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaFactory; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.Schemas; import org.apache.calcite.schema.Statistic; import org.apache.calcite.schema.Statistics; import org.apache.calcite.schema.Table; import org.apache.calcite.schema.TableMacro; import org.apache.calcite.schema.TranslatableTable; import org.apache.calcite.schema.impl.AbstractSchema; import org.apache.calcite.schema.impl.AbstractTableQueryable; import org.apache.calcite.schema.impl.ReflectiveFunctionBase; import org.apache.calcite.util.BuiltInMethod; import org.apache.calcite.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Collections; import java.util.List; import java.util.Map; /** * Implementation of {@link org.apache.calcite.schema.Schema} that exposes the * public fields and methods in a Java object. */ public class ReflectiveSchema extends AbstractSchema { private final Class clazz; private Object target; /** * Creates a ReflectiveSchema. * * @param target Object whose fields will be sub-objects of the schema */ public ReflectiveSchema(Object target) { super(); this.clazz = target.getClass(); this.target = target; } @Override public String toString() { return "ReflectiveSchema(target=" + target + ")"; } /** Returns the wrapped object. * * <p>May not appear to be used, but is used in generated code via * {@link org.apache.calcite.util.BuiltInMethod#REFLECTIVE_SCHEMA_GET_TARGET}. */ public Object getTarget() { return target; } @SuppressWarnings({ "rawtypes", "unchecked" }) @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); } Map<String, Table> tableMap = builder.build(); // Unique-Key - Foreign-Key for (Field field : clazz.getFields()) { if (RelReferentialConstraint.class.isAssignableFrom(field.getType())) { RelReferentialConstraint rc; try { rc = (RelReferentialConstraint) field.get(target); } catch (IllegalAccessException e) { throw new RuntimeException("Error while accessing field " + field, e); } FieldTable table = (FieldTable) tableMap.get(Util.last(rc.getSourceQualifiedName())); assert table != null; table.statistic = Statistics.of(ImmutableList.copyOf(Iterables .concat(table.getStatistic().getReferentialConstraints(), Collections.singleton(rc)))); } } return tableMap; } @Override protected Multimap<String, Function> getFunctionMultimap() { final ImmutableMultimap.Builder<String, Function> builder = ImmutableMultimap.builder(); for (Method method : clazz.getMethods()) { final String methodName = method.getName(); if (method.getDeclaringClass() == Object.class || methodName.equals("toString")) { continue; } if (TranslatableTable.class.isAssignableFrom(method.getReturnType())) { final TableMacro tableMacro = new MethodTableMacro(this, method); builder.put(methodName, tableMacro); } } return builder.build(); } /** Returns an expression for the object wrapped by this schema (not the * schema itself). */ Expression getTargetExpression(SchemaPlus parentSchema, String name) { return Types.castIfNecessary(target.getClass(), Expressions.call(Schemas.unwrap(getExpression(parentSchema, name), 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<>(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, ScannableTable { private final Type elementType; private final Enumerable enumerable; 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 Enumerable<Object[]> scan(DataContext root) { if (elementType == Object[].class) { //noinspection unchecked return enumerable; } else { //noinspection unchecked return enumerable.select(new FieldSelector((Class) elementType)); } } 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: "org.apache.calcite.adapter.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 { final Constructor<?> constructor = clazz.getConstructor(); target = constructor.newInstance(); } catch (Exception e) { throw new RuntimeException("Error instantiating class " + className, e); } } return new ReflectiveSchema(target); } } /** Table macro based on a Java method. */ private static class MethodTableMacro extends ReflectiveFunctionBase implements TableMacro { private final ReflectiveSchema schema; MethodTableMacro(ReflectiveSchema schema, Method method) { super(method); this.schema = schema; assert TranslatableTable.class.isAssignableFrom( method.getReturnType()) : "Method should return TranslatableTable so the macro can be " + "expanded"; } public String toString() { return "Member {method=" + method + "}"; } public TranslatableTable apply(final List<Object> arguments) { try { final Object o = method.invoke(schema.getTarget(), arguments.toArray()); return (TranslatableTable) o; } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } /** Table based on a Java field. */ private static class FieldTable<T> extends ReflectiveTable { private final Field field; private Statistic statistic; FieldTable(Field field, Type elementType, Enumerable<T> enumerable) { this(field, elementType, enumerable, Statistics.UNKNOWN); } FieldTable(Field field, Type elementType, Enumerable<T> enumerable, Statistic statistic) { super(elementType, enumerable); this.field = field; this.statistic = statistic; } public String toString() { return "Relation {field=" + field.getName() + "}"; } @Override public Statistic getStatistic() { return statistic; } @Override public Expression getExpression(SchemaPlus schema, String tableName, Class clazz) { return Expressions.field(schema.unwrap(ReflectiveSchema.class) .getTargetExpression(schema.getParentSchema(), schema.getName()), field); } } /** Function that returns an array of a given object's field values. */ private static class FieldSelector implements Function1<Object, Object[]> { private final Field[] fields; FieldSelector(Class elementType) { this.fields = elementType.getFields(); } public Object[] apply(Object o) { try { final Object[] objects = new Object[fields.length]; for (int i = 0; i < fields.length; i++) { objects[i] = fields[i].get(o); } return objects; } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } // End ReflectiveSchema.java