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.application.ApplicationManager;
022    import com.intellij.openapi.diagnostic.Logger;
023    import com.intellij.openapi.project.Project;
024    import com.intellij.openapi.util.Comparing;
025    import com.intellij.openapi.vfs.VirtualFile;
026    import com.intellij.psi.PsiAnnotation;
027    import com.intellij.psi.PsiClass;
028    import com.intellij.psi.PsiPackage;
029    import com.intellij.psi.search.DelegatingGlobalSearchScope;
030    import com.intellij.psi.search.GlobalSearchScope;
031    import org.jetbrains.annotations.NotNull;
032    import org.jetbrains.annotations.Nullable;
033    import org.jetbrains.jet.lang.resolve.java.resolver.JavaAnnotationResolver;
034    import org.jetbrains.jet.lang.resolve.name.FqName;
035    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
036    import org.jetbrains.jet.plugin.JetFileType;
037    
038    import javax.annotation.PostConstruct;
039    import javax.inject.Inject;
040    import java.util.List;
041    import java.util.Set;
042    
043    public class PsiClassFinderImpl implements PsiClassFinder {
044        private static final Logger LOG = Logger.getInstance(PsiClassFinderImpl.class);
045    
046        @NotNull
047        private Project project;
048    
049        private GlobalSearchScope javaSearchScope;
050        private JavaPsiFacadeKotlinHacks javaFacade;
051    
052        @Inject
053        public void setProject(@NotNull Project project) {
054            this.project = project;
055        }
056    
057        @PostConstruct
058        public void initialize() {
059            javaSearchScope = new DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) {
060                @Override
061                public boolean contains(VirtualFile file) {
062                    return myBaseScope.contains(file) && file.getFileType() != JetFileType.INSTANCE;
063                }
064    
065                @Override
066                public int compare(VirtualFile file1, VirtualFile file2) {
067                    // TODO: this is a hackish workaround for the following problem:
068                    // since we are working with the allScope(), if the same class FqName
069                    // to be on the class path twice, because it is included into different libraries
070                    // (e.g. junit-4.0.jar is used as a separate library and as a part of idea_full)
071                    // the two libraries are attached to different modules, the parent compare()
072                    // can't tell which one comes first, so they can come in random order
073                    // To fix this, we sort additionally by the full path, to make the ordering deterministic
074                    // TODO: Delete this hack when proper scopes are used
075                    int compare = super.compare(file1, file2);
076                    if (compare == 0) {
077                        return Comparing.compare(file1.getPath(), file2.getPath());
078                    }
079                    return compare;
080                }
081            };
082            javaFacade = new JavaPsiFacadeKotlinHacks(project);
083        }
084    
085    
086        @Override
087        @Nullable
088        public PsiClass findPsiClass(@NotNull FqName qualifiedName, @NotNull RuntimeClassesHandleMode runtimeClassesHandleMode) {
089            PsiClass original = javaFacade.findClass(qualifiedName.asString(), javaSearchScope);
090    
091            if (original != null) {
092                String classQualifiedName = original.getQualifiedName();
093                FqName actualQualifiedName = classQualifiedName != null ? new FqName(classQualifiedName) : null;
094                if (!qualifiedName.equals(actualQualifiedName)) {
095                    throw new IllegalStateException("requested " + qualifiedName + ", got " + actualQualifiedName);
096                }
097            }
098    
099            if (original instanceof JetJavaMirrorMarker) {
100                throw new IllegalStateException("JetJavaMirrorMaker is not possible in resolve.java, resolving: " + qualifiedName);
101            }
102    
103            if (original == null) {
104                return null;
105            }
106    
107            if (KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME.equals(qualifiedName.parent())) {
108                PsiAnnotation assertInvisibleAnnotation = JavaAnnotationResolver.findOwnAnnotation(
109                        original, JvmStdlibNames.ASSERT_INVISIBLE_IN_RESOLVER.getFqName().asString());
110    
111                if (assertInvisibleAnnotation != null) {
112                    switch (runtimeClassesHandleMode) {
113                        case IGNORE:
114                            break;
115                        case REPORT_ERROR:
116                            if (ApplicationManager.getApplication().isInternal()) {
117                                LOG.error("classpath is configured incorrectly:" +
118                                          " class " + qualifiedName + " from runtime must not be loaded by compiler");
119                            }
120                            break;
121                        default:
122                            throw new IllegalStateException("unknown parameter value: " + runtimeClassesHandleMode);
123                    }
124                    return null;
125                }
126            }
127    
128            return original;
129        }
130    
131        @Override
132        @Nullable
133        public PsiPackage findPsiPackage(@NotNull FqName qualifiedName) {
134            return javaFacade.findPackage(qualifiedName.asString());
135        }
136    
137        @NotNull
138        @Override
139        public List<PsiClass> findPsiClasses(@NotNull PsiPackage psiPackage) {
140            return filterDuplicateClasses(psiPackage.getClasses());
141        }
142    
143        @NotNull
144        @Override
145        public List<PsiClass> findInnerPsiClasses(@NotNull PsiClass psiClass) {
146            return filterDuplicateClasses(psiClass.getInnerClasses());
147        }
148    
149        private static List<PsiClass> filterDuplicateClasses(PsiClass[] classes) {
150            Set<String> addedQualifiedNames = Sets.newHashSet();
151            List<PsiClass> filteredClasses = Lists.newArrayList();
152    
153            for (PsiClass aClass : classes) {
154                String qualifiedName = aClass.getQualifiedName();
155    
156                if (qualifiedName != null) {
157                    if (addedQualifiedNames.add(qualifiedName)) {
158                        filteredClasses.add(aClass);
159                    }
160                }
161            }
162    
163            return filteredClasses;
164        }
165    }