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 }