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.annotations.Nullable;
022    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
023    import org.jetbrains.kotlin.descriptors.*;
024    import org.jetbrains.kotlin.name.Name;
025    import org.jetbrains.kotlin.psi.KtClass;
026    import org.jetbrains.kotlin.psi.KtClassOrObject;
027    import org.jetbrains.kotlin.psi.KtParameter;
028    import org.jetbrains.kotlin.resolve.BindingContext;
029    import org.jetbrains.kotlin.resolve.BindingContextUtils;
030    import org.jetbrains.kotlin.resolve.OverrideResolver;
031    import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
032    
033    import java.util.Collections;
034    import java.util.List;
035    
036    /**
037     * A platform-independent logic for generating data class synthetic methods.
038     * TODO: data class with zero components gets no toString/equals/hashCode methods. This is inconsistent and should be
039     *       changed here with the platform backends adopted.
040     */
041    public abstract class DataClassMethodGenerator {
042        private final KtClassOrObject declaration;
043        private final BindingContext bindingContext;
044        private final ClassDescriptor classDescriptor;
045        private final KotlinBuiltIns builtIns;
046    
047        public DataClassMethodGenerator(KtClassOrObject declaration, BindingContext bindingContext) {
048            this.declaration = declaration;
049            this.bindingContext = bindingContext;
050            this.classDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration);
051            this.builtIns = DescriptorUtilsKt.getBuiltIns(classDescriptor);
052        }
053    
054        public void generate() {
055            generateComponentFunctionsForDataClasses();
056    
057            generateCopyFunctionForDataClasses(getPrimaryConstructorParameters());
058    
059            List<PropertyDescriptor> properties = getDataProperties();
060            if (!properties.isEmpty()) {
061                generateDataClassToStringIfNeeded(properties);
062                generateDataClassHashCodeIfNeeded(properties);
063                generateDataClassEqualsIfNeeded(properties);
064            }
065        }
066    
067        protected abstract void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull ValueParameterDescriptor parameter);
068    
069        protected abstract void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<KtParameter> constructorParameters);
070    
071        protected abstract void generateToStringMethod(@NotNull FunctionDescriptor function, @NotNull List<PropertyDescriptor> properties);
072    
073        protected abstract void generateHashCodeMethod(@NotNull FunctionDescriptor function, @NotNull List<PropertyDescriptor> properties);
074    
075        protected abstract void generateEqualsMethod(@NotNull FunctionDescriptor function, @NotNull List<PropertyDescriptor> properties);
076    
077        @NotNull
078        protected ClassDescriptor getClassDescriptor() {
079            return classDescriptor;
080        }
081    
082        private void generateComponentFunctionsForDataClasses() {
083            ConstructorDescriptor constructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
084            // primary constructor should exist for data classes
085            // but when generating light-classes still need to check we have one
086            if (constructor == null) return;
087    
088            for (ValueParameterDescriptor parameter : constructor.getValueParameters()) {
089                FunctionDescriptor function = bindingContext.get(BindingContext.DATA_CLASS_COMPONENT_FUNCTION, parameter);
090                if (function != null) {
091                    generateComponentFunction(function, parameter);
092                }
093            }
094        }
095    
096        private void generateCopyFunctionForDataClasses(List<KtParameter> constructorParameters) {
097            FunctionDescriptor copyFunction = bindingContext.get(BindingContext.DATA_CLASS_COPY_FUNCTION, classDescriptor);
098            if (copyFunction != null) {
099                generateCopyFunction(copyFunction, constructorParameters);
100            }
101        }
102    
103        private void generateDataClassToStringIfNeeded(@NotNull List<PropertyDescriptor> properties) {
104            FunctionDescriptor function = getDeclaredMember("toString", builtIns.getString());
105            if (function != null && isTrivial(function)) {
106                generateToStringMethod(function, properties);
107            }
108        }
109    
110        private void generateDataClassHashCodeIfNeeded(@NotNull List<PropertyDescriptor> properties) {
111            FunctionDescriptor function = getDeclaredMember("hashCode", builtIns.getInt());
112            if (function != null && isTrivial(function)) {
113                generateHashCodeMethod(function, properties);
114            }
115        }
116    
117        private void generateDataClassEqualsIfNeeded(@NotNull List<PropertyDescriptor> properties) {
118            FunctionDescriptor function = getDeclaredMember("equals", builtIns.getBoolean(), builtIns.getAny());
119            if (function != null && isTrivial(function)) {
120                generateEqualsMethod(function, properties);
121            }
122        }
123    
124        private List<PropertyDescriptor> getDataProperties() {
125            List<PropertyDescriptor> result = Lists.newArrayList();
126            for (KtParameter parameter : getPrimaryConstructorParameters()) {
127                if (parameter.hasValOrVar()) {
128                    result.add(bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter));
129                }
130            }
131            return result;
132        }
133    
134        @NotNull
135        private List<KtParameter> getPrimaryConstructorParameters() {
136            if (declaration instanceof KtClass) {
137                return declaration.getPrimaryConstructorParameters();
138            }
139            return Collections.emptyList();
140        }
141    
142        @Nullable
143        private FunctionDescriptor getDeclaredMember(
144                @NotNull String name,
145                @NotNull ClassDescriptor returnedClassifier,
146                @NotNull ClassDescriptor... valueParameterClassifiers
147        ) {
148            return CodegenUtil.getDeclaredFunctionByRawSignature(
149                    classDescriptor, Name.identifier(name), returnedClassifier, valueParameterClassifiers
150            );
151        }
152    
153        /**
154         * @return true if the member is an inherited implementation of a method from Any
155         */
156        private boolean isTrivial(@NotNull FunctionDescriptor function) {
157            if (function.getKind() == CallableMemberDescriptor.Kind.DECLARATION) {
158                return false;
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(builtIns.getAny())) {
165                    return false;
166                }
167            }
168    
169            return true;
170        }
171    }