001    /*
002     * Copyright 2010-2015 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.kotlin.load.java.structure.impl;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.kotlin.load.java.structure.*;
022    
023    import java.util.Collection;
024    import java.util.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    
028    public class JavaTypeSubstitutorImpl implements JavaTypeSubstitutor {
029        private final Map<JavaTypeParameter, JavaType> substitutionMap;
030    
031        public JavaTypeSubstitutorImpl(@NotNull Map<JavaTypeParameter, JavaType> substitutionMap) {
032            this.substitutionMap = substitutionMap;
033        }
034    
035        @NotNull
036        @Override
037        public JavaType substitute(@NotNull JavaType type) {
038            JavaType substitutedType = substituteInternal(type);
039            return substitutedType != null ? substitutedType : correctSubstitutionForRawType(type);
040        }
041    
042        @NotNull
043        // In case of raw type we get substitution map like T -> null,
044        // in this case we should substitute upper bound of T or,
045        // if it does not exist, return java.lang.Object
046        private JavaType correctSubstitutionForRawType(@NotNull JavaType original) {
047            if (original instanceof JavaClassifierType) {
048                JavaClassifier classifier = ((JavaClassifierType) original).getClassifier();
049                if (classifier instanceof JavaTypeParameter) {
050                    return rawTypeForTypeParameter((JavaTypeParameter) classifier);
051                }
052            }
053    
054            return original;
055        }
056    
057        @Nullable
058        private JavaType substituteInternal(@NotNull JavaType type) {
059            if (type instanceof JavaClassifierType) {
060                JavaClassifierType classifierType = (JavaClassifierType) type;
061                JavaClassifier classifier = classifierType.getClassifier();
062    
063                if (classifier instanceof JavaTypeParameter) {
064                    return substitute((JavaTypeParameter) classifier);
065                }
066                else if (classifier instanceof JavaClass) {
067                    JavaClass javaClass = (JavaClass) classifier;
068                    Map<JavaTypeParameter, JavaType> substMap = new HashMap<JavaTypeParameter, JavaType>();
069                    processClass(javaClass, classifierType.getSubstitutor(), substMap);
070    
071                    return javaClass.createImmediateType(new JavaTypeSubstitutorImpl(substMap));
072                }
073    
074                return type;
075            }
076            else if (type instanceof JavaPrimitiveType) {
077                return type;
078            }
079            else if (type instanceof JavaArrayType) {
080                JavaType componentType = ((JavaArrayType) type).getComponentType();
081                JavaType substitutedComponentType = substitute(componentType);
082                if (substitutedComponentType == componentType) return type;
083    
084                return substitutedComponentType.createArrayType();
085            }
086            else if (type instanceof JavaWildcardType) {
087                return substituteWildcardType((JavaWildcardType) type);
088            }
089    
090            return type;
091        }
092    
093        private void processClass(@NotNull JavaClass javaClass, @NotNull JavaTypeSubstitutor substitutor, @NotNull Map<JavaTypeParameter, JavaType> substMap) {
094            List<JavaTypeParameter> typeParameters = javaClass.getTypeParameters();
095            for (JavaTypeParameter typeParameter : typeParameters) {
096                JavaType substitutedParam = substitutor.substitute(typeParameter);
097                if (substitutedParam == null) {
098                    substMap.put(typeParameter, null);
099                }
100                else {
101                    substMap.put(typeParameter, substituteInternal(substitutedParam));
102                }
103            }
104    
105            if (javaClass.isStatic()) {
106                return;
107            }
108    
109            JavaClass outerClass = javaClass.getOuterClass();
110            if (outerClass != null) {
111                processClass(outerClass, substitutor, substMap);
112            }
113        }
114    
115        @Nullable
116        private JavaType substituteWildcardType(@NotNull JavaWildcardType wildcardType) {
117            JavaType bound = wildcardType.getBound();
118            if (bound == null) {
119                return wildcardType;
120            }
121    
122            JavaType newBound = substituteInternal(bound);
123            if (newBound == null) {
124                // This can be in case of substitution wildcard to raw type
125                return null;
126            }
127    
128            return rebound(wildcardType, newBound);
129        }
130    
131        @NotNull
132        private static JavaWildcardType rebound(@NotNull JavaWildcardType type, @NotNull JavaType newBound) {
133            if (type.getTypeProvider().createJavaLangObjectType().equals(newBound)) {
134                return type.getTypeProvider().createUnboundedWildcard();
135            }
136    
137            if (type.isExtends()) {
138                return type.getTypeProvider().createUpperBoundWildcard(newBound);
139            }
140            else {
141                return type.getTypeProvider().createLowerBoundWildcard(newBound);
142            }
143        }
144    
145        @NotNull
146        private JavaType rawTypeForTypeParameter(@NotNull JavaTypeParameter typeParameter) {
147            Collection<JavaClassifierType> bounds = typeParameter.getUpperBounds();
148            if (!bounds.isEmpty()) {
149                return substitute(bounds.iterator().next());
150            }
151    
152            return typeParameter.getTypeProvider().createJavaLangObjectType();
153        }
154    
155        @Override
156        @Nullable
157        public JavaType substitute(@NotNull JavaTypeParameter typeParameter) {
158            if (substitutionMap.containsKey(typeParameter)) {
159                return substitutionMap.get(typeParameter);
160            }
161    
162            return typeParameter.getType();
163        }
164    
165        @Override
166        @NotNull
167        public Map<JavaTypeParameter, JavaType> getSubstitutionMap() {
168            return substitutionMap;
169        }
170    
171        @Override
172        public int hashCode() {
173            return substitutionMap.hashCode();
174        }
175    
176        @Override
177        public boolean equals(Object obj) {
178            return obj instanceof JavaTypeSubstitutorImpl && substitutionMap.equals(((JavaTypeSubstitutorImpl) obj).substitutionMap);
179        }
180    
181        @Override
182        public String toString() {
183            return getClass().getSimpleName() + ": " + substitutionMap;
184        }
185    }