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
017package org.jetbrains.jet.lang.resolve.java;
018
019import com.google.common.collect.Lists;
020import com.google.common.collect.Sets;
021import com.intellij.openapi.application.ApplicationManager;
022import com.intellij.openapi.diagnostic.Logger;
023import com.intellij.openapi.project.Project;
024import com.intellij.openapi.util.Comparing;
025import com.intellij.openapi.vfs.VirtualFile;
026import com.intellij.psi.PsiAnnotation;
027import com.intellij.psi.PsiClass;
028import com.intellij.psi.PsiPackage;
029import com.intellij.psi.search.DelegatingGlobalSearchScope;
030import com.intellij.psi.search.GlobalSearchScope;
031import org.jetbrains.annotations.NotNull;
032import org.jetbrains.annotations.Nullable;
033import org.jetbrains.jet.lang.resolve.java.resolver.JavaAnnotationResolver;
034import org.jetbrains.jet.lang.resolve.name.FqName;
035import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
036import org.jetbrains.jet.plugin.JetFileType;
037
038import javax.annotation.PostConstruct;
039import javax.inject.Inject;
040import java.util.List;
041import java.util.Set;
042
043public 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}