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