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.resolver;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
022    import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
023    import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
024    import org.jetbrains.jet.lang.resolve.java.mapping.JavaToKotlinClassMap;
025    import org.jetbrains.jet.lang.resolve.java.structure.*;
026    import org.jetbrains.jet.lang.resolve.name.FqName;
027    import org.jetbrains.jet.lang.types.*;
028    import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
029    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
030    
031    import javax.inject.Inject;
032    import java.util.*;
033    
034    import static org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule.INCLUDE_KOTLIN_SOURCES;
035    import static org.jetbrains.jet.lang.resolve.java.resolver.TypeUsage.*;
036    import static org.jetbrains.jet.lang.types.Variance.*;
037    
038    public class JavaTypeTransformer {
039        private JavaClassResolver classResolver;
040    
041        @Inject
042        public void setClassResolver(JavaClassResolver classResolver) {
043            this.classResolver = classResolver;
044        }
045    
046        @NotNull
047        private TypeProjection transformToTypeProjection(
048                @NotNull JavaType type,
049                @NotNull TypeParameterDescriptor typeParameterDescriptor,
050                @NotNull TypeVariableResolver typeVariableResolver,
051                @NotNull TypeUsage howThisTypeIsUsed
052        ) {
053            if (!(type instanceof JavaWildcardType)) {
054                return new TypeProjectionImpl(transformToType(type, howThisTypeIsUsed, typeVariableResolver));
055            }
056    
057            JavaWildcardType wildcardType = (JavaWildcardType) type;
058            JavaType bound = wildcardType.getBound();
059            if (bound == null) {
060                return SubstitutionUtils.makeStarProjection(typeParameterDescriptor);
061            }
062    
063            Variance variance = wildcardType.isExtends() ? OUT_VARIANCE : IN_VARIANCE;
064    
065            return new TypeProjectionImpl(variance, transformToType(bound, UPPER_BOUND, typeVariableResolver));
066        }
067    
068        @NotNull
069        public JetType transformToType(@NotNull JavaType type, @NotNull TypeVariableResolver typeVariableResolver) {
070            return transformToType(type, TypeUsage.MEMBER_SIGNATURE_INVARIANT, typeVariableResolver);
071        }
072    
073        @NotNull
074        public JetType transformToType(
075                @NotNull JavaType type,
076                @NotNull TypeUsage howThisTypeIsUsed,
077                @NotNull TypeVariableResolver typeVariableResolver
078        ) {
079            if (type instanceof JavaClassifierType) {
080                JavaClassifierType classifierType = (JavaClassifierType) type;
081                JetType jetType = transformClassifierType(classifierType, howThisTypeIsUsed, typeVariableResolver);
082                if (jetType == null) {
083                    return ErrorUtils.createErrorType("Unresolved java classifier: " + classifierType.getPresentableText());
084                }
085                return jetType;
086            }
087            else if (type instanceof JavaPrimitiveType) {
088                String canonicalText = ((JavaPrimitiveType) type).getCanonicalText();
089                JetType jetType = JavaToKotlinClassMap.getInstance().mapPrimitiveKotlinClass(canonicalText);
090                assert jetType != null : "Primitive type is not found: " + canonicalText;
091                return jetType;
092            }
093            else if (type instanceof JavaArrayType) {
094                return transformArrayType((JavaArrayType) type, howThisTypeIsUsed, typeVariableResolver, false);
095            }
096            else {
097                throw new UnsupportedOperationException("Unsupported type: " + type); // TODO
098            }
099        }
100    
101        @Nullable
102        private JetType transformClassifierType(
103                @NotNull JavaClassifierType classifierType,
104                @NotNull TypeUsage howThisTypeIsUsed,
105                @NotNull TypeVariableResolver typeVariableResolver
106        ) {
107            JavaClassifier javaClassifier = classifierType.getClassifier();
108            if (javaClassifier == null) {
109                return null;
110            }
111            if (javaClassifier instanceof JavaTypeParameter) {
112                return transformTypeParameter((JavaTypeParameter) javaClassifier, howThisTypeIsUsed, typeVariableResolver);
113            }
114            else if (javaClassifier instanceof JavaClass) {
115                FqName fqName = ((JavaClass) javaClassifier).getFqName();
116                assert fqName != null : "Class type should have a FQ name: " + javaClassifier;
117                return transformClassType(fqName, classifierType, howThisTypeIsUsed, typeVariableResolver);
118            }
119            else {
120                throw new UnsupportedOperationException("Unsupported classifier: " + javaClassifier);
121            }
122        }
123    
124        @Nullable
125        private JetType transformTypeParameter(
126                @NotNull JavaTypeParameter typeParameter,
127                @NotNull TypeUsage howThisTypeIsUsed,
128                @NotNull TypeVariableResolver typeVariableResolver
129        ) {
130            JavaTypeParameterListOwner owner = typeParameter.getOwner();
131            if (owner instanceof JavaMethod && ((JavaMethod) owner).isConstructor()) {
132                Set<JetType> supertypesJet = new HashSet<JetType>();
133                for (JavaClassifierType supertype : typeParameter.getUpperBounds()) {
134                    supertypesJet.add(transformToType(supertype, UPPER_BOUND, typeVariableResolver));
135                }
136                return TypeUtils.intersect(JetTypeChecker.INSTANCE, supertypesJet);
137            }
138    
139            TypeParameterDescriptor descriptor = typeVariableResolver.getTypeVariable(typeParameter.getName());
140            if (descriptor == null) return null;
141    
142            // In Java: ArrayList<T>
143            // In Kotlin: ArrayList<T>, not ArrayList<T?>
144            // nullability will be taken care of in individual member signatures
145            boolean nullable = !EnumSet.of(TYPE_ARGUMENT, UPPER_BOUND, SUPERTYPE_ARGUMENT).contains(howThisTypeIsUsed);
146    
147            return TypeUtils.makeNullableIfNeeded(descriptor.getDefaultType(), nullable);
148        }
149    
150        @Nullable
151        private JetType transformClassType(
152                @NotNull FqName fqName,
153                @NotNull JavaClassifierType classifierType,
154                @NotNull TypeUsage howThisTypeIsUsed,
155                @NotNull TypeVariableResolver typeVariableResolver
156        ) {
157            // 'L extends List<T>' in Java is a List<T> in Kotlin, not a List<T?>
158            boolean nullable = !EnumSet.of(TYPE_ARGUMENT, SUPERTYPE_ARGUMENT, SUPERTYPE).contains(howThisTypeIsUsed);
159    
160            ClassDescriptor classData = JavaToKotlinClassMap.getInstance().mapKotlinClass(fqName, howThisTypeIsUsed);
161    
162            if (classData == null) {
163                classData = classResolver.resolveClass(fqName, INCLUDE_KOTLIN_SOURCES);
164            }
165            if (classData == null) {
166                return null;
167            }
168    
169            List<TypeProjection> arguments = new ArrayList<TypeProjection>();
170            List<TypeParameterDescriptor> parameters = classData.getTypeConstructor().getParameters();
171            if (isRaw(classifierType, !parameters.isEmpty())) {
172                for (TypeParameterDescriptor parameter : parameters) {
173                    // not making a star projection because of this case:
174                    // Java:
175                    // class C<T extends C> {}
176                    // The upper bound is raw here, and we can't compute the projection: it would be infinite:
177                    // C<*> = C<out C<out C<...>>>
178                    // this way we loose some type information, even when the case is not so bad, but it doesn't seem to matter
179    
180                    // projections are not allowed in immediate arguments of supertypes
181                    Variance projectionKind = parameter.getVariance() == OUT_VARIANCE || howThisTypeIsUsed == SUPERTYPE
182                                              ? INVARIANT
183                                              : OUT_VARIANCE;
184                    arguments.add(new TypeProjectionImpl(projectionKind, KotlinBuiltIns.getInstance().getNullableAnyType()));
185                }
186            }
187            else {
188                List<JavaType> javaTypeArguments = classifierType.getTypeArguments();
189    
190                if (parameters.size() != javaTypeArguments.size()) {
191                    // Most of the time this means there is an error in the Java code
192                    for (TypeParameterDescriptor parameter : parameters) {
193                        arguments.add(new TypeProjectionImpl(ErrorUtils.createErrorType(parameter.getName().asString())));
194                    }
195                }
196                else {
197                    for (int i = 0, size = javaTypeArguments.size(); i < size; i++) {
198                        JavaType typeArgument = javaTypeArguments.get(i);
199                        TypeParameterDescriptor typeParameterDescriptor = parameters.get(i);
200    
201                        TypeUsage howTheProjectionIsUsed = howThisTypeIsUsed == SUPERTYPE ? SUPERTYPE_ARGUMENT : TYPE_ARGUMENT;
202                        TypeProjection typeProjection =
203                                transformToTypeProjection(typeArgument, typeParameterDescriptor, typeVariableResolver, howTheProjectionIsUsed);
204    
205                        if (typeProjection.getProjectionKind() == typeParameterDescriptor.getVariance()) {
206                            // remove redundant 'out' and 'in'
207                            arguments.add(new TypeProjectionImpl(INVARIANT, typeProjection.getType()));
208                        }
209                        else {
210                            arguments.add(typeProjection);
211                        }
212                    }
213                }
214            }
215    
216            return new JetTypeImpl(
217                    Collections.<AnnotationDescriptor>emptyList(),
218                    classData.getTypeConstructor(),
219                    nullable,
220                    arguments,
221                    classData.getMemberScope(arguments));
222        }
223    
224        @NotNull
225        private JetType transformArrayType(
226                @NotNull JavaArrayType arrayType,
227                @NotNull TypeUsage howThisTypeIsUsed,
228                @NotNull TypeVariableResolver typeVariableResolver,
229                boolean vararg
230        ) {
231            JavaType componentType = arrayType.getComponentType();
232            if (componentType instanceof JavaPrimitiveType) {
233                JetType jetType = JavaToKotlinClassMap.getInstance().mapPrimitiveKotlinClass(
234                        "[" + ((JavaPrimitiveType) componentType).getCanonicalText());
235                if (jetType != null) {
236                    return TypeUtils.makeNullable(jetType);
237                }
238            }
239    
240            Variance projectionKind = arrayElementTypeProjectionKind(howThisTypeIsUsed, vararg);
241            TypeUsage howArgumentTypeIsUsed = vararg ? MEMBER_SIGNATURE_CONTRAVARIANT : TYPE_ARGUMENT;
242    
243            JetType type = transformToType(componentType, howArgumentTypeIsUsed, typeVariableResolver);
244            return TypeUtils.makeNullable(KotlinBuiltIns.getInstance().getArrayType(projectionKind, type));
245        }
246    
247        @NotNull
248        private static Variance arrayElementTypeProjectionKind(@NotNull TypeUsage howThisTypeIsUsed, boolean vararg) {
249            if (howThisTypeIsUsed == MEMBER_SIGNATURE_CONTRAVARIANT && !vararg) {
250                return OUT_VARIANCE;
251            }
252            else {
253                return INVARIANT;
254            }
255        }
256    
257        @NotNull
258        public JetType transformVarargType(
259                @NotNull JavaArrayType type,
260                @NotNull TypeUsage howThisTypeIsUsed,
261                @NotNull TypeVariableResolver typeVariableResolver
262        ) {
263            return transformArrayType(type, howThisTypeIsUsed, typeVariableResolver, true);
264        }
265    
266        private static boolean isRaw(@NotNull JavaClassifierType classifierType, boolean argumentsExpected) {
267            // The second option is needed because sometimes we get weird versions of JDK classes in the class path,
268            // such as collections with no generics, so the Java types are not raw, formally, but they don't match with
269            // their Kotlin analogs, so we treat them as raw to avoid exceptions
270            return classifierType.isRaw() || argumentsExpected && classifierType.getTypeArguments().isEmpty();
271        }
272    }