/*
* Copyright 2003-2015 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 jetbrains.mps.java.stub;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.persistence.Memento;
import java.util.ArrayList;
import java.util.List;
/**
* Manage access to a subset of packages available from a source (e.g. model root) and classes in these packages.
* Comes handy when we want to control set of packages coming from a well-known classpath item, e.g. Java SDK
* classes, when we want to expose only specific, public APIs.
*
* @author Artem Tikhomirov
*/
public final class PackageScopeControl {
private boolean mySkipPrivate = false;
private List<String> myIncludePrefix;
private List<String> myExcludePrefix;
public PackageScopeControl() {
}
// caller controls element in memento we write to, e.g. if it needs to write few scopes, it's up to caller to
// name them and to place in a container
public void save(Memento memento) {
memento.put("skip-private", Boolean.toString(mySkipPrivate));
if (myIncludePrefix != null) {
for (String prefix : myIncludePrefix) {
memento.createChild("include").put("prefix", prefix);
}
}
if (myExcludePrefix != null) {
for (String prefix : myExcludePrefix) {
memento.createChild("exclude").put("prefix", prefix);
}
}
}
public void load(Memento memento) {
mySkipPrivate = Boolean.parseBoolean(memento.get("skip-private"));
for (Memento entry : memento.getChildren("include")) {
includeWithPrefix(entry.get("prefix"));
}
for (Memento entry : memento.getChildren("exclude")) {
excludeWithPrefix(entry.get("prefix"));
}
}
/**
* Package name prefix. To match exact package, not any starting with the prefix, don't forget
* to put dot in the end of the prefix (e.g. "org.com." not to match "org.common")
*
* @param packageNamePrefix qualified package name prefix to match
*/
public void includeWithPrefix(@NotNull String packageNamePrefix) {
if (myIncludePrefix == null) {
myIncludePrefix = new ArrayList<String>(4);
}
if (packageNamePrefix.length() == 0) {
throw new IllegalArgumentException();
}
myIncludePrefix.add(packageNamePrefix);
}
public void excludeWithPrefix(@NotNull String packageNamePrefix) {
if (myExcludePrefix == null) {
myExcludePrefix = new ArrayList<String>(4);
}
if (packageNamePrefix.length() == 0) {
throw new IllegalArgumentException();
}
myExcludePrefix.add(packageNamePrefix);
}
public boolean isSkipPrivate() {
return mySkipPrivate;
}
public void setSkipPrivate(boolean skipPrivate) {
mySkipPrivate = skipPrivate;
}
/**
* First, package name is checked for inclusion, then for exclusion (so that
* <code>include("com"); exclude("com.b.")</code> would allow <code>com.a.A</code>, <code>com.D</code>
* and forbid <code>com.b.C</code>.
*
* @param qualifiedPackageName package to check for scope
* @return true if package deemed part of the scope
*/
public boolean isIncluded(@NotNull String qualifiedPackageName) {
boolean included = myIncludePrefix == null || matchPrefix(myIncludePrefix, qualifiedPackageName, false);
boolean excluded = myExcludePrefix != null && matchPrefix(myExcludePrefix, qualifiedPackageName, false);
return included && !excluded;
}
/**
* Checking if any of sub-packages of this package is included by {@link #myIncludePrefix}
*
* @param qualifiedPackageName package to check for scope
* @return true if at least one of child packages may be included
*/
public boolean isAnyChildIncluded(@NotNull String qualifiedPackageName) {
return myIncludePrefix != null && matchPrefix(myIncludePrefix, qualifiedPackageName, true);
}
/**
* @param prefixes - the list of prefixes
* @param name - the name of the package
* @param checkingChildPrefixes - true the name is a name of potential parent package for any of included packages (prefixes),
* used from {@link #isAnyChildIncluded(String)}
* @return boolean if package name starts with any of specified prefixes of vice versa (if <code>checkingChildPrefixes</code> is true)
*/
private static boolean matchPrefix(List<String> prefixes, String name, boolean checkingChildPrefixes) {
// prefix may indicate exact package, thus we make sure name looks 'complete'
// e.g. prefix "org.com." shall match name == "org.com"
name = name + '.';
for (String prefix : prefixes) {
if (checkingChildPrefixes ? prefix.startsWith(name) : name.startsWith(prefix)) {
return true;
}
}
return false;
}
}