All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.adapter.java.ReflectiveSchema Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
/*
 * 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 com.hazelcast.org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;

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;

import static java.util.Objects.requireNonNull;

/**
 * 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 final Object target;
  private @MonotonicNonNull Map tableMap;
  private @MonotonicNonNull 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); } requireNonNull(rc, () -> "field must not be null: " + field); FieldTable table = (FieldTable) tableMap.get(Util.last(rc.getSourceQualifiedName())); assert table != null; List referentialConstraints = table.getStatistic().getReferentialConstraints(); if (referentialConstraints == null) { // This enables to keep the same Statistics.of below referentialConstraints = ImmutableList.of(); } table.statistic = Statistics.of( ImmutableList.copyOf( Iterables.concat( referentialConstraints, 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(@Nullable 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 @Nullable 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); } requireNonNull(o, () -> "field " + field + " is null for " + target); @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 @Nullable 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 Enumerable enumerable; ReflectiveTable(Type elementType, Enumerable enumerable) { super(elementType); this.enumerable = enumerable; } @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { return ((JavaTypeFactory) typeFactory).createType(elementType); } @Override public Statistic getStatistic() { return Statistics.UNKNOWN; } @Override public Enumerable<@Nullable Object[]> scan(DataContext root) { if (elementType == Object[].class) { //noinspection unchecked return enumerable; } else { //noinspection unchecked return enumerable.select(new FieldSelector((Class) elementType)); } } @Override public Queryable asQueryable(QueryProvider queryProvider, SchemaPlus schema, String tableName) { return new AbstractTableQueryable(queryProvider, schema, this, tableName) { @SuppressWarnings("unchecked") @Override public Enumerator enumerator() { return (Enumerator) enumerable.enumerator(); } }; } } /** Factory that creates a schema by instantiating an object and looking at * its public fields. * *

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. * *

   * schemas: [
   *     {
   *       name: "foodmart",
   *       type: "custom",
   *       factory: "com.hazelcast.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;
   * }
*/ public static class Factory implements SchemaFactory { @Override public Schema create(SchemaPlus parentSchema, String name, Map 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); requireNonNull(target, () -> "method " + method + " returns 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"; } @Override public String toString() { return "Member {method=" + method + "}"; } @Override public TranslatableTable apply(final List arguments) { try { final Object o = requireNonNull( method.invoke(schema.getTarget(), arguments.toArray()), () -> "method " + method + " returned null for arguments " + arguments); return (TranslatableTable) o; } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } /** Table based on a Java field. * * @param element type */ private static class FieldTable extends ReflectiveTable { private final Field field; private Statistic statistic; FieldTable(Field field, Type elementType, Enumerable enumerable) { this(field, elementType, enumerable, Statistics.UNKNOWN); } FieldTable(Field field, Type elementType, Enumerable enumerable, Statistic statistic) { super(elementType, enumerable); this.field = field; this.statistic = statistic; } @Override public String toString() { return "Relation {field=" + field.getName() + "}"; } @Override public Statistic getStatistic() { return statistic; } @Override public Expression getExpression(SchemaPlus schema, String tableName, Class clazz) { ReflectiveSchema reflectiveSchema = requireNonNull( schema.unwrap(ReflectiveSchema.class), () -> "schema.unwrap(ReflectiveSchema.class) for " + schema); return Expressions.field( reflectiveSchema.getTargetExpression( schema.getParentSchema(), schema.getName()), field); } } /** Function that returns an array of a given object's field values. */ private static class FieldSelector implements Function1 { private final Field[] fields; FieldSelector(Class elementType) { this.fields = elementType.getFields(); } @Override public @Nullable Object[] apply(Object o) { try { final @Nullable 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); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy