com.redhat.ceylon.compiler.java.codegen.Strategy Maven / Gradle / Ivy
/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.compiler.java.codegen;
import java.util.List;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.MethodDeclaration;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.PositionalArgument;
import com.redhat.ceylon.model.loader.JvmBackendUtil;
import com.redhat.ceylon.model.loader.model.LazyClass;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassAlias;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Constructor;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Element;
import com.redhat.ceylon.model.typechecker.model.Functional;
import com.redhat.ceylon.model.typechecker.model.Function;
import com.redhat.ceylon.model.typechecker.model.FunctionOrValue;
import com.redhat.ceylon.model.typechecker.model.Interface;
import com.redhat.ceylon.model.typechecker.model.ModelUtil;
import com.redhat.ceylon.model.typechecker.model.Parameter;
import com.redhat.ceylon.model.typechecker.model.ParameterList;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.model.typechecker.model.Unit;
import com.redhat.ceylon.model.typechecker.model.Value;
/**
* Utility functions telling you about code generation strategies
* @see Decl
*/
class Strategy {
private Strategy() {}
public static boolean defaultParameterMethodTakesThis(Tree.Declaration decl) {
return defaultParameterMethodTakesThis(decl.getDeclarationModel());
}
public static boolean defaultParameterMethodTakesThis(Declaration decl) {
return decl instanceof Function
&& decl.isToplevel();
}
enum DefaultParameterMethodOwner {
/**
* DPM should be a member of an init companion (used for local class
* initializers with defaulted parameters)
*/
INIT_COMPANION,
/**
* DPM should be static in the top level class
*/
STATIC,
/**
* DPM should be a member of the outer class
*/
OUTER,
/**
* DPM should be a member of the outer interface's companion
*/
OUTER_COMPANION,
/**
* DPM should be a member of the current class/interface
*/
SELF
}
public static DefaultParameterMethodOwner defaultParameterMethodOwner(Declaration decl) {
if (decl instanceof Function
&& !Decl.withinInterface(decl)
&& !((Function)decl).isParameter()) {
return DefaultParameterMethodOwner.SELF;
}
if (decl instanceof FunctionOrValue
&& ((FunctionOrValue)decl).isParameter()) {
decl = (Declaration) decl.getContainer();
}
if (Decl.isConstructor(decl)) {
decl = Decl.getConstructedClass(decl);
}
if ((decl instanceof Function || decl instanceof Class)
&& decl.isToplevel()) {
// Only top-level methods have static default value methods
return DefaultParameterMethodOwner.STATIC;
} else if ((decl instanceof Class)
&& !decl.isToplevel()
&& !Decl.isLocalNotInitializer(decl)) {
// Only inner classes have their default value methods on their outer
return Decl.getClassOrInterfaceContainer(decl, false) instanceof Class ? DefaultParameterMethodOwner.OUTER : DefaultParameterMethodOwner.OUTER_COMPANION;
}
return DefaultParameterMethodOwner.INIT_COMPANION;
}
public static boolean defaultParameterMethodStatic(Tree.Declaration decl) {
// Only top-level methods and top-level class initializers
// have static default value methods
return defaultParameterMethodStatic(decl.getDeclarationModel());
}
public static boolean defaultParameterMethodStatic(Element decl) {
if (decl instanceof FunctionOrValue
&& ((FunctionOrValue)decl).isParameter()) {
decl = (Element) decl.getContainer();
}
if (decl instanceof Declaration && Decl.isConstructor((Declaration)decl)) {
decl = (Class)Decl.getConstructedClass((Declaration)decl);
}
// Only top-level methods have static default value methods
return ((decl instanceof Function && !((Function)decl).isParameter())
|| decl instanceof Class)
&& ((Declaration)decl).isToplevel();
}
public static boolean defaultParameterMethodOnOuter(Tree.Declaration decl) {
// Only top-level methods and top-level class initializers
// have static default value methods
return defaultParameterMethodOnOuter(decl.getDeclarationModel());
}
public static boolean defaultParameterMethodOnOuter(Element elem) {
if (elem instanceof Declaration
&& Decl.isConstructor((Declaration)elem)) {
elem = Decl.getConstructedClass((Declaration)elem);
}
if (elem instanceof FunctionOrValue
&& ((FunctionOrValue)elem).isParameter()) {
elem = (Element) ((FunctionOrValue)elem).getContainer();
}
// Only inner classes have their default value methods on their outer
return (elem instanceof Class)
&& !((Class)elem).isToplevel()
&& !Decl.isLocalNotInitializer((Class)elem);
}
public static boolean defaultParameterMethodOnSelf(Tree.Declaration decl) {
return defaultParameterMethodOnSelf(decl.getDeclarationModel());
}
public static boolean defaultParameterMethodOnSelf(Declaration decl) {
return decl instanceof Function
&& !((Function)decl).isParameter()
&& !Decl.withinInterface(decl);
}
public static boolean hasDefaultParameterValueMethod(Parameter param) {
return param.isDefaulted();
}
public static boolean hasDefaultParameterOverload(Parameter param) {
return param.isDefaulted() ||
(param.isSequenced() && !param.isAtLeastOne());
}
public static boolean hasEmptyDefaultArgument(Parameter param) {
return (param.isSequenced() && !param.isAtLeastOne());
}
/**
* Determines whether the given Class def should have a {@code main()} method generated.
* I.e. it's a shared concrete top level Class without initializer parameters
* @param def
*/
public static boolean generateMain(Tree.ClassOrInterface def) {
for (Tree.CompilerAnnotation c: def.getCompilerAnnotations()) {
if (c.getIdentifier().getText().equals("nomain")) {
return false;
}
}
return def instanceof Tree.AnyClass
&& Decl.isToplevel(def)
&& Decl.isShared(def)
&& !Decl.isAbstract(def)
&& !def.getDeclarationModel().isNativeHeader()
&& hasNoRequiredParameters((Class)def.getDeclarationModel());
}
/**
* Returns true if the given functional takes no parameters or accepts only defaulted params
*/
private static boolean hasNoRequiredParameters(Functional model) {
List parameterLists = model.getParameterLists();
if(parameterLists == null || parameterLists.size() != 1)
return false;
ParameterList parameterList = parameterLists.get(0);
if(parameterList == null)
return false;
List parameters = parameterList.getParameters();
if(parameters == null)
return false;
if(parameters.isEmpty())
return true;
// if the first one is optional, all others have to be too
return parameters.get(0).isDefaulted();
}
/**
* Determines whether the given Function def should have a {@code main()} method generated.
* I.e. it's a shared top level method without parameters
* @param def
*/
public static boolean generateMain(Tree.AnyMethod def) {
return Decl.isToplevel(def)
&& Decl.isShared(def)
&& hasNoRequiredParameters(def.getDeclarationModel());
}
public static boolean generateThisDelegates(Tree.AnyMethod def) {
return Decl.withinInterface(def.getDeclarationModel())
&& def instanceof MethodDeclaration
&& ((MethodDeclaration) def).getSpecifierExpression() == null;
}
public static boolean needsOuterMethodInCompanion(ClassOrInterface model) {
return Decl.withinClassOrInterface(model);
}
public static boolean useField(Value attr) {
return !Decl.withinInterface(attr) && Decl.isCaptured(attr);
}
public static boolean createField(Parameter p, Value v) {
return !Decl.withinInterface(v)
&& (p == null
|| (useField(v)));
}
/**
* Determines whether a method wrapping the Callable should be generated
* for a FunctionalParameter
*/
static boolean createMethod(Tree.Parameter parameter) {
return createMethod(parameter.getParameterModel());
}
/**
* Determines whether a method wrapping the Callable should be generated
* for a FunctionalParameter
*/
static boolean createMethod(Parameter parameter) {
FunctionOrValue model = parameter.getModel();
return JvmBackendUtil.createMethod(model);
}
/**
* Non-{@code shared} concrete interface members are
* only defined/declared on the companion class, not on the transformed
* interface itself.
*/
public static boolean onlyOnCompanion(Declaration model) {
return Decl.withinInterface(model)
&& (model instanceof ClassOrInterface
|| !Decl.isShared(model));
}
static boolean generateInstantiator(Declaration model) {
if (model instanceof Class) {
Class cls = (Class)model;
return !cls.isAbstract()
&& (Decl.isRefinableMemberClass(cls)
||
// If shared, generate an instantiator so that BC is
// preserved should the member class later become refinable
(Decl.isCeylon(cls)
&& model.isMember()
&& cls.isShared()
&& !cls.isAnonymous()));
} else if (Decl.isConstructor(model)) {
Constructor ctor = Decl.getConstructor(model);
Class cls = Decl.getConstructedClass(ctor);
return cls.isMember()
&& cls.isShared()
&& ctor.isShared();
} else {
return false;
}
}
/**
* Does the given declaration have an instantiator that is declared to
* return Object (so needs a typecast when invoked).
*/
static boolean isInstantiatorUntyped(Declaration model) {
return generateInstantiator(model) && Decl.isAncestorLocal(model);
}
/**
* Determines whether a {@code void} Ceylon method should be declared to
* return {@code void} or {@code java.lang.Object} (the erasure of
* {@code ceylon.language.Anything}) in Java.
* If the method can be refined,
* (but was not itself refined from a Java {@code void} method), or is
* {@code actual} then {@code java.lang.Object} should be used.
*/
static boolean useBoxedVoid(Function m) {
return m.isMember() &&
(m.isDefault() || m.isFormal() || m.isActual()) &&
m.getType().isAnything() &&
Decl.isCeylon((TypeDeclaration)m.getRefinedDeclaration().getContainer());
}
public static boolean hasDelegatedDpm(Class cls) {
return Decl.isRefinableMemberClass(cls.getRefinedDeclaration()) &&
Strategy.defaultParameterMethodOnOuter(cls) &&
Decl.withinInterface(cls.getRefinedDeclaration());
}
/**
* Determines whether we should inline {@code x^n} as a repeated multiplication
* ({@code x*x*...*x}) instead of calling {@code x.power(n)}.
*/
public static boolean inlinePowerAsMultiplication(Tree.PowerOp op) {
java.lang.Long power;
try {
power = ExpressionTransformer.getIntegerLiteralPower(op);
if (power != null) {
Unit unit = op.getUnit();
Type baseType = op.getLeftTerm().getTypeModel();
// Although the optimisation still works for powers > 64, it ends
// up bloating the code (e.g. imagine x^1_000_000_000)
if (power > 0
&& power <= 64
&& baseType.isExactly(unit.getIntegerType())) {
return true;
}
else if (power > 0
&& power <= 64
&& baseType.isExactly(unit.getFloatType())) {
return true;
}
}
} catch (ErroneousException e) {
return false;
}
return false;
}
/**
* Whether to use a
* {@code LazySwitchingIterable} instead of a {@code LazyInvokingIterable}.
* The only real consideration here is whether switching would result in
* method code that exceeded the limits of the class file format.
*/
public static boolean preferLazySwitchingIterable(
java.util.List positionalArguments) {
return positionalArguments.size() < 128;
}
public static boolean generateJpaCtor(
Tree.ClassOrInterface def) {
return generateJpaCtor(def.getDeclarationModel());
}
static boolean generateJpaCtor(ClassOrInterface declarationModel) {
if (declarationModel instanceof Class
&& !(declarationModel instanceof ClassAlias)
&& declarationModel.isToplevel()) {
Class cls = (Class)declarationModel;
if (cls.getCaseValues() != null && !cls.getCaseValues().isEmpty()) {
return false;
}
if (hasNullaryNonJpaConstructor(cls)) {
// The class will already have a nullary ctor
return false;
}
boolean hasDelegatableSuper = false;
Class superClass = (Class)cls.getExtendedType().getDeclaration();
if (superClass instanceof LazyClass
&& !((LazyClass)superClass).isCeylon()) {
if (superClass.isAbstraction()) {
for (Declaration s : superClass.getOverloads()) {
if (s instanceof Class
&& isNullary((Class)s)) {
hasDelegatableSuper = true;
break;
}
}
} else {
// If the superclass is Java then generate a Jpa constructor
// if there's a nullary superclass constructor we can call
hasDelegatableSuper = isNullary(superClass);
}
} else {
hasDelegatableSuper = hasNullaryNonJpaConstructor(superClass)
|| hasJpaConstructor(superClass);
}
boolean constrained =
(cls.getCaseValues() != null
&& !cls.getCaseValues().isEmpty())
|| cls.hasEnumerated() && Decl.hasOnlyValueConstructors(cls);
return hasDelegatableSuper
&& !constrained;
} else {
return false;
}
}
private static boolean isNullary(Class superClass) {
return superClass.getParameterList() != null
&& superClass.getParameterList().getParameters() != null
&& superClass.getParameterList().getParameters().isEmpty();
}
/**
* Whether the given class has a "JPA constructor".
* A JPA constructor is a hidden-from-Ceylon constructor
* which is used by frameworks like JPA. To be useful to frameworks
* the JPA constructor must be nullary. A JPA constructor
* will not be nullary if the class is generic: This makes it non-useful
* to JPA, but allows subclasses to delegate to it and have their own
* nullary JPA constructor.
* @param c The class
* @return
*/
public static boolean hasJpaConstructor(Class c) {
if (c instanceof LazyClass
&& !(c instanceof ClassAlias)) {
// If it's binary it has a JpaConstructor if it says so
return ((LazyClass)c).hasJpaConstructor();
} else {
return generateJpaCtor(c);
}
}
/**
* Will this class have a nullary Java constructor, excluding JPA constructors
* @param c
* @return
*/
protected static boolean hasNullaryNonJpaConstructor(Class c) {
if (c.isToplevel()
&& !(c instanceof ClassAlias)) {
List parameters = null;
if (c.hasConstructors()) {
Constructor defaultConstructor = Decl.getDefaultConstructor(c);
if (defaultConstructor != null
&& defaultConstructor.getParameterList() != null) {
parameters = defaultConstructor.getParameterList().getParameters();
}
} else if (c.getParameterList() != null ){
parameters = c.getParameterList().getParameters();
}
return parameters != null
&& (parameters.isEmpty()
|| parameters.get(0).isDefaulted()
|| (parameters.get(0).isSequenced() && !parameters.get(0).isAtLeastOne()));
}
return false;
}
public static boolean introduceJavaIoSerializable(
Class cls, Interface ser) {
if (!(cls instanceof ClassAlias)) {
if ((Decl.hasOnlyValueConstructors(cls)
|| cls.isAnonymous()
|| (cls.getExtendedType() != null
&& (cls.getExtendedType().isBasic()
|| cls.getExtendedType().isObject())))
&& !cls.getSatisfiedTypes().contains(ser.getType())) {
return true;
}
}
return false;
}
/** Should the given class have a readResolve() method added ?
* @param ser */
public static boolean addReadResolve(Class cls) {
if (cls.isAnonymous()
&& cls.isToplevel()) {
return true;
}
return false;
}
public static boolean useSerializationProxy(Class model) {
return model.hasEnumerated()
&& (model.isToplevel() || model.isMember());
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy