001 /*
002 * Copyright 2010-2013 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.lang.resolve.java.sam;
018
019 import org.jetbrains.annotations.NotNull;
020 import org.jetbrains.annotations.Nullable;
021 import org.jetbrains.jet.lang.descriptors.*;
022 import org.jetbrains.jet.lang.descriptors.annotations.Annotations;
023 import org.jetbrains.jet.lang.descriptors.impl.TypeParameterDescriptorImpl;
024 import org.jetbrains.jet.lang.descriptors.impl.ValueParameterDescriptorImpl;
025 import org.jetbrains.jet.lang.resolve.java.descriptor.*;
026 import org.jetbrains.jet.lang.resolve.java.resolver.DescriptorResolverUtils;
027 import org.jetbrains.jet.lang.resolve.java.structure.*;
028 import org.jetbrains.jet.lang.resolve.name.FqName;
029 import org.jetbrains.jet.lang.resolve.name.Name;
030 import org.jetbrains.jet.lang.types.*;
031 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
032
033 import java.util.*;
034
035 import static org.jetbrains.jet.lang.types.Variance.INVARIANT;
036
037 public class SingleAbstractMethodUtils {
038 private SingleAbstractMethodUtils() {
039 }
040
041 @NotNull
042 public static List<CallableMemberDescriptor> getAbstractMembers(@NotNull JetType type) {
043 List<CallableMemberDescriptor> abstractMembers = new ArrayList<CallableMemberDescriptor>();
044 for (DeclarationDescriptor member : type.getMemberScope().getAllDescriptors()) {
045 if (member instanceof CallableMemberDescriptor && ((CallableMemberDescriptor) member).getModality() == Modality.ABSTRACT) {
046 abstractMembers.add((CallableMemberDescriptor) member);
047 }
048 }
049 return abstractMembers;
050 }
051
052 private static JetType fixProjections(@NotNull JetType functionType) {
053 //removes redundant projection kinds and detects conflicts
054
055 List<TypeParameterDescriptor> typeParameters = functionType.getConstructor().getParameters();
056 List<TypeProjection> arguments = new ArrayList<TypeProjection>(typeParameters.size());
057 for (TypeParameterDescriptor typeParameter : typeParameters) {
058 Variance variance = typeParameter.getVariance();
059 TypeProjection argument = functionType.getArguments().get(typeParameter.getIndex());
060 Variance kind = argument.getProjectionKind();
061 if (kind != INVARIANT && variance != INVARIANT) {
062 if (kind == variance) {
063 arguments.add(new TypeProjectionImpl(argument.getType()));
064 }
065 else {
066 return null;
067 }
068 }
069 else {
070 arguments.add(argument);
071 }
072 }
073 ClassifierDescriptor classifier = functionType.getConstructor().getDeclarationDescriptor();
074 assert classifier instanceof ClassDescriptor : "Not class: " + classifier;
075 return new JetTypeImpl(
076 functionType.getAnnotations(),
077 functionType.getConstructor(),
078 functionType.isNullable(),
079 arguments,
080 ((ClassDescriptor) classifier).getMemberScope(arguments)
081 );
082 }
083
084 @Nullable
085 private static JetType getFunctionTypeForSamType(@NotNull JetType samType) {
086 // e.g. samType == Comparator<String>?
087
088 ClassifierDescriptor classifier = samType.getConstructor().getDeclarationDescriptor();
089 if (classifier instanceof JavaClassDescriptor) {
090 // Function2<T, T, Int>
091 JetType functionTypeDefault = ((JavaClassDescriptor) classifier).getFunctionTypeForSamInterface();
092
093 if (functionTypeDefault != null) {
094 // Function2<String, String, Int>?
095 JetType substitute = TypeSubstitutor.create(samType).substitute(functionTypeDefault, Variance.INVARIANT);
096
097 return substitute == null ? null : fixProjections(TypeUtils.makeNullableAsSpecified(substitute, samType.isNullable()));
098 }
099 }
100 return null;
101 }
102
103 @NotNull
104 public static JetType getFunctionTypeForAbstractMethod(@NotNull FunctionDescriptor function) {
105 JetType returnType = function.getReturnType();
106 assert returnType != null : "function is not initialized: " + function;
107 List<ValueParameterDescriptor> valueParameters = function.getValueParameters();
108 List<JetType> parameterTypes = new ArrayList<JetType>(valueParameters.size());
109 for (ValueParameterDescriptor parameter : valueParameters) {
110 parameterTypes.add(parameter.getType());
111 }
112 return KotlinBuiltIns.getInstance().getFunctionType(
113 Annotations.EMPTY, null, parameterTypes, returnType);
114 }
115
116 private static boolean isSamInterface(@NotNull ClassDescriptor klass) {
117 if (klass.getKind() != ClassKind.TRAIT) {
118 return false;
119 }
120
121 List<CallableMemberDescriptor> abstractMembers = getAbstractMembers(klass.getDefaultType());
122 if (abstractMembers.size() == 1) {
123 CallableMemberDescriptor member = abstractMembers.get(0);
124 if (member instanceof SimpleFunctionDescriptor) {
125 return member.getTypeParameters().isEmpty();
126 }
127 }
128 return false;
129 }
130
131 @NotNull
132 public static SamConstructorDescriptor createSamConstructorFunction(
133 @NotNull ClassOrPackageFragmentDescriptor owner,
134 @NotNull JavaClassDescriptor samInterface
135 ) {
136 assert isSamInterface(samInterface) : samInterface;
137
138 SamConstructorDescriptor result = new SamConstructorDescriptor(owner, samInterface);
139
140 TypeParameters typeParameters = recreateAndInitializeTypeParameters(samInterface.getTypeConstructor().getParameters(), result);
141
142 JetType parameterTypeUnsubstituted = getFunctionTypeForSamType(samInterface.getDefaultType());
143 assert parameterTypeUnsubstituted != null : "couldn't get function type for SAM type " + samInterface.getDefaultType();
144 JetType parameterType = typeParameters.substitutor.substitute(parameterTypeUnsubstituted, Variance.IN_VARIANCE);
145 assert parameterType != null : "couldn't substitute type: " + parameterTypeUnsubstituted +
146 ", substitutor = " + typeParameters.substitutor;
147 ValueParameterDescriptor parameter = new ValueParameterDescriptorImpl(
148 result, null, 0, Annotations.EMPTY, Name.identifier("function"), parameterType, false, null, SourceElement.NO_SOURCE);
149
150 JetType returnType = typeParameters.substitutor.substitute(samInterface.getDefaultType(), Variance.OUT_VARIANCE);
151 assert returnType != null : "couldn't substitute type: " + samInterface.getDefaultType() +
152 ", substitutor = " + typeParameters.substitutor;
153
154 result.initialize(
155 null,
156 null,
157 typeParameters.descriptors,
158 Arrays.asList(parameter),
159 returnType,
160 Modality.FINAL,
161 samInterface.getVisibility()
162 );
163
164 return result;
165 }
166
167 public static boolean isSamType(@NotNull JetType type) {
168 return getFunctionTypeForSamType(type) != null;
169 }
170
171 public static boolean isSamAdapterNecessary(@NotNull FunctionDescriptor fun) {
172 for (ValueParameterDescriptor param : fun.getValueParameters()) {
173 if (isSamType(param.getType())) {
174 return true;
175 }
176 }
177 return false;
178 }
179
180 @NotNull
181 public static SamAdapterDescriptor<JavaMethodDescriptor> createSamAdapterFunction(@NotNull final JavaMethodDescriptor original) {
182 final SamAdapterFunctionDescriptor result = new SamAdapterFunctionDescriptor(original);
183 return initSamAdapter(original, result, new FunctionInitializer() {
184 @Override
185 public void initialize(
186 @NotNull List<TypeParameterDescriptor> typeParameters,
187 @NotNull List<ValueParameterDescriptor> valueParameters,
188 @Nullable JetType returnType
189 ) {
190 result.initialize(
191 null,
192 original.getExpectedThisObject(),
193 typeParameters,
194 valueParameters,
195 returnType,
196 Modality.FINAL,
197 original.getVisibility()
198 );
199 }
200 });
201 }
202
203 @NotNull
204 public static SamAdapterDescriptor<JavaConstructorDescriptor> createSamAdapterConstructor(@NotNull final JavaConstructorDescriptor original) {
205 final SamAdapterConstructorDescriptor result = new SamAdapterConstructorDescriptor(original);
206 return initSamAdapter(original, result, new FunctionInitializer() {
207 @Override
208 public void initialize(
209 @NotNull List<TypeParameterDescriptor> typeParameters,
210 @NotNull List<ValueParameterDescriptor> valueParameters,
211 @Nullable JetType returnType
212 ) {
213 result.initialize(
214 typeParameters,
215 valueParameters,
216 original.getVisibility(),
217 original.getExpectedThisObject() == ReceiverParameterDescriptor.NO_RECEIVER_PARAMETER
218 );
219 }
220 });
221 }
222
223 @NotNull
224 private static <F extends FunctionDescriptor> SamAdapterDescriptor<F> initSamAdapter(
225 @NotNull F original,
226 @NotNull SamAdapterDescriptor<F> adapter,
227 @NotNull FunctionInitializer initializer
228 ) {
229 TypeParameters typeParameters = recreateAndInitializeTypeParameters(original.getTypeParameters(), adapter);
230
231 JetType returnTypeUnsubstituted = original.getReturnType();
232 JetType returnType;
233 if (returnTypeUnsubstituted == null) { // return type may be null for not yet initialized constructors
234 returnType = null;
235 }
236 else {
237 returnType = typeParameters.substitutor.substitute(returnTypeUnsubstituted, Variance.OUT_VARIANCE);
238 assert returnType != null : "couldn't substitute type: " + returnTypeUnsubstituted +
239 ", substitutor = " + typeParameters.substitutor;
240 }
241
242 List<ValueParameterDescriptor> originalValueParameters = original.getValueParameters();
243 List<ValueParameterDescriptor> valueParameters = new ArrayList<ValueParameterDescriptor>(originalValueParameters.size());
244 for (ValueParameterDescriptor originalParam : originalValueParameters) {
245 JetType originalType = originalParam.getType();
246 JetType functionType = getFunctionTypeForSamType(originalType);
247 JetType newTypeUnsubstituted = functionType != null ? functionType : originalType;
248 JetType newType = typeParameters.substitutor.substitute(newTypeUnsubstituted, Variance.IN_VARIANCE);
249 assert newType != null : "couldn't substitute type: " + newTypeUnsubstituted + ", substitutor = " + typeParameters.substitutor;
250
251 ValueParameterDescriptor newParam = new ValueParameterDescriptorImpl(
252 adapter, null, originalParam.getIndex(), originalParam.getAnnotations(),
253 originalParam.getName(), newType, false, null, SourceElement.NO_SOURCE
254 );
255 valueParameters.add(newParam);
256 }
257
258 initializer.initialize(typeParameters.descriptors, valueParameters, returnType);
259
260 return adapter;
261 }
262
263 @NotNull
264 private static TypeParameters recreateAndInitializeTypeParameters(
265 @NotNull List<TypeParameterDescriptor> originalParameters,
266 @Nullable DeclarationDescriptor newOwner
267 ) {
268 Map<TypeParameterDescriptor, TypeParameterDescriptorImpl> traitToFunTypeParameters =
269 DescriptorResolverUtils.recreateTypeParametersAndReturnMapping(originalParameters, newOwner);
270 TypeSubstitutor typeParametersSubstitutor = DescriptorResolverUtils.createSubstitutorForTypeParameters(traitToFunTypeParameters);
271 for (Map.Entry<TypeParameterDescriptor, TypeParameterDescriptorImpl> mapEntry : traitToFunTypeParameters.entrySet()) {
272 TypeParameterDescriptor traitTypeParameter = mapEntry.getKey();
273 TypeParameterDescriptorImpl funTypeParameter = mapEntry.getValue();
274
275 for (JetType upperBound : traitTypeParameter.getUpperBounds()) {
276 JetType upperBoundSubstituted = typeParametersSubstitutor.substitute(upperBound, Variance.INVARIANT);
277 assert upperBoundSubstituted != null : "couldn't substitute type: " + upperBound + ", substitutor = " + typeParametersSubstitutor;
278 funTypeParameter.addUpperBound(upperBoundSubstituted);
279 }
280
281 funTypeParameter.setInitialized();
282 }
283
284 List<TypeParameterDescriptor> typeParameters = new ArrayList<TypeParameterDescriptor>(traitToFunTypeParameters.values());
285 return new TypeParameters(typeParameters, typeParametersSubstitutor);
286 }
287
288 public static boolean isSamInterface(@NotNull JavaClass javaClass) {
289 return getSamInterfaceMethod(javaClass) != null;
290 }
291
292 // Returns null if not SAM interface
293 @Nullable
294 public static JavaMethod getSamInterfaceMethod(@NotNull JavaClass javaClass) {
295 FqName fqName = javaClass.getFqName();
296 if (fqName == null || fqName.firstSegmentIs(KotlinBuiltIns.BUILT_INS_PACKAGE_NAME)) {
297 return null;
298 }
299 if (!javaClass.isInterface() || javaClass.isAnnotationType()) {
300 return null;
301 }
302
303 return findOnlyAbstractMethod(javaClass);
304 }
305
306 @Nullable
307 private static JavaMethod findOnlyAbstractMethod(@NotNull JavaClass javaClass) {
308 OnlyAbstractMethodFinder finder = new OnlyAbstractMethodFinder();
309 if (finder.find(javaClass.getDefaultType())) {
310 return finder.getFoundMethod();
311 }
312 return null;
313 }
314
315 private static class TypeParameters {
316 public final List<TypeParameterDescriptor> descriptors;
317 public final TypeSubstitutor substitutor;
318
319 private TypeParameters(List<TypeParameterDescriptor> descriptors, TypeSubstitutor substitutor) {
320 this.descriptors = descriptors;
321 this.substitutor = substitutor;
322 }
323 }
324
325 private static abstract class FunctionInitializer {
326 public abstract void initialize(
327 @NotNull List<TypeParameterDescriptor> typeParameters,
328 @NotNull List<ValueParameterDescriptor> valueParameters,
329 @Nullable JetType returnType
330 );
331 }
332
333 private static class OnlyAbstractMethodFinder {
334 private static final FqName OBJECT_FQ_NAME = new FqName("java.lang.Object");
335
336 private JavaMethod foundMethod;
337 private JavaTypeSubstitutor foundClassSubstitutor;
338
339 private boolean find(@NotNull JavaClassifierType classifierType) {
340 JavaTypeSubstitutor classSubstitutor = classifierType.getSubstitutor();
341 JavaClassifier classifier = classifierType.getClassifier();
342 if (classifier == null) {
343 return false; // can't resolve class -> not a SAM interface
344 }
345 assert classifier instanceof JavaClass : "Classifier should be a class here: " + classifier;
346 JavaClass javaClass = (JavaClass) classifier;
347 if (OBJECT_FQ_NAME.equals(javaClass.getFqName())) {
348 return true;
349 }
350 for (JavaMethod method : javaClass.getMethods()) {
351 if (DescriptorResolverUtils.isObjectMethod(method)) { // e.g., ignore toString() declared in interface
352 continue;
353 }
354 if (!method.getTypeParameters().isEmpty()) {
355 return false; // if interface has generic methods, it is not a SAM interface
356 }
357
358 if (foundMethod == null) {
359 foundMethod = method;
360 foundClassSubstitutor = classSubstitutor;
361 continue;
362 }
363
364 if (!areSignaturesErasureEqual(method, classSubstitutor, foundMethod, foundClassSubstitutor)) {
365 return false; // different signatures
366 }
367 }
368
369 for (JavaClassifierType t : classifierType.getSupertypes()) {
370 if (!find(t)) {
371 return false;
372 }
373 }
374
375 return true;
376 }
377
378 /**
379 * @see com.intellij.psi.util.MethodSignatureUtil#areSignaturesErasureEqual
380 */
381 private static boolean areSignaturesErasureEqual(
382 @NotNull JavaMethod method1,
383 @NotNull JavaTypeSubstitutor substitutor1,
384 @NotNull JavaMethod method2,
385 @NotNull JavaTypeSubstitutor substitutor2
386 ) {
387 if (method1.isConstructor() != method2.isConstructor()) return false;
388 if (!method1.isConstructor() && !method1.getName().equals(method2.getName())) return false;
389
390 if (method1.isVararg() != method2.isVararg()) return false;
391
392 Collection<JavaValueParameter> parameters1 = method1.getValueParameters();
393 Collection<JavaValueParameter> parameters2 = method2.getValueParameters();
394 if (parameters1.size() != parameters2.size()) return false;
395
396 for (Iterator<JavaValueParameter> it1 = parameters1.iterator(), it2 = parameters2.iterator(); it1.hasNext(); ) {
397 JavaType type1 = DescriptorResolverUtils.erasure(substitutor1.substitute(it1.next().getType()), substitutor1);
398 JavaType type2 = DescriptorResolverUtils.erasure(substitutor2.substitute(it2.next().getType()), substitutor2);
399 if (!(type1 == null ? type2 == null : type1.equals(type2))) return false;
400 }
401
402 return true;
403 }
404
405 @Nullable
406 private JavaMethod getFoundMethod() {
407 return foundMethod;
408 }
409 }
410 }