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