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