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