org.apache.calcite.linq4j.tree.ClassDeclarationFinder 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 org.apache.calcite.linq4j.tree;
import org.apache.calcite.linq4j.function.Function1;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* Entry point for optimizers that factor out deterministic expressions to
* final static fields.
* Instances of this class should not be reused, so new visitor should be
* created for optimizing a new expression tree.
*/
public class ClassDeclarationFinder extends Shuttle {
protected final @Nullable ClassDeclarationFinder parent;
/**
* The list of new final static fields to be added to the current class.
*/
protected final List addedDeclarations = new ArrayList<>();
private final Function1 childFactory;
private static final Function1 DEFAULT_CHILD_FACTORY =
DeterministicCodeOptimizer::new;
/**
* Creates visitor that uses default optimizer.
*
* @return optimizing visitor
*/
public static ClassDeclarationFinder create() {
return create(DEFAULT_CHILD_FACTORY);
}
/**
* Creates visitor that uses given class as optimizer.
* The implementation should support ({@code ClassDeclarationFinder})
* constructor.
*
* @param optimizingClass class that implements optimizations
* @return optimizing visitor
*/
public static ClassDeclarationFinder create(
final Class extends ClassDeclarationFinder> optimizingClass) {
return create(newChildCreator(optimizingClass));
}
/**
* Creates visitor that uses given factory to create optimizers.
*
* @param childFactory factory that creates optimizers
* @return optimizing visitor
*/
public static ClassDeclarationFinder create(
Function1 childFactory) {
return new ClassDeclarationFinder(childFactory);
}
/**
* Creates factory that creates instances of optimizing visitors.
* The implementation should support ({@code ClassDeclarationFinder})
* constructor.
*
* @param optimizingClass class that implements optimizations
* @return factory that creates instances of given classes
*/
private static Function1 newChildCreator(
Class extends ClassDeclarationFinder> optimizingClass) {
try {
final Constructor extends ClassDeclarationFinder> constructor =
optimizingClass.getConstructor(ClassDeclarationFinder.class);
return a0 -> {
try {
return constructor.newInstance(a0);
} catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException(
"Unable to create optimizer via " + constructor, e);
}
};
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Given class " + optimizingClass
+ "does not support (ClassDeclarationFinder) constructor", e);
}
}
/**
* Creates optimizer with no parent.
*/
private ClassDeclarationFinder(
Function1 childFactory) {
this.parent = null;
this.childFactory = childFactory;
}
/**
* Creates a child optimizer.
* Typically a child is created for each class declaration,
* so each optimizer collects fields for exactly one class.
*
* @param parent parent optimizer
*/
protected ClassDeclarationFinder(ClassDeclarationFinder parent) {
this.parent = parent;
this.childFactory = parent.childFactory;
}
/**
* Creates optimizer local to the newly generated anonymous class.
*
* @param newExpression expression to optimize
* @return nested visitor if anonymous class is given
*/
@Override public Shuttle preVisit(NewExpression newExpression) {
if (newExpression.memberDeclarations == null) {
return this;
}
ClassDeclarationFinder visitor = goDeeper();
visitor.learnFinalStaticDeclarations(newExpression.memberDeclarations);
return visitor;
}
/**
* Creates optimizer local to the newly generated class.
*
* @param classDeclaration expression to optimize
* @return nested visitor
*/
@Override public Shuttle preVisit(ClassDeclaration classDeclaration) {
ClassDeclarationFinder visitor = goDeeper();
visitor.learnFinalStaticDeclarations(classDeclaration.memberDeclarations);
return visitor;
}
@Override public Expression visit(NewExpression newExpression,
List arguments, @Nullable List memberDeclarations) {
if (parent == null) {
// Unable to optimize since no wrapper class exists to put fields to.
arguments = newExpression.arguments;
} else if (memberDeclarations != null) {
// Arguments to new Test(1+2) { ... } should be optimized via parent
// optimizer.
arguments = Expressions.acceptExpressions(newExpression.arguments, parent);
}
Expression result =
super.visit(newExpression, arguments, memberDeclarations);
if (memberDeclarations == null) {
return tryOptimizeNewInstance((NewExpression) result);
}
memberDeclarations = optimizeDeclarations(memberDeclarations);
return super.visit((NewExpression) result, arguments,
memberDeclarations);
}
/**
* Processes the list of declarations when class expression detected.
* Sub-classes might figure out the existing fields for reuse.
*
* @param memberDeclarations list of declarations to process.
*/
protected void learnFinalStaticDeclarations(
List memberDeclarations) {
}
/**
* Optimizes {@code new Type()} constructs.
*
* @param newExpression expression to optimize
* @return always returns un-optimized expression
*/
protected Expression tryOptimizeNewInstance(NewExpression newExpression) {
return newExpression;
}
@Override public ClassDeclaration visit(ClassDeclaration classDeclaration,
List memberDeclarations) {
memberDeclarations = optimizeDeclarations(memberDeclarations);
return super.visit(classDeclaration, memberDeclarations);
}
/**
* Adds new declarations (e.g. final static fields) to the list of existing
* ones.
*
* @param memberDeclarations existing list of declarations
* @return new list of declarations or the same if no modifications required
*/
protected List optimizeDeclarations(
List memberDeclarations) {
if (addedDeclarations.isEmpty()) {
return memberDeclarations;
}
List newDecls =
new ArrayList<>(memberDeclarations.size()
+ addedDeclarations.size());
newDecls.addAll(memberDeclarations);
newDecls.addAll(addedDeclarations);
return newDecls;
}
/**
* Verifies if the expression is effectively constant.
* This method should be overridden in sub-classes.
*
* @param expression expression to test
* @return always returns false
*/
protected boolean isConstant(Expression expression) {
return false;
}
/**
* Verifies if all the expressions in given list are effectively constant.
*
* @param list list of expressions to test
* @return true when all the expressions are known to be constant
*/
protected boolean isConstant(Iterable extends Expression> list) {
for (Expression expression : list) {
if (!isConstant(expression)) {
return false;
}
}
return true;
}
/**
* Finds if there exists ready for reuse declaration for given expression.
* This method should be overridden in sub-classes.
*
* @param expression input expression
* @return always returns null
*/
protected @Nullable ParameterExpression findDeclaredExpression(Expression expression) {
return null;
}
/**
* Verifies if the variable name is already in use.
* This method should be overridden in sub-classes.
*
* @param name name of the variable to test
* @return always returns false
*/
protected boolean hasField(String name) {
return false;
}
/**
* Creates child visitor. It is used to traverse nested class declarations.
*
* @return new {@code Visitor} that is used to optimize class declarations
*/
protected ClassDeclarationFinder goDeeper() {
return childFactory.apply(this);
}
}