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