com.hazelcast.org.apache.calcite.adapter.java.ReflectiveSchema Maven / Gradle / Ivy
/*
* 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 com.hazelcast.org.apache.calcite.adapter.java;
import com.hazelcast.org.apache.calcite.DataContext;
import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumUtils;
import com.hazelcast.org.apache.calcite.linq4j.Enumerable;
import com.hazelcast.org.apache.calcite.linq4j.Enumerator;
import com.hazelcast.org.apache.calcite.linq4j.Linq4j;
import com.hazelcast.org.apache.calcite.linq4j.QueryProvider;
import com.hazelcast.org.apache.calcite.linq4j.Queryable;
import com.hazelcast.org.apache.calcite.linq4j.function.Function1;
import com.hazelcast.org.apache.calcite.linq4j.tree.Expression;
import com.hazelcast.org.apache.calcite.linq4j.tree.Expressions;
import com.hazelcast.org.apache.calcite.linq4j.tree.Primitive;
import com.hazelcast.org.apache.calcite.rel.RelReferentialConstraint;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFactory;
import com.hazelcast.org.apache.calcite.schema.Function;
import com.hazelcast.org.apache.calcite.schema.ScannableTable;
import com.hazelcast.org.apache.calcite.schema.Schema;
import com.hazelcast.org.apache.calcite.schema.SchemaFactory;
import com.hazelcast.org.apache.calcite.schema.SchemaPlus;
import com.hazelcast.org.apache.calcite.schema.Schemas;
import com.hazelcast.org.apache.calcite.schema.Statistic;
import com.hazelcast.org.apache.calcite.schema.Statistics;
import com.hazelcast.org.apache.calcite.schema.Table;
import com.hazelcast.org.apache.calcite.schema.TableMacro;
import com.hazelcast.org.apache.calcite.schema.TranslatableTable;
import com.hazelcast.org.apache.calcite.schema.impl.AbstractSchema;
import com.hazelcast.org.apache.calcite.schema.impl.AbstractTableQueryable;
import com.hazelcast.org.apache.calcite.schema.impl.ReflectiveFunctionBase;
import com.hazelcast.org.apache.calcite.util.BuiltInMethod;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.com.google.common.collect.ImmutableList;
import com.hazelcast.com.google.common.collect.ImmutableMap;
import com.hazelcast.com.google.common.collect.ImmutableMultimap;
import com.hazelcast.com.google.common.collect.Iterables;
import com.hazelcast.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 com.hazelcast.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;
private Map tableMap;
private Multimap functionMap;
/**
* 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.
*
* May not appear to be used, but is used in generated code via
* {@link com.hazelcast.org.apache.calcite.util.BuiltInMethod#REFLECTIVE_SCHEMA_GET_TARGET}.
*/
public Object getTarget() {
return target;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override protected Map getTableMap() {
if (tableMap == null) {
tableMap = createTableMap();
}
return tableMap;
}
private Map createTableMap() {
final ImmutableMap.Builder 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 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 getFunctionMultimap() {
if (functionMap == null) {
functionMap = createFunctionMap();
}
return functionMap;
}
private Multimap createFunctionMap() {
final ImmutableMultimap.Builder 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 EnumUtils.convert(
Expressions.call(
Schemas.unwrap(
getExpression(parentSchema, name),
ReflectiveSchema.class),
BuiltInMethod.REFLECTIVE_SCHEMA_GET_TARGET.method),
target.getClass());
}
/** 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 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 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