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