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.lazy.declarations;
018    
019    import com.google.common.base.Predicate;
020    import com.google.common.base.Predicates;
021    import com.google.common.collect.*;
022    import com.intellij.openapi.util.Computable;
023    import com.intellij.psi.NavigatablePsiElement;
024    import com.intellij.util.Function;
025    import com.intellij.util.containers.ContainerUtil;
026    import org.jetbrains.annotations.NotNull;
027    import org.jetbrains.annotations.Nullable;
028    import org.jetbrains.jet.lang.psi.JetFile;
029    import org.jetbrains.jet.lang.psi.JetNamespaceHeader;
030    import org.jetbrains.jet.lang.psi.JetPsiUtil;
031    import org.jetbrains.jet.lang.resolve.lazy.data.JetClassLikeInfo;
032    import org.jetbrains.jet.lang.resolve.lazy.storage.MemoizedFunctionToNullable;
033    import org.jetbrains.jet.lang.resolve.lazy.storage.NotNullLazyValue;
034    import org.jetbrains.jet.lang.resolve.lazy.storage.StorageManager;
035    import org.jetbrains.jet.lang.resolve.name.FqName;
036    import org.jetbrains.jet.util.QualifiedNamesUtil;
037    
038    import java.util.Collection;
039    import java.util.Collections;
040    import java.util.Set;
041    
042    public class FileBasedDeclarationProviderFactory implements DeclarationProviderFactory {
043    
044        private static class Index {
045            private final Multimap<FqName, JetFile> filesByPackage = HashMultimap.create();
046            private final Set<FqName> declaredPackages = Sets.newHashSet();
047        }
048    
049        private final Predicate<FqName> isPackageDeclaredExternally;
050    
051        private final StorageManager storageManager;
052        private final NotNullLazyValue<Index> index;
053    
054        private final MemoizedFunctionToNullable<FqName, PackageMemberDeclarationProvider> packageDeclarationProviders;
055    
056        public FileBasedDeclarationProviderFactory(@NotNull StorageManager storageManager, @NotNull Collection<JetFile> files) {
057            this(storageManager, files, Predicates.<FqName>alwaysFalse());
058        }
059    
060        public FileBasedDeclarationProviderFactory(
061                @NotNull StorageManager storageManager,
062                @NotNull final Collection<JetFile> files,
063                @NotNull Predicate<FqName> isPackageDeclaredExternally
064        ) {
065            this.storageManager = storageManager;
066            this.isPackageDeclaredExternally = isPackageDeclaredExternally;
067            this.index = storageManager.createLazyValue(new Computable<Index>() {
068                @Override
069                public Index compute() {
070                    return computeFilesByPackage(files);
071                }
072            });
073            this.packageDeclarationProviders = storageManager.createMemoizedFunctionWithNullableValues(new Function<FqName, PackageMemberDeclarationProvider>() {
074                @Override
075                public PackageMemberDeclarationProvider fun(FqName fqName) {
076                    return createPackageMemberDeclarationProvider(fqName);
077                }
078            }, StorageManager.ReferenceKind.STRONG);
079        }
080    
081        @NotNull
082        private static Index computeFilesByPackage(@NotNull Collection<JetFile> files) {
083            Index index = new Index();
084            for (JetFile file : files) {
085                JetNamespaceHeader header = file.getNamespaceHeader();
086                if (header == null) {
087                    throw new IllegalArgumentException("Scripts are not supported");
088                }
089    
090                FqName packageFqName = new FqName(header.getQualifiedName());
091                addMeAndParentPackages(index, packageFqName);
092                index.filesByPackage.put(packageFqName, file);
093            }
094            return index;
095        }
096    
097        private static void addMeAndParentPackages(@NotNull Index index, @NotNull FqName name) {
098            index.declaredPackages.add(name);
099            if (!name.isRoot()) {
100                addMeAndParentPackages(index, name.parent());
101            }
102        }
103    
104        /*package*/ boolean isPackageDeclaredExplicitly(@NotNull FqName packageFqName) {
105            return index.compute().declaredPackages.contains(packageFqName);
106        }
107    
108        /*package*/ boolean isPackageDeclared(@NotNull FqName packageFqName) {
109            return isPackageDeclaredExplicitly(packageFqName) || isPackageDeclaredExternally.apply(packageFqName);
110        }
111    
112        /*package*/ Collection<FqName> getAllDeclaredSubPackagesOf(@NotNull final FqName parent) {
113            return Collections2.filter(index.compute().declaredPackages, new Predicate<FqName>() {
114                @Override
115                public boolean apply(FqName fqName) {
116                    return !fqName.isRoot() && fqName.parent().equals(parent);
117                }
118            });
119        }
120    
121        /*package*/ Collection<NavigatablePsiElement> getPackageDeclarations(@NotNull final FqName fqName) {
122            if (fqName.isRoot()) {
123                return Collections.emptyList();
124            }
125    
126            Collection<NavigatablePsiElement> resultElements = Lists.newArrayList();
127            for (FqName declaredPackage : index.compute().filesByPackage.keys()) {
128                if (QualifiedNamesUtil.isSubpackageOf(declaredPackage, fqName)) {
129                    Collection<JetFile> files = index.compute().filesByPackage.get(declaredPackage);
130                    resultElements.addAll(ContainerUtil.map(files, new Function<JetFile, NavigatablePsiElement>() {
131                        @Override
132                        public NavigatablePsiElement fun(JetFile file) {
133                            return JetPsiUtil.getPackageReference(file, QualifiedNamesUtil.numberOfSegments(fqName) - 1);
134                        }
135                    }));
136                }
137            }
138    
139            return resultElements;
140        }
141    
142        @Override
143        public PackageMemberDeclarationProvider getPackageMemberDeclarationProvider(@NotNull FqName packageFqName) {
144            return packageDeclarationProviders.fun(packageFqName);
145        }
146    
147        @Nullable
148        public PackageMemberDeclarationProvider createPackageMemberDeclarationProvider(@NotNull FqName packageFqName) {
149            if (!isPackageDeclaredExplicitly(packageFqName)) {
150                if (isPackageDeclaredExternally.apply(packageFqName)) {
151                    return EmptyPackageMemberDeclarationProvider.INSTANCE;
152                }
153                return null;
154            }
155    
156            return new FileBasedPackageMemberDeclarationProvider(storageManager, packageFqName, this, index.compute().filesByPackage.get(packageFqName));
157        }
158    
159        @NotNull
160        @Override
161        public ClassMemberDeclarationProvider getClassMemberDeclarationProvider(@NotNull JetClassLikeInfo classLikeInfo) {
162            if (!index.compute().filesByPackage.containsKey(classLikeInfo.getContainingPackageFqName())) {
163                throw new IllegalStateException("This factory doesn't know about this class: " + classLikeInfo);
164            }
165    
166            return new PsiBasedClassMemberDeclarationProvider(storageManager, classLikeInfo);
167        }
168    }