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
017package org.jetbrains.jet.lang.resolve.java;
018
019import com.google.common.collect.Lists;
020import com.google.common.collect.Sets;
021import com.intellij.openapi.diagnostic.Logger;
022import com.intellij.psi.*;
023import org.jetbrains.annotations.NotNull;
024import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
025import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
026import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
027import org.jetbrains.jet.lang.resolve.java.resolver.JavaAnnotationResolver;
028import org.jetbrains.jet.lang.resolve.name.FqName;
029import org.jetbrains.jet.lang.types.*;
030import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
031import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
032import org.jetbrains.jet.rt.signature.JetSignatureReader;
033
034import javax.inject.Inject;
035import java.util.Collections;
036import java.util.EnumSet;
037import java.util.List;
038import java.util.Set;
039
040import static org.jetbrains.jet.lang.resolve.java.TypeUsage.*;
041
042public class JavaTypeTransformer {
043
044    private static final Logger LOG = Logger.getInstance(JavaTypeTransformer.class);
045
046    private JavaSemanticServices javaSemanticServices;
047    private JavaDescriptorResolver resolver;
048    @Inject
049    public void setJavaSemanticServices(JavaSemanticServices javaSemanticServices) {
050        this.javaSemanticServices = javaSemanticServices;
051    }
052
053    @Inject
054    public void setResolver(JavaDescriptorResolver resolver) {
055        this.resolver = resolver;
056    }
057
058    @NotNull
059    private TypeProjection transformToTypeProjection(@NotNull PsiType javaType,
060            @NotNull final TypeParameterDescriptor typeParameterDescriptor,
061            @NotNull final TypeVariableResolver typeVariableByPsiResolver,
062            @NotNull final TypeUsage howThisTypeIsUsed
063    ) {
064        TypeProjection result = javaType.accept(new PsiTypeVisitor<TypeProjection>() {
065
066            @Override
067            public TypeProjection visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) {
068                throw new UnsupportedOperationException(); // TODO
069            }
070
071            @Override
072            public TypeProjection visitWildcardType(PsiWildcardType wildcardType) {
073                if (!wildcardType.isBounded()) {
074                    return SubstitutionUtils.makeStarProjection(typeParameterDescriptor);
075                }
076                Variance variance = wildcardType.isExtends() ? Variance.OUT_VARIANCE : Variance.IN_VARIANCE;
077
078                PsiType bound = wildcardType.getBound();
079                assert bound != null;
080                return new TypeProjection(variance, transformToType(bound, UPPER_BOUND, typeVariableByPsiResolver));
081            }
082
083            @Override
084            public TypeProjection visitType(PsiType type) {
085                return new TypeProjection(transformToType(type, howThisTypeIsUsed, typeVariableByPsiResolver));
086            }
087        });
088        return result;
089    }
090
091    @NotNull
092    public JetType transformToType(@NotNull String kotlinSignature, TypeVariableResolver typeVariableResolver) {
093        final JetType[] r = new JetType[1];
094        JetTypeJetSignatureReader reader = new JetTypeJetSignatureReader(javaSemanticServices, KotlinBuiltIns.getInstance(), typeVariableResolver) {
095            @Override
096            protected void done(@NotNull JetType jetType) {
097                r[0] = jetType;
098            }
099        };
100        new JetSignatureReader(kotlinSignature).acceptType(reader);
101        return r[0];
102    }
103
104    @NotNull
105    public JetType transformToType(@NotNull PsiType javaType,
106                                   @NotNull TypeVariableResolver typeVariableResolver) {
107        return transformToType(javaType, TypeUsage.MEMBER_SIGNATURE_INVARIANT, typeVariableResolver);
108    }
109
110    @NotNull
111    public JetType transformToType(@NotNull PsiType javaType, @NotNull final TypeUsage howThisTypeIsUsed,
112            @NotNull final TypeVariableResolver typeVariableResolver) {
113        JetType result = javaType.accept(new PsiTypeVisitor<JetType>() {
114            @Override
115            public JetType visitClassType(PsiClassType classType) {
116                PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics();
117                PsiClass psiClass = classResolveResult.getElement();
118                if (psiClass == null) {
119                    return ErrorUtils.createErrorType("Unresolved java class: " + classType.getPresentableText());
120                }
121
122                if (psiClass instanceof PsiTypeParameter) {
123                    PsiTypeParameter typeParameter = (PsiTypeParameter) psiClass;
124
125                    PsiTypeParameterListOwner typeParameterListOwner = typeParameter.getOwner();
126                    if (typeParameterListOwner instanceof PsiMethod) {
127                        PsiMethod psiMethod = (PsiMethod) typeParameterListOwner;
128                        if (psiMethod.isConstructor()) {
129                            Set<JetType> supertypesJet = Sets.newHashSet();
130                            for (PsiClassType supertype : typeParameter.getExtendsListTypes()) {
131                                supertypesJet.add(transformToType(supertype, UPPER_BOUND, typeVariableResolver));
132                            }
133                            return TypeUtils.intersect(JetTypeChecker.INSTANCE, supertypesJet);
134                        }
135                    }
136
137                    TypeParameterDescriptor typeParameterDescriptor = typeVariableResolver.getTypeVariable(typeParameter.getName());
138
139                    // In Java: ArrayList<T>
140                    // In Kotlin: ArrayList<T>, not ArrayList<T?>
141                    // nullability will be taken care of in individual member signatures
142                    boolean nullable = !EnumSet.of(TYPE_ARGUMENT, UPPER_BOUND, SUPERTYPE_ARGUMENT).contains(howThisTypeIsUsed);
143                    if (nullable) {
144                        return TypeUtils.makeNullable(typeParameterDescriptor.getDefaultType());
145                    }
146                    else {
147                        return typeParameterDescriptor.getDefaultType();
148                    }
149                }
150                else {
151                    // 'L extends List<T>' in Java is a List<T> in Kotlin, not a List<T?>
152                    boolean nullable = !EnumSet.of(TYPE_ARGUMENT, SUPERTYPE_ARGUMENT, SUPERTYPE).contains(howThisTypeIsUsed);
153
154                    ClassDescriptor classData = JavaToKotlinClassMap.getInstance().mapKotlinClass(new FqName(psiClass.getQualifiedName()),
155                                                                                                    howThisTypeIsUsed);
156
157                    if (classData == null) {
158                        classData = resolver.resolveClass(new FqName(psiClass.getQualifiedName()), DescriptorSearchRule.INCLUDE_KOTLIN);
159                    }
160                    if (classData == null) {
161                        return ErrorUtils.createErrorType("Unresolved java class: " + classType.getPresentableText());
162                    }
163
164                    List<TypeProjection> arguments = Lists.newArrayList();
165                    List<TypeParameterDescriptor> parameters = classData.getTypeConstructor().getParameters();
166                    if (isRaw(classType, !parameters.isEmpty())) {
167                        for (TypeParameterDescriptor parameter : parameters) {
168                            TypeProjection starProjection = SubstitutionUtils.makeStarProjection(parameter);
169                            if (howThisTypeIsUsed == SUPERTYPE) {
170                                // projections are not allowed in immediate arguments of supertypes
171                                arguments.add(new TypeProjection(starProjection.getType()));
172                            }
173                            else {
174                                arguments.add(starProjection);
175                            }
176                        }
177                    }
178                    else {
179                        PsiType[] psiArguments = classType.getParameters();
180                        
181                        if (parameters.size() != psiArguments.length) {
182                            // Most of the time this means there is an error in the Java code
183                            LOG.warn("parameters = " + parameters.size() + ", actual arguments = " + psiArguments.length +
184                                     " in " + classType.getPresentableText() + "\n PsiClass: \n" + psiClass.getText());
185
186                            for (TypeParameterDescriptor parameter : parameters) {
187                                arguments.add(new TypeProjection(ErrorUtils.createErrorType(parameter.getName().asString())));
188                            }
189                        }
190                        else {
191                            for (int i = 0; i < parameters.size(); i++) {
192                                PsiType psiArgument = psiArguments[i];
193                                TypeParameterDescriptor typeParameterDescriptor = parameters.get(i);
194
195                                TypeUsage howTheProjectionIsUsed = howThisTypeIsUsed == SUPERTYPE ? SUPERTYPE_ARGUMENT : TYPE_ARGUMENT;
196                                TypeProjection typeProjection = transformToTypeProjection(
197                                        psiArgument, typeParameterDescriptor, typeVariableResolver, howTheProjectionIsUsed);
198
199                                if (typeProjection.getProjectionKind() == typeParameterDescriptor.getVariance()) {
200                                    // remove redundant 'out' and 'in'
201                                    arguments.add(new TypeProjection(Variance.INVARIANT, typeProjection.getType()));
202                                }
203                                else {
204                                    arguments.add(typeProjection);
205                                }
206                            }
207                        }
208                    }
209
210                    return new JetTypeImpl(
211                            Collections.<AnnotationDescriptor>emptyList(),
212                            classData.getTypeConstructor(),
213                            nullable,
214                            arguments,
215                            classData.getMemberScope(arguments));
216                }
217            }
218
219            @Override
220            public JetType visitPrimitiveType(PsiPrimitiveType primitiveType) {
221                String canonicalText = primitiveType.getCanonicalText();
222                JetType type = JavaToKotlinClassMap.getInstance().mapPrimitiveKotlinClass(canonicalText);
223                assert type != null : canonicalText;
224                return type;
225            }
226
227            @Override
228            public JetType visitArrayType(PsiArrayType arrayType) {
229                PsiType componentType = arrayType.getComponentType();
230                if (componentType instanceof PsiPrimitiveType) {
231                    JetType jetType = JavaToKotlinClassMap.getInstance().mapPrimitiveKotlinClass("[" + componentType.getCanonicalText());
232                    if (jetType != null)
233                        return TypeUtils.makeNullable(jetType);
234                }
235
236                boolean vararg = arrayType instanceof PsiEllipsisType;
237
238                Variance projectionKind = arrayElementTypeProjectionKind(vararg);
239                TypeUsage howArgumentTypeIsUsed = vararg ? MEMBER_SIGNATURE_CONTRAVARIANT : TYPE_ARGUMENT;
240
241                JetType type = transformToType(componentType, howArgumentTypeIsUsed, typeVariableResolver);
242                return TypeUtils.makeNullable(KotlinBuiltIns.getInstance().getArrayType(projectionKind, type));
243            }
244
245            private Variance arrayElementTypeProjectionKind(boolean vararg) {
246                Variance variance;
247                if (howThisTypeIsUsed == MEMBER_SIGNATURE_CONTRAVARIANT && !vararg) {
248                    variance = Variance.OUT_VARIANCE;
249                }
250                else {
251                    variance = Variance.INVARIANT;
252                }
253                return variance;
254            }
255
256            @Override
257            public JetType visitType(PsiType type) {
258                throw new UnsupportedOperationException("Unsupported type: " + type.getPresentableText()); // TODO
259            }
260        });
261        return result;
262    }
263
264    private static boolean isRaw(@NotNull PsiClassType classType, boolean argumentsExpected) {
265        // The second option is needed because sometimes we get weird versions of JDK classes in the class path,
266        // such as collections with no generics, so the Java types are not raw, formally, but they don't match with
267        // their Kotlin analogs, so we treat them as raw to avoid exceptions
268        return classType.isRaw() || argumentsExpected && classType.getParameterCount() == 0;
269    }
270
271    public static TypeUsage adjustTypeUsageWithMutabilityAnnotations(PsiModifierListOwner owner, TypeUsage originalTypeUsage) {
272        // Overrides type usage in method signature depending on mutability annotation present
273        EnumSet<TypeUsage> signatureTypeUsages =
274                EnumSet.of(TypeUsage.MEMBER_SIGNATURE_COVARIANT, TypeUsage.MEMBER_SIGNATURE_CONTRAVARIANT, TypeUsage.MEMBER_SIGNATURE_INVARIANT);
275        if (!signatureTypeUsages.contains(originalTypeUsage)) {
276            return originalTypeUsage;
277        }
278        if (JavaAnnotationResolver.findAnnotationWithExternal(owner, JvmAbi.JETBRAINS_MUTABLE_ANNOTATION.getFqName().asString()) != null) {
279            return TypeUsage.MEMBER_SIGNATURE_COVARIANT;
280        }
281        if (JavaAnnotationResolver.findAnnotationWithExternal(owner, JvmAbi.JETBRAINS_READONLY_ANNOTATION.getFqName().asString()) != null) {
282            return TypeUsage.MEMBER_SIGNATURE_CONTRAVARIANT;
283        }
284        return originalTypeUsage;
285    }
286}