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