Dépôt officiel du code source de l'ERP OpenConcerto
Rev 177 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils.cc;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.Tuple2;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.jcip.annotations.Immutable;
/**
* A class to specify which objects to include or exclude.
*
* @author Sylvain
* @param <T> type of items
* @see #isIncluded(Object)
*/
@Immutable
public class IncludeExclude<T> {
static private final IncludeExclude<Object> EMPTY = new IncludeExclude<Object>(Collections.<Object> emptySet(), Collections.<Object> emptySet(), true);
static private final IncludeExclude<Object> FULL = new IncludeExclude<Object>(null, Collections.<Object> emptySet(), true);
@SuppressWarnings("unchecked")
public static <T> IncludeExclude<T> getEmpty() {
return (IncludeExclude<T>) EMPTY;
}
@SuppressWarnings("unchecked")
public static <T> IncludeExclude<T> getFull() {
return (IncludeExclude<T>) FULL;
}
@SafeVarargs
public static <T> IncludeExclude<T> getNormalized(final T... includes) {
return getNormalizedInclude(Arrays.asList(includes));
}
@Deprecated
public static <T> IncludeExclude<T> getNormalized(final Collection<? extends T> includes) {
return getNormalizedInclude(includes);
}
/**
* Return the normalized version including only the passed values. E.g. if <code>includes</code>
* is empty or <code>null</code>, this won't allocate any memory and {@link #getEmpty()} or
* {@link #getFull()} will be returned.
*
* @param <T> type of items
* @param includes which values to include.
* @return the normalized version.
*/
public static <T> IncludeExclude<T> getNormalizedInclude(final Collection<? extends T> includes) {
return getNormalized(includes, Collections.<T> emptySet());
}
/**
* Return the normalized version excluding only the passed values. E.g. if <code>excludes</code>
* is empty or <code>null</code>, this won't allocate any memory and {@link #getFull()} or
* {@link #getEmpty()} will be returned.
*
* @param <T> type of items
* @param excludes which values to exclude.
* @return the normalized version.
*/
public static <T> IncludeExclude<T> getNormalizedExclude(final Collection<? extends T> excludes) {
return getNormalized(null, excludes);
}
public static <T> IncludeExclude<T> getNormalized(final Collection<? extends T> includes, final Collection<? extends T> excludes) {
return new IncludeExclude<T>(includes, excludes).normal;
}
private final Set<T> includes;
private final Set<T> excludes;
private final IncludeExclude<T> normal;
public IncludeExclude(final Collection<? extends T> includes) {
this(includes, Collections.<T> emptySet());
}
/**
* Create a new instance.
*
* @param includes which objects to include, <code>null</code> meaning all.
* @param excludes which objects to exclude, <code>null</code> meaning all.
* @see #isIncluded(Object)
*/
public IncludeExclude(final Collection<? extends T> includes, final Collection<? extends T> excludes) {
this(includes, excludes, false);
}
private IncludeExclude(final Collection<? extends T> includes, final Collection<? extends T> excludes, final boolean isNormal) {
this(includes == null ? null : Collections.unmodifiableSet(new HashSet<T>(includes)), excludes == null ? null : Collections.unmodifiableSet(new HashSet<T>(excludes)), false, isNormal);
}
private IncludeExclude(final Set<T> includes, final Set<T> excludes, final boolean areMutable, final boolean isNormal) {
super();
// parameter just to overload constructor
if (areMutable)
throw new IllegalStateException();
this.includes = includes;
this.excludes = excludes;
this.normal = isNormal ? this : this.normalize();
assert this.normal.excludes.isEmpty() || this.normal.includes == null;
}
private final IncludeExclude<T> normalize() {
if (this.excludes == null || (this.includes != null && this.includes.isEmpty()))
return getEmpty();
if (this.excludes.isEmpty() && this.includes == null)
return getFull();
if (this.excludes.isEmpty() || this.includes == null)
return this;
return new IncludeExclude<T>(CollectionUtils.substract(this.includes, this.excludes), Collections.<T> emptySet(), true);
}
public final IncludeExclude<T> getNormal() {
return this.normal;
}
/**
* Whether the passed object is included or excluded by this.
*
* @param s an object to test.
* @return <code>true</code> if is in include and not in exclude.
*/
public final boolean isIncluded(final T s) {
// "if" order is irrelevant since those methods use getNormal() and it can't be both "all"
// and "none".
if (this.isAllIncluded())
return true;
else if (this.isNoneIncluded())
return false;
else
return (this.normal.includes == null || this.normal.includes.contains(s)) && !this.normal.excludes.contains(s);
}
public final <S extends T> Collection<S> getExcluded(final Collection<S> items) {
if (this.areNoneIncluded(items))
return items;
else if (this.areAllIncluded(items))
return Collections.emptySet();
final Set<S> res = new HashSet<>(items);
// avoid checks of getIncluded()
res.removeAll(this.createIncluded(items));
return res;
}
public final <S extends T> Collection<S> getIncluded(final Collection<S> items) {
if (this.areAllIncluded(items))
return items;
else if (this.isNoneIncluded())
return Collections.emptySet();
else
return createIncluded(items);
}
private <S extends T> Collection<S> createIncluded(final Collection<S> items) {
final Set<S> res = new HashSet<>(items);
if (this.normal.includes != null)
res.retainAll(this.normal.includes);
res.removeAll(this.normal.excludes);
return res;
}
public final boolean areAllIncluded(final Collection<? extends T> items) {
if (this.isAllIncluded() || items.isEmpty())
return true;
else if (this.isNoneIncluded())
return false;
else
return (this.normal.includes == null || this.normal.includes.containsAll(items)) && Collections.disjoint(this.normal.excludes, items);
}
public final boolean areNoneIncluded(final Collection<? extends T> items) {
if (this.isNoneIncluded() || items.isEmpty())
return true;
else if (this.isAllIncluded())
return false;
else
return (this.normal.includes != null && Collections.disjoint(this.normal.includes, items)) || this.normal.excludes.containsAll(items);
}
/**
* Whether this includes all objects.
*
* @return <code>true</code> if {@link #isIncluded(Object)} always return <code>true</code>
*/
public final boolean isAllIncluded() {
// use normalized value to avoid ambiguous "include all" and "exclude all"
return this.normal == FULL;
}
/**
* Whether this includes no objects.
*
* @return <code>true</code> if {@link #isIncluded(Object)} always return <code>false</code>
*/
public final boolean isNoneIncluded() {
// use normalized value to avoid ambiguous "include all" and "exclude all"
return this.normal == EMPTY;
}
/**
* The one and only object included by this.
*
* @return <code>true</code> if {@link #isIncluded(Object)} returns <code>true</code> for one
* and only one object.
*/
public final Tuple2<Boolean, T> getSole() {
if (this.normal.includes == null) {
return Tuple2.create(false, null);
} else {
assert this.normal.excludes.isEmpty();
final boolean res = this.normal.includes.size() == 1;
return Tuple2.create(res, res ? this.normal.includes.iterator().next() : null);
}
}
/**
* Return the one and only object included by this, or the passed object.
*
* @param ifNoSole the object to return if this doesn't include exactly one object.
* @return {@link #getSole()} or <code>ifNoSole</code>.
*/
public final T getSole(final T ifNoSole) {
final Tuple2<Boolean, T> sole = this.getSole();
if (sole.get0())
return sole.get1();
else
return ifNoSole;
}
@SafeVarargs
public final IncludeExclude<T> include(final T... items) {
return this.include(Arrays.asList(items));
}
public final IncludeExclude<T> include(final Collection<? extends T> items) {
if (this.areAllIncluded(items))
return this;
else if (this.excludes == null)
throw new IllegalStateException("Cannot include an item when excluding all");
Set<T> newIncludes;
if (this.includes == null || this.includes.containsAll(items)) {
newIncludes = this.includes;
} else {
newIncludes = new HashSet<>(this.includes);
newIncludes.addAll(items);
newIncludes = Collections.unmodifiableSet(newIncludes);
}
Set<T> newExcludes;
if (Collections.disjoint(this.excludes, items)) {
newExcludes = this.excludes;
} else {
newExcludes = new HashSet<>(this.excludes);
newExcludes.removeAll(items);
newExcludes = Collections.unmodifiableSet(newExcludes);
}
return new IncludeExclude<T>(newIncludes, newExcludes, false, false);
}
@SafeVarargs
public final IncludeExclude<T> exclude(final T... items) {
return this.exclude(Arrays.asList(items));
}
public final IncludeExclude<T> exclude(final Collection<? extends T> items) {
if (this.areNoneIncluded(items))
return this;
assert this.excludes != null : "!areNoneIncluded() but this.excludes == null";
Set<T> newExcludes = new HashSet<>(this.excludes);
newExcludes.addAll(items);
newExcludes = Collections.unmodifiableSet(newExcludes);
return new IncludeExclude<T>(this.includes, Collections.unmodifiableSet(newExcludes), false, false);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.excludes == null) ? 0 : this.excludes.hashCode());
result = prime * result + ((this.includes == null) ? 0 : this.includes.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final IncludeExclude<?> other = (IncludeExclude<?>) obj;
return CompareUtils.equals(this.includes, other.includes) && CompareUtils.equals(this.excludes, other.excludes);
}
@Override
public String toString() {
final String suffix;
if (this == FULL)
suffix = " ALL";
else if (this == EMPTY)
suffix = " NONE";
else
suffix = " includes " + this.includes + " except " + this.excludes;
return this.getClass().getSimpleName() + suffix;
}
}