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.name.Name;
023    import org.jetbrains.kotlin.psi.JetClass;
024    import org.jetbrains.kotlin.psi.JetClassOrObject;
025    import org.jetbrains.kotlin.psi.JetParameter;
026    import org.jetbrains.kotlin.resolve.BindingContext;
027    import org.jetbrains.kotlin.resolve.BindingContextUtils;
028    import org.jetbrains.kotlin.resolve.OverrideResolver;
029    
030    import java.util.Collections;
031    import java.util.List;
032    
033    import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage.getBuiltIns;
034    
035    /**
036     * A platform-independent logic for generating data class synthetic methods.
037     * TODO: data class with zero components gets no toString/equals/hashCode methods. This is inconsistent and should be
038     *       changed here with the platform backends adopted.
039     */
040    public abstract class DataClassMethodGenerator {
041        private final JetClassOrObject declaration;
042        private final BindingContext bindingContext;
043        private final ClassDescriptor classDescriptor;
044    
045        public DataClassMethodGenerator(JetClassOrObject declaration, BindingContext bindingContext) {
046            this.declaration = declaration;
047            this.bindingContext = bindingContext;
048            this.classDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration);
049        }
050    
051        public void generate() {
052            generateComponentFunctionsForDataClasses();
053    
054            generateCopyFunctionForDataClasses(getPrimaryConstructorParameters());
055    
056            List<PropertyDescriptor> properties = getDataProperties();
057            if (!properties.isEmpty()) {
058                generateDataClassToStringIfNeeded(properties);
059                generateDataClassHashCodeIfNeeded(properties);
060                generateDataClassEqualsIfNeeded(properties);
061            }
062        }
063    
064        // Backend-specific implementations.
065        protected abstract void generateComponentFunction(
066                @NotNull FunctionDescriptor function,
067                @NotNull ValueParameterDescriptor parameter
068        );
069    
070        protected abstract void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<JetParameter> constructorParameters);
071    
072        protected abstract void generateToStringMethod(@NotNull List<PropertyDescriptor> properties);
073    
074        protected abstract void generateHashCodeMethod(@NotNull List<PropertyDescriptor> properties);
075    
076        protected abstract void generateEqualsMethod(@NotNull List<PropertyDescriptor> properties);
077    
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<JetParameter> 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            ClassDescriptor stringClass = getBuiltIns(classDescriptor).getString();
105            if (!hasDeclaredNonTrivialMember(CodegenUtil.TO_STRING_METHOD_NAME, stringClass)) {
106                generateToStringMethod(properties);
107            }
108        }
109    
110        private void generateDataClassHashCodeIfNeeded(@NotNull List<PropertyDescriptor> properties) {
111            ClassDescriptor intClass = getBuiltIns(classDescriptor).getInt();
112            if (!hasDeclaredNonTrivialMember(CodegenUtil.HASH_CODE_METHOD_NAME, intClass)) {
113                generateHashCodeMethod(properties);
114            }
115        }
116    
117        private void generateDataClassEqualsIfNeeded(@NotNull List<PropertyDescriptor> properties) {
118            ClassDescriptor booleanClass = getBuiltIns(classDescriptor).getBoolean();
119            ClassDescriptor anyClass = getBuiltIns(classDescriptor).getAny();
120            if (!hasDeclaredNonTrivialMember(CodegenUtil.EQUALS_METHOD_NAME, booleanClass, anyClass)) {
121                generateEqualsMethod(properties);
122            }
123        }
124    
125        private List<PropertyDescriptor> getDataProperties() {
126            List<PropertyDescriptor> result = Lists.newArrayList();
127            for (JetParameter parameter : getPrimaryConstructorParameters()) {
128                if (parameter.hasValOrVarNode()) {
129                    result.add(bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter));
130                }
131            }
132            return result;
133        }
134    
135        private
136        @NotNull
137        List<JetParameter> getPrimaryConstructorParameters() {
138            if (declaration instanceof JetClass) {
139                return ((JetClass) declaration).getPrimaryConstructorParameters();
140            }
141            return Collections.emptyList();
142        }
143    
144        /**
145         * @return true if the class has a declared member with the given name anywhere in its hierarchy besides Any
146         */
147        private boolean hasDeclaredNonTrivialMember(
148                @NotNull String name,
149                @NotNull ClassDescriptor returnedClassifier,
150                @NotNull ClassDescriptor... valueParameterClassifiers
151        ) {
152            FunctionDescriptor function =
153                    CodegenUtil.getDeclaredFunctionByRawSignature(classDescriptor, Name.identifier(name), returnedClassifier,
154                                                                  valueParameterClassifiers);
155            if (function == null) {
156                return false;
157            }
158    
159            if (function.getKind() == CallableMemberDescriptor.Kind.DECLARATION) {
160                return true;
161            }
162    
163            for (CallableDescriptor overridden : OverrideResolver.getOverriddenDeclarations(function)) {
164                if (overridden instanceof CallableMemberDescriptor
165                    && ((CallableMemberDescriptor) overridden).getKind() == CallableMemberDescriptor.Kind.DECLARATION
166                    && !overridden.getContainingDeclaration().equals(getBuiltIns(classDescriptor).getAny())) {
167                    return true;
168                }
169            }
170    
171            return false;
172        }
173    }