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