/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.packageDependencies;
import com.intellij.icons.AllIcons;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.components.MainConfigurationStateSplitter;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.components.StoragePathMacros;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.scope.packageSet.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@State(name = "DependencyValidationManager", storages = {
@Storage(file = StoragePathMacros.PROJECT_CONFIG_DIR + "/scopes/", stateSplitter = DependencyValidationManagerImpl.ScopesStateSplitter.class)})
public class DependencyValidationManagerImpl extends DependencyValidationManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.packageDependencies.DependencyValidationManagerImpl");
private final List<DependencyRule> myRules = new ArrayList<DependencyRule>();
private final NamedScopeManager myNamedScopeManager;
private boolean mySkipImportStatements;
private boolean mySkipImportStatementsWasSpecified;
@NonNls
private static final String DENY_RULE_KEY = "deny_rule";
@NonNls
private static final String FROM_SCOPE_KEY = "from_scope";
@NonNls
private static final String TO_SCOPE_KEY = "to_scope";
@NonNls
private static final String IS_DENY_KEY = "is_deny";
@NonNls
private static final String UNNAMED_SCOPE = "unnamed_scope";
@NonNls
private static final String VALUE = "value";
private final Map<String, PackageSet> myUnnamedScopes = new THashMap<String, PackageSet>();
public DependencyValidationManagerImpl(final Project project, NamedScopeManager namedScopeManager) {
super(project);
myNamedScopeManager = namedScopeManager;
namedScopeManager.addScopeListener(new ScopeListener() {
@Override
public void scopesChanged() {
reloadScopes();
}
});
}
@Override
@NotNull
public List<NamedScope> getPredefinedScopes() {
final List<NamedScope> predefinedScopes = new ArrayList<NamedScope>();
final CustomScopesProvider[] scopesProviders = CustomScopesProvider.CUSTOM_SCOPES_PROVIDER.getExtensions(myProject);
for (CustomScopesProvider scopesProvider : scopesProviders) {
predefinedScopes.addAll(scopesProvider.getCustomScopes());
}
return predefinedScopes;
}
@Override
public NamedScope getPredefinedScope(@NotNull String name) {
final CustomScopesProvider[] scopesProviders = CustomScopesProvider.CUSTOM_SCOPES_PROVIDER.getExtensions(myProject);
for (CustomScopesProvider scopesProvider : scopesProviders) {
final NamedScope scope = scopesProvider instanceof CustomScopesProviderEx
? ((CustomScopesProviderEx)scopesProvider).getCustomScope(name)
: CustomScopesProviderEx.findPredefinedScope(name, scopesProvider.getCustomScopes());
if (scope != null) {
return scope;
}
}
return null;
}
@Override
public boolean hasRules() {
return !myRules.isEmpty();
}
@Override
@Nullable
public DependencyRule getViolatorDependencyRule(@NotNull PsiFile from, @NotNull PsiFile to) {
for (DependencyRule dependencyRule : myRules) {
if (dependencyRule.isForbiddenToUse(from, to)) return dependencyRule;
}
return null;
}
@Override
@NotNull
public DependencyRule[] getViolatorDependencyRules(@NotNull PsiFile from, @NotNull PsiFile to) {
ArrayList<DependencyRule> result = new ArrayList<DependencyRule>();
for (DependencyRule dependencyRule : myRules) {
if (dependencyRule.isForbiddenToUse(from, to)) {
result.add(dependencyRule);
}
}
return result.toArray(new DependencyRule[result.size()]);
}
@NotNull
@Override
public DependencyRule[] getApplicableRules(@NotNull PsiFile file) {
ArrayList<DependencyRule> result = new ArrayList<DependencyRule>();
for (DependencyRule dependencyRule : myRules) {
if (dependencyRule.isApplicable(file)) {
result.add(dependencyRule);
}
}
return result.toArray(new DependencyRule[result.size()]);
}
@Override
public boolean skipImportStatements() {
return mySkipImportStatements;
}
@Override
public void setSkipImportStatements(final boolean skip) {
mySkipImportStatements = skip;
}
@NotNull
@Override
public Map<String, PackageSet> getUnnamedScopes() {
return myUnnamedScopes;
}
@NotNull
@Override
public DependencyRule[] getAllRules() {
return myRules.toArray(new DependencyRule[myRules.size()]);
}
@Override
public void removeAllRules() {
myRules.clear();
}
@Override
public void addRule(@NotNull DependencyRule rule) {
appendUnnamedScope(rule.getFromScope());
appendUnnamedScope(rule.getToScope());
myRules.add(rule);
}
@Override
public void reloadRules() {
final Element element = new Element("rules_2_reload");
writeRules(element);
readRules(element);
}
private void appendUnnamedScope(final NamedScope fromScope) {
if (getScope(fromScope.getName()) == null) {
final PackageSet packageSet = fromScope.getValue();
if (packageSet != null && !myUnnamedScopes.containsKey(packageSet.getText())) {
myUnnamedScopes.put(packageSet.getText(), packageSet);
}
}
}
@Override
public String getDisplayName() {
return IdeBundle.message("shared.scopes.node.text");
}
@Override
public Icon getIcon() {
return AllIcons.Ide.SharedScope;
}
@Override
public void loadState(Element element) {
Element option = element.getChild("option");
if (option != null && "SKIP_IMPORT_STATEMENTS".equals(option.getAttributeValue("name"))) {
mySkipImportStatementsWasSpecified = !myProject.isDefault();
mySkipImportStatements = Boolean.parseBoolean(option.getAttributeValue("value"));
}
super.loadState(element);
myUnnamedScopes.clear();
final List unnamedScopes = element.getChildren(UNNAMED_SCOPE);
final PackageSetFactory packageSetFactory = PackageSetFactory.getInstance();
for (Object unnamedScope : unnamedScopes) {
try {
final String packageSet = ((Element)unnamedScope).getAttributeValue(VALUE);
myUnnamedScopes.put(packageSet, packageSetFactory.compile(packageSet));
}
catch (ParsingException ignored) {
//skip pattern
}
}
readRules(element);
}
private void readRules(Element element) {
removeAllRules();
for (Element rule1 : element.getChildren(DENY_RULE_KEY)) {
DependencyRule rule = readRule(rule1);
if (rule != null) {
addRule(rule);
}
}
}
@Override
public Element getState() {
Element element = super.getState();
assert element != null;
if (mySkipImportStatements || mySkipImportStatementsWasSpecified) {
element.addContent(new Element("option").setAttribute("name", "SKIP_IMPORT_STATEMENTS").setAttribute("value", Boolean.toString(mySkipImportStatements)));
}
if (!myUnnamedScopes.isEmpty()) {
String[] unnamedScopes = myUnnamedScopes.keySet().toArray(new String[myUnnamedScopes.size()]);
Arrays.sort(unnamedScopes);
for (String unnamedScope : unnamedScopes) {
element.addContent(new Element(UNNAMED_SCOPE).setAttribute(VALUE, unnamedScope));
}
}
writeRules(element);
return element;
}
private void writeRules(Element element) {
for (DependencyRule rule : myRules) {
Element ruleElement = writeRule(rule);
if (ruleElement != null) {
element.addContent(ruleElement);
}
}
}
@Override
@Nullable
public NamedScope getScope(@Nullable final String name) {
final NamedScope scope = super.getScope(name);
if (scope == null) {
final PackageSet packageSet = myUnnamedScopes.get(name);
if (packageSet != null) {
return new NamedScope.UnnamedScope(packageSet);
}
}
//compatibility for predefined scopes: rename Project -> All
if (scope == null && Comparing.strEqual(name, "Project")) {
return super.getScope("All");
}
return scope;
}
@Nullable
private static Element writeRule(DependencyRule rule) {
NamedScope fromScope = rule.getFromScope();
NamedScope toScope = rule.getToScope();
if (fromScope == null || toScope == null) return null;
Element ruleElement = new Element(DENY_RULE_KEY);
ruleElement.setAttribute(FROM_SCOPE_KEY, fromScope.getName());
ruleElement.setAttribute(TO_SCOPE_KEY, toScope.getName());
ruleElement.setAttribute(IS_DENY_KEY, Boolean.valueOf(rule.isDenyRule()).toString());
return ruleElement;
}
@Nullable
private DependencyRule readRule(Element ruleElement) {
String fromScope = ruleElement.getAttributeValue(FROM_SCOPE_KEY);
String toScope = ruleElement.getAttributeValue(TO_SCOPE_KEY);
String denyRule = ruleElement.getAttributeValue(IS_DENY_KEY);
if (fromScope == null || toScope == null || denyRule == null) return null;
final NamedScope fromNamedScope = getScope(fromScope);
final NamedScope toNamedScope = getScope(toScope);
if (fromNamedScope == null || toNamedScope == null) return null;
return new DependencyRule(fromNamedScope, toNamedScope, Boolean.valueOf(denyRule).booleanValue());
}
static final class ScopesStateSplitter extends MainConfigurationStateSplitter {
@NotNull
@Override
protected String getSubStateFileName(@NotNull Element element) {
return element.getAttributeValue("name");
}
@NotNull
@Override
protected String getComponentStateFileName() {
return "scope_settings";
}
@NotNull
@Override
protected String getSubStateTagName() {
return "scope";
}
}
private final List<Pair<NamedScope, NamedScopesHolder>> myScopes = ContainerUtil.createLockFreeCopyOnWriteList();
private void reloadScopes() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (getProject().isDisposed()) return;
List<Pair<NamedScope, NamedScopesHolder>> scopeList = new ArrayList<Pair<NamedScope, NamedScopesHolder>>();
addScopesToList(scopeList, DependencyValidationManagerImpl.this);
addScopesToList(scopeList, myNamedScopeManager);
myScopes.clear();
myScopes.addAll(scopeList);
reloadRules();
}
});
}
private static void addScopesToList(@NotNull final List<Pair<NamedScope, NamedScopesHolder>> scopeList, @NotNull final NamedScopesHolder holder) {
for (NamedScope scope : holder.getScopes()) {
scopeList.add(Pair.create(scope, holder));
}
}
@NotNull
public List<Pair<NamedScope, NamedScopesHolder>> getScopeBasedHighlightingCachedScopes() {
return myScopes;
}
@Override
public void fireScopeListeners() {
super.fireScopeListeners();
reloadScopes();
}
}