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            if (!declaration.hasPrimaryConstructor()) return;
083    
084            ConstructorDescriptor constructor = classDescriptor.getConstructors().iterator().next();
085    
086            for (ValueParameterDescriptor parameter : constructor.getValueParameters()) {
087                FunctionDescriptor function = bindingContext.get(BindingContext.DATA_CLASS_COMPONENT_FUNCTION, parameter);
088                if (function != null) {
089                    generateComponentFunction(function, parameter);
090                }
091            }
092        }
093    
094        private void generateCopyFunctionForDataClasses(List<JetParameter> constructorParameters) {
095            FunctionDescriptor copyFunction = bindingContext.get(BindingContext.DATA_CLASS_COPY_FUNCTION, classDescriptor);
096            if (copyFunction != null) {
097                generateCopyFunction(copyFunction, constructorParameters);
098            }
099        }
100    
101        private void generateDataClassToStringIfNeeded(@NotNull List<PropertyDescriptor> properties) {
102            ClassDescriptor stringClass = KotlinBuiltIns.getInstance().getString();
103            if (!hasDeclaredNonTrivialMember(CodegenUtil.TO_STRING_METHOD_NAME, stringClass)) {
104                generateToStringMethod(properties);
105            }
106        }
107    
108        private void generateDataClassHashCodeIfNeeded(@NotNull List<PropertyDescriptor> properties) {
109            ClassDescriptor intClass = KotlinBuiltIns.getInstance().getInt();
110            if (!hasDeclaredNonTrivialMember(CodegenUtil.HASH_CODE_METHOD_NAME, intClass)) {
111                generateHashCodeMethod(properties);
112            }
113        }
114    
115        private void generateDataClassEqualsIfNeeded(@NotNull List<PropertyDescriptor> properties) {
116            ClassDescriptor booleanClass = KotlinBuiltIns.getInstance().getBoolean();
117            ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny();
118            if (!hasDeclaredNonTrivialMember(CodegenUtil.EQUALS_METHOD_NAME, booleanClass, anyClass)) {
119                generateEqualsMethod(properties);
120            }
121        }
122    
123        private List<PropertyDescriptor> getDataProperties() {
124            List<PropertyDescriptor> result = Lists.newArrayList();
125            for (JetParameter parameter : getPrimaryConstructorParameters()) {
126                if (parameter.hasValOrVarNode()) {
127                    result.add(bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter));
128                }
129            }
130            return result;
131        }
132    
133        private
134        @NotNull
135        List<JetParameter> getPrimaryConstructorParameters() {
136            if (declaration instanceof JetClass) {
137                return ((JetClass) declaration).getPrimaryConstructorParameters();
138            }
139            return Collections.emptyList();
140        }
141    
142        /**
143         * @return true if the class has a declared member with the given name anywhere in its hierarchy besides Any
144         */
145        private boolean hasDeclaredNonTrivialMember(
146                @NotNull String name,
147                @NotNull ClassDescriptor returnedClassifier,
148                @NotNull ClassDescriptor... valueParameterClassifiers
149        ) {
150            FunctionDescriptor function =
151                    CodegenUtil.getDeclaredFunctionByRawSignature(classDescriptor, Name.identifier(name), returnedClassifier,
152                                                                  valueParameterClassifiers);
153            if (function == null) {
154                return false;
155            }
156    
157            if (function.getKind() == CallableMemberDescriptor.Kind.DECLARATION) {
158                return true;
159            }
160    
161            for (CallableDescriptor overridden : OverrideResolver.getOverriddenDeclarations(function)) {
162                if (overridden instanceof CallableMemberDescriptor
163                    && ((CallableMemberDescriptor) overridden).getKind() == CallableMemberDescriptor.Kind.DECLARATION
164                    && !overridden.getContainingDeclaration().equals(KotlinBuiltIns.getInstance().getAny())) {
165                    return true;
166                }
167            }
168    
169            return false;
170        }
171    }