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 }