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    }