001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.backend.common;
018    
019    import com.google.common.collect.Lists;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.kotlin.descriptors.*;
022    import org.jetbrains.kotlin.psi.JetClass;
023    import org.jetbrains.kotlin.psi.JetClassOrObject;
024    import org.jetbrains.kotlin.psi.JetParameter;
025    import org.jetbrains.kotlin.resolve.BindingContext;
026    import org.jetbrains.kotlin.resolve.BindingContextUtils;
027    import org.jetbrains.kotlin.resolve.OverrideResolver;
028    import org.jetbrains.kotlin.name.Name;
029    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
030    
031    import java.util.Collections;
032    import java.util.List;
033    
034    /**
035     * A platform-independent logic for generating data class synthetic methods.
036     * TODO: data class with zero components gets no toString/equals/hashCode methods. This is inconsistent and should be
037     *       changed here with the platform backends adopted.
038     */
039    public abstract class DataClassMethodGenerator {
040        private final JetClassOrObject declaration;
041        private final BindingContext bindingContext;
042        private final ClassDescriptor classDescriptor;
043    
044        public DataClassMethodGenerator(JetClassOrObject declaration, BindingContext bindingContext) {
045            this.declaration = declaration;
046            this.bindingContext = bindingContext;
047            this.classDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration);
048        }
049    
050        public void generate() {
051            generateComponentFunctionsForDataClasses();
052    
053            generateCopyFunctionForDataClasses(getPrimaryConstructorParameters());
054    
055            List<PropertyDescriptor> properties = getDataProperties();
056            if (!properties.isEmpty()) {
057                generateDataClassToStringIfNeeded(properties);
058                generateDataClassHashCodeIfNeeded(properties);
059                generateDataClassEqualsIfNeeded(properties);
060            }
061        }
062    
063        // Backend-specific implementations.
064        protected abstract void generateComponentFunction(
065                @NotNull FunctionDescriptor function,
066                @NotNull ValueParameterDescriptor parameter
067        );
068    
069        protected abstract void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<JetParameter> constructorParameters);
070    
071        protected abstract void generateToStringMethod(@NotNull List<PropertyDescriptor> properties);
072    
073        protected abstract void generateHashCodeMethod(@NotNull List<PropertyDescriptor> properties);
074    
075        protected abstract void generateEqualsMethod(@NotNull List<PropertyDescriptor> properties);
076    
077        protected ClassDescriptor getClassDescriptor() {
078            return classDescriptor;
079        }
080    
081        private void generateComponentFunctionsForDataClasses() {
082            ConstructorDescriptor constructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
083            // primary constructor should exist for data classes
084            // but when generating light-classes still need to check we have one
085            if (constructor == null) return;
086    
087            for (ValueParameterDescriptor parameter : constructor.getValueParameters()) {
088                FunctionDescriptor function = bindingContext.get(BindingContext.DATA_CLASS_COMPONENT_FUNCTION, parameter);
089                if (function != null) {
090                    generateComponentFunction(function, parameter);
091                }
092            }
093        }
094    
095        private void generateCopyFunctionForDataClasses(List<JetParameter> constructorParameters) {
096            FunctionDescriptor copyFunction = bindingContext.get(BindingContext.DATA_CLASS_COPY_FUNCTION, classDescriptor);
097            if (copyFunction != null) {
098                generateCopyFunction(copyFunction, constructorParameters);
099            }
100        }
101    
102        private void generateDataClassToStringIfNeeded(@NotNull List<PropertyDescriptor> properties) {
103            ClassDescriptor stringClass = KotlinBuiltIns.getInstance().getString();
104            if (!hasDeclaredNonTrivialMember(CodegenUtil.TO_STRING_METHOD_NAME, stringClass)) {
105                generateToStringMethod(properties);
106            }
107        }
108    
109        private void generateDataClassHashCodeIfNeeded(@NotNull List<PropertyDescriptor> properties) {
110            ClassDescriptor intClass = KotlinBuiltIns.getInstance().getInt();
111            if (!hasDeclaredNonTrivialMember(CodegenUtil.HASH_CODE_METHOD_NAME, intClass)) {
112                generateHashCodeMethod(properties);
113            }
114        }
115    
116        private void generateDataClassEqualsIfNeeded(@NotNull List<PropertyDescriptor> properties) {
117            ClassDescriptor booleanClass = KotlinBuiltIns.getInstance().getBoolean();
118            ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny();
119            if (!hasDeclaredNonTrivialMember(CodegenUtil.EQUALS_METHOD_NAME, booleanClass, anyClass)) {
120                generateEqualsMethod(properties);
121            }
122        }
123    
124        private List<PropertyDescriptor> getDataProperties() {
125            List<PropertyDescriptor> result = Lists.newArrayList();
126            for (JetParameter parameter : getPrimaryConstructorParameters()) {
127                if (parameter.hasValOrVarNode()) {
128                    result.add(bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter));
129                }
130            }
131            return result;
132        }
133    
134        private
135        @NotNull
136        List<JetParameter> getPrimaryConstructorParameters() {
137            if (declaration instanceof JetClass) {
138                return ((JetClass) declaration).getPrimaryConstructorParameters();
139            }
140            return Collections.emptyList();
141        }
142    
143        /**
144         * @return true if the class has a declared member with the given name anywhere in its hierarchy besides Any
145         */
146        private boolean hasDeclaredNonTrivialMember(
147                @NotNull String name,
148                @NotNull ClassDescriptor returnedClassifier,
149                @NotNull ClassDescriptor... valueParameterClassifiers
150        ) {
151            FunctionDescriptor function =
152                    CodegenUtil.getDeclaredFunctionByRawSignature(classDescriptor, Name.identifier(name), returnedClassifier,
153                                                                  valueParameterClassifiers);
154            if (function == null) {
155                return false;
156            }
157    
158            if (function.getKind() == CallableMemberDescriptor.Kind.DECLARATION) {
159                return true;
160            }
161    
162            for (CallableDescriptor overridden : OverrideResolver.getOverriddenDeclarations(function)) {
163                if (overridden instanceof CallableMemberDescriptor
164                    && ((CallableMemberDescriptor) overridden).getKind() == CallableMemberDescriptor.Kind.DECLARATION
165                    && !overridden.getContainingDeclaration().equals(KotlinBuiltIns.getInstance().getAny())) {
166                    return true;
167                }
168            }
169    
170            return false;
171        }
172    }