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

com.redhat.ceylon.compiler.java.codegen.Strategy Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * 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