001 /*
002 * Copyright 2010-2014 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.jet.backend.common;
018
019 import com.google.common.collect.Lists;
020 import org.jetbrains.annotations.NotNull;
021 import org.jetbrains.jet.lang.descriptors.*;
022 import org.jetbrains.jet.lang.psi.JetClass;
023 import org.jetbrains.jet.lang.psi.JetClassOrObject;
024 import org.jetbrains.jet.lang.psi.JetParameter;
025 import org.jetbrains.jet.lang.resolve.BindingContext;
026 import org.jetbrains.jet.lang.resolve.BindingContextUtils;
027 import org.jetbrains.jet.lang.resolve.OverrideResolver;
028 import org.jetbrains.jet.lang.resolve.name.Name;
029 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
030
031 import java.util.Collections;
032 import java.util.List;
033
034 /**
035 * A platform-independent logic for generating data class synthetic methods.
036 * TODO: data class with zero components gets no toString/equals/hashCode methods. This is inconsistent and should be
037 * changed here with the platform backends adopted.
038 */
039 public abstract class DataClassMethodGenerator {
040 private final JetClassOrObject declaration;
041 private final BindingContext bindingContext;
042 private final ClassDescriptor classDescriptor;
043
044 public DataClassMethodGenerator(JetClassOrObject declaration, BindingContext bindingContext) {
045 this.declaration = declaration;
046 this.bindingContext = bindingContext;
047 this.classDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration);
048 }
049
050 public void generate() {
051 generateComponentFunctionsForDataClasses();
052
053 generateCopyFunctionForDataClasses(getPrimaryConstructorParameters());
054
055 List<PropertyDescriptor> properties = getDataProperties();
056 if (!properties.isEmpty()) {
057 generateDataClassToStringIfNeeded(properties);
058 generateDataClassHashCodeIfNeeded(properties);
059 generateDataClassEqualsIfNeeded(properties);
060 }
061 }
062
063 // Backend-specific implementations.
064 protected abstract void generateComponentFunction(
065 @NotNull FunctionDescriptor function,
066 @NotNull ValueParameterDescriptor parameter
067 );
068
069 protected abstract void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<JetParameter> constructorParameters);
070
071 protected abstract void generateToStringMethod(@NotNull List<PropertyDescriptor> properties);
072
073 protected abstract void generateHashCodeMethod(@NotNull List<PropertyDescriptor> properties);
074
075 protected abstract void generateEqualsMethod(@NotNull List<PropertyDescriptor> properties);
076
077 protected ClassDescriptor getClassDescriptor() {
078 return classDescriptor;
079 }
080
081 private void generateComponentFunctionsForDataClasses() {
082 if (!declaration.hasPrimaryConstructor()) return;
083
084 ConstructorDescriptor constructor = classDescriptor.getConstructors().iterator().next();
085
086 for (ValueParameterDescriptor parameter : constructor.getValueParameters()) {
087 FunctionDescriptor function = bindingContext.get(BindingContext.DATA_CLASS_COMPONENT_FUNCTION, parameter);
088 if (function != null) {
089 generateComponentFunction(function, parameter);
090 }
091 }
092 }
093
094 private void generateCopyFunctionForDataClasses(List<JetParameter> constructorParameters) {
095 FunctionDescriptor copyFunction = bindingContext.get(BindingContext.DATA_CLASS_COPY_FUNCTION, classDescriptor);
096 if (copyFunction != null) {
097 generateCopyFunction(copyFunction, constructorParameters);
098 }
099 }
100
101 private void generateDataClassToStringIfNeeded(@NotNull List<PropertyDescriptor> properties) {
102 ClassDescriptor stringClass = KotlinBuiltIns.getInstance().getString();
103 if (!hasDeclaredNonTrivialMember(CodegenUtil.TO_STRING_METHOD_NAME, stringClass)) {
104 generateToStringMethod(properties);
105 }
106 }
107
108 private void generateDataClassHashCodeIfNeeded(@NotNull List<PropertyDescriptor> properties) {
109 ClassDescriptor intClass = KotlinBuiltIns.getInstance().getInt();
110 if (!hasDeclaredNonTrivialMember(CodegenUtil.HASH_CODE_METHOD_NAME, intClass)) {
111 generateHashCodeMethod(properties);
112 }
113 }
114
115 private void generateDataClassEqualsIfNeeded(@NotNull List<PropertyDescriptor> properties) {
116 ClassDescriptor booleanClass = KotlinBuiltIns.getInstance().getBoolean();
117 ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny();
118 if (!hasDeclaredNonTrivialMember(CodegenUtil.EQUALS_METHOD_NAME, booleanClass, anyClass)) {
119 generateEqualsMethod(properties);
120 }
121 }
122
123 private List<PropertyDescriptor> getDataProperties() {
124 List<PropertyDescriptor> result = Lists.newArrayList();
125 for (JetParameter parameter : getPrimaryConstructorParameters()) {
126 if (parameter.hasValOrVarNode()) {
127 result.add(bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter));
128 }
129 }
130 return result;
131 }
132
133 private
134 @NotNull
135 List<JetParameter> getPrimaryConstructorParameters() {
136 if (declaration instanceof JetClass) {
137 return ((JetClass) declaration).getPrimaryConstructorParameters();
138 }
139 return Collections.emptyList();
140 }
141
142 /**
143 * @return true if the class has a declared member with the given name anywhere in its hierarchy besides Any
144 */
145 private boolean hasDeclaredNonTrivialMember(
146 @NotNull String name,
147 @NotNull ClassDescriptor returnedClassifier,
148 @NotNull ClassDescriptor... valueParameterClassifiers
149 ) {
150 FunctionDescriptor function =
151 CodegenUtil.getDeclaredFunctionByRawSignature(classDescriptor, Name.identifier(name), returnedClassifier,
152 valueParameterClassifiers);
153 if (function == null) {
154 return false;
155 }
156
157 if (function.getKind() == CallableMemberDescriptor.Kind.DECLARATION) {
158 return true;
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(KotlinBuiltIns.getInstance().getAny())) {
165 return true;
166 }
167 }
168
169 return false;
170 }
171 }