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