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 kotlin.Function1;
020    import kotlin.Unit;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.descriptors.*;
024    import org.jetbrains.jet.lang.descriptors.impl.TypeParameterDescriptorImpl;
025    import org.jetbrains.jet.lang.resolve.OverridingUtil;
026    import org.jetbrains.jet.lang.resolve.java.sam.SingleAbstractMethodUtils;
027    import org.jetbrains.jet.lang.resolve.java.structure.*;
028    import org.jetbrains.jet.lang.resolve.name.FqName;
029    import org.jetbrains.jet.lang.resolve.name.Name;
030    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
031    import org.jetbrains.jet.lang.types.TypeConstructor;
032    import org.jetbrains.jet.lang.types.TypeProjection;
033    import org.jetbrains.jet.lang.types.TypeProjectionImpl;
034    import org.jetbrains.jet.lang.types.TypeSubstitutor;
035    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
036    
037    import java.util.*;
038    
039    public final class DescriptorResolverUtils {
040        public static final FqName OBJECT_FQ_NAME = new FqName("java.lang.Object");
041    
042        private DescriptorResolverUtils() {
043        }
044    
045        @NotNull
046        public static <D extends CallableMemberDescriptor> Collection<D> resolveOverrides(
047                @NotNull Name name,
048                @NotNull Collection<D> membersFromSupertypes,
049                @NotNull Collection<D> membersFromCurrent,
050                @NotNull ClassDescriptor classDescriptor,
051                @NotNull final ErrorReporter errorReporter
052        ) {
053            final Set<D> result = new HashSet<D>();
054    
055            OverridingUtil.generateOverridesInFunctionGroup(
056                    name, membersFromSupertypes, membersFromCurrent, classDescriptor,
057                    new OverridingUtil.DescriptorSink() {
058                        @Override
059                        @SuppressWarnings("unchecked")
060                        public void addToScope(@NotNull CallableMemberDescriptor fakeOverride) {
061                            OverridingUtil.resolveUnknownVisibilityForMember(fakeOverride, new Function1<CallableMemberDescriptor, Unit>() {
062                                @Override
063                                public Unit invoke(@NotNull CallableMemberDescriptor descriptor) {
064                                    errorReporter.reportCannotInferVisibility(descriptor);
065                                    return Unit.VALUE;
066                                }
067                            });
068                            result.add((D) fakeOverride);
069                        }
070    
071                        @Override
072                        public void conflict(@NotNull CallableMemberDescriptor fromSuper, @NotNull CallableMemberDescriptor fromCurrent) {
073                            // nop
074                        }
075                    }
076            );
077    
078            return result;
079        }
080    
081        @Nullable
082        public static ValueParameterDescriptor getAnnotationParameterByName(@NotNull Name name, @NotNull ClassDescriptor annotationClass) {
083            Collection<ConstructorDescriptor> constructors = annotationClass.getConstructors();
084            assert constructors.size() == 1 : "Annotation class descriptor must have only one constructor";
085    
086            for (ValueParameterDescriptor parameter : constructors.iterator().next().getValueParameters()) {
087                if (parameter.getName().equals(name)) {
088                    return parameter;
089                }
090            }
091    
092            return null;
093        }
094    
095        /**
096         * @return true if {@code method} is a static method of enum class, which is to be put into its class object (and not into the
097         *         corresponding package). This applies to values() and valueOf(String) methods
098         */
099        public static boolean shouldBeInEnumClassObject(@NotNull JavaMethod method) {
100            if (!method.getContainingClass().isEnum()) return false;
101    
102            String name = method.getName().asString();
103            if (name.equals("values")) {
104                return method.getValueParameters().isEmpty();
105            }
106            else if (name.equals("valueOf")) {
107                return isMethodWithOneParameterWithFqName(method, "java.lang.String");
108            }
109            return false;
110        }
111    
112        public static boolean isObjectMethodInInterface(@NotNull JavaMember member) {
113            return member.getContainingClass().isInterface() && member instanceof JavaMethod && isObjectMethod((JavaMethod) member);
114        }
115    
116        public static boolean isObjectMethod(@NotNull JavaMethod method) {
117            String name = method.getName().asString();
118            if (name.equals("toString") || name.equals("hashCode")) {
119                return method.getValueParameters().isEmpty();
120            }
121            else if (name.equals("equals")) {
122                return isMethodWithOneParameterWithFqName(method, "java.lang.Object");
123            }
124            return false;
125        }
126    
127        private static boolean isMethodWithOneParameterWithFqName(@NotNull JavaMethod method, @NotNull String fqName) {
128            List<JavaValueParameter> parameters = method.getValueParameters();
129            if (parameters.size() == 1) {
130                JavaType type = parameters.get(0).getType();
131                if (type instanceof JavaClassifierType) {
132                    JavaClassifier classifier = ((JavaClassifierType) type).getClassifier();
133                    if (classifier instanceof JavaClass) {
134                        FqName classFqName = ((JavaClass) classifier).getFqName();
135                        return classFqName != null && classFqName.asString().equals(fqName);
136                    }
137                }
138            }
139            return false;
140        }
141    
142        @NotNull
143        public static Collection<JavaClass> getClassesInPackage(@NotNull JavaPackage javaPackage) {
144            Collection<JavaClass> classes = javaPackage.getClasses();
145            Set<FqName> addedQualifiedNames = new HashSet<FqName>(classes.size());
146            List<JavaClass> result = new ArrayList<JavaClass>(classes.size());
147    
148            for (JavaClass javaClass : classes) {
149                FqName fqName = javaClass.getFqName();
150                if (fqName != null && addedQualifiedNames.add(fqName)) {
151                    result.add(javaClass);
152                }
153            }
154    
155            return result;
156        }
157    
158        /**
159         * @see com.intellij.psi.util.TypeConversionUtil#erasure(com.intellij.psi.PsiType)
160         */
161        @Nullable
162        public static JavaType erasure(@NotNull JavaType type) {
163            return erasure(type, JavaTypeSubstitutor.EMPTY);
164        }
165    
166        /**
167         * @see com.intellij.psi.util.TypeConversionUtil#erasure(com.intellij.psi.PsiType, com.intellij.psi.PsiSubstitutor)
168         */
169        @Nullable
170        public static JavaType erasure(@NotNull JavaType type, @NotNull JavaTypeSubstitutor substitutor) {
171            if (type instanceof JavaClassifierType) {
172                JavaClassifier classifier = ((JavaClassifierType) type).getClassifier();
173                if (classifier instanceof JavaClass) {
174                    return ((JavaClass) classifier).getDefaultType();
175                }
176                else if (classifier instanceof JavaTypeParameter) {
177                    JavaTypeParameter typeParameter = (JavaTypeParameter) classifier;
178                    return typeParameterErasure(typeParameter, new HashSet<JavaTypeParameter>(), substitutor);
179                }
180                else {
181                    return null;
182                }
183            }
184            else if (type instanceof JavaPrimitiveType) {
185                return type;
186            }
187            else if (type instanceof JavaArrayType) {
188                JavaType erasure = erasure(((JavaArrayType) type).getComponentType(), substitutor);
189                return erasure == null ? null : erasure.createArrayType();
190            }
191            else if (type instanceof JavaWildcardType) {
192                JavaWildcardType wildcardType = (JavaWildcardType) type;
193                JavaType bound = wildcardType.getBound();
194                if (bound != null && wildcardType.isExtends()) {
195                    return erasure(bound, substitutor);
196                }
197                return wildcardType.getTypeProvider().createJavaLangObjectType();
198            }
199            else {
200                throw new IllegalStateException("Unsupported type: " + type);
201            }
202        }
203    
204        /**
205         * @see com.intellij.psi.util.TypeConversionUtil#typeParameterErasure(com.intellij.psi.PsiTypeParameter)
206         */
207        @Nullable
208        private static JavaType typeParameterErasure(
209                @NotNull JavaTypeParameter typeParameter,
210                @NotNull HashSet<JavaTypeParameter> visited,
211                @NotNull JavaTypeSubstitutor substitutor
212        ) {
213            Collection<JavaClassifierType> upperBounds = typeParameter.getUpperBounds();
214            if (!upperBounds.isEmpty()) {
215                JavaClassifier classifier = upperBounds.iterator().next().getClassifier();
216                if (classifier instanceof JavaTypeParameter && !visited.contains(classifier)) {
217                    JavaTypeParameter typeParameterBound = (JavaTypeParameter) classifier;
218                    visited.add(typeParameterBound);
219                    JavaType substitutedType = substitutor.substitute(typeParameterBound);
220                    if (substitutedType != null) {
221                        return erasure(substitutedType);
222                    }
223                    return typeParameterErasure(typeParameterBound, visited, substitutor);
224                }
225                else if (classifier instanceof JavaClass) {
226                    return ((JavaClass) classifier).getDefaultType();
227                }
228            }
229            return typeParameter.getTypeProvider().createJavaLangObjectType();
230        }
231    
232        @NotNull
233        public static Map<TypeParameterDescriptor, TypeParameterDescriptorImpl> recreateTypeParametersAndReturnMapping(
234                @NotNull List<TypeParameterDescriptor> originalParameters,
235                @Nullable DeclarationDescriptor newOwner
236        ) {
237            // LinkedHashMap to save the order of type parameters
238            Map<TypeParameterDescriptor, TypeParameterDescriptorImpl> result =
239                    new LinkedHashMap<TypeParameterDescriptor, TypeParameterDescriptorImpl>();
240            for (TypeParameterDescriptor typeParameter : originalParameters) {
241                result.put(typeParameter,
242                           TypeParameterDescriptorImpl.createForFurtherModification(
243                                   newOwner == null ? typeParameter.getContainingDeclaration() : newOwner,
244                                   typeParameter.getAnnotations(),
245                                   typeParameter.isReified(),
246                                   typeParameter.getVariance(),
247                                   typeParameter.getName(),
248                                   typeParameter.getIndex()));
249            }
250            return result;
251        }
252    
253        @NotNull
254        public static TypeSubstitutor createSubstitutorForTypeParameters(
255                @NotNull Map<TypeParameterDescriptor, TypeParameterDescriptorImpl> originalToAltTypeParameters
256        ) {
257            Map<TypeConstructor, TypeProjection> typeSubstitutionContext = new HashMap<TypeConstructor, TypeProjection>();
258            for (Map.Entry<TypeParameterDescriptor, TypeParameterDescriptorImpl> originalToAltTypeParameter : originalToAltTypeParameters
259                    .entrySet()) {
260                typeSubstitutionContext.put(originalToAltTypeParameter.getKey().getTypeConstructor(),
261                                            new TypeProjectionImpl(originalToAltTypeParameter.getValue().getDefaultType()));
262            }
263            return TypeSubstitutor.create(typeSubstitutionContext);
264        }
265    
266        public static boolean hasStaticMembers(@NotNull JavaClass javaClass) {
267            for (JavaMethod method : javaClass.getMethods()) {
268                if (method.isStatic() && !shouldBeInEnumClassObject(method)) {
269                    return true;
270                }
271            }
272    
273            for (JavaField field : javaClass.getFields()) {
274                if (field.isStatic() && !field.isEnumEntry()) {
275                    return true;
276                }
277            }
278    
279            for (JavaClass nestedClass : javaClass.getInnerClasses()) {
280                if (SingleAbstractMethodUtils.isSamInterface(nestedClass)) {
281                    return true;
282                }
283                if (nestedClass.isStatic() && hasStaticMembers(nestedClass)) {
284                    return true;
285                }
286            }
287    
288            return false;
289        }
290    
291        @Nullable
292        public static ClassDescriptor getKotlinBuiltinClassDescriptor(@NotNull FqName qualifiedName) {
293            if (!qualifiedName.firstSegmentIs(KotlinBuiltIns.BUILT_INS_PACKAGE_NAME)) return null;
294    
295            List<Name> segments = qualifiedName.pathSegments();
296            if (segments.size() < 2) return null;
297    
298            JetScope scope = KotlinBuiltIns.getInstance().getBuiltInsPackageScope();
299            for (int i = 1, size = segments.size(); i < size; i++) {
300                ClassifierDescriptor classifier = scope.getClassifier(segments.get(i));
301                if (classifier == null) return null;
302                assert classifier instanceof ClassDescriptor : "Unexpected classifier in built-ins: " + classifier;
303                scope = ((ClassDescriptor) classifier).getUnsubstitutedInnerClassesScope();
304            }
305    
306            return (ClassDescriptor) scope.getContainingDeclaration();
307        }
308    }