cdc.applic.expressions.content.StringSet Maven / Gradle / Ivy
Show all versions of cdc-applic-expressions Show documentation
package cdc.applic.expressions.content;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import cdc.applic.expressions.SyntacticException;
import cdc.applic.expressions.parsing.SItemsParsing;
import cdc.util.lang.Checks;
import cdc.util.lang.CollectionUtils;
/**
* Implementation of {@link SItemSet} containing {@link StringSItem}s.
*
* Order of definition of SItems is preserved.
* Duplicates are removed.
*
* @author Damien Carbonne
*/
public final class StringSet extends AbstractSItemSet
implements CheckedSet, CountableSet, Comparable {
/**
* A Comparator based on StringValue alphabetical comparison.
*
* Use {@link #lexicographicComparator(Comparator)} for more elaborated comparators.
*/
public static final Comparator LEXICOGRAPHIC_COMPARATOR =
(s1,
s2) -> {
if (s1 == s2) {
return 0;
}
if (s1 == null || s2 == null) {
return s1 == null ? -1 : 1;
}
return CollectionUtils.compareLexicographic(s1.values, s2.values);
};
private static final List NO_VALUES = Collections.emptyList();
/**
* Unmodifiable list (check this in constructors).
*/
private final List values;
/**
* The empty {@link StringSet}.
*/
public static final StringSet EMPTY = of();
/**
* The {@link StringSet} than only contains the {@link StringValue#PATTERN_UNKNOWN}.
*/
public static final StringSet UNKNOWN = of(StringValue.PATTERN_UNKNOWN);
private StringSet(Collection values) {
Checks.isNotNull(values, "values");
if (values.isEmpty()) {
this.values = NO_VALUES;
} else {
final List list = new ArrayList<>();
final Set set = new HashSet<>();
for (final StringValue value : values) {
if (value == null) {
throw new IllegalArgumentException("Null value");
}
if (!set.contains(value)) {
set.add(value);
list.add(value);
}
}
this.values = Collections.unmodifiableList(list);
}
}
/**
* Converts an {@link SItemSet} set to a {@link StringSet}.
*
* @param set The set.
* @return The conversion of {@code set} to a {@link StringSet}.
* @throws IllegalArgumentException When {@code set} can not be converted to a {@link StringSet}.
*/
public static StringSet convert(SItemSet set) {
if (set instanceof StringSet) {
return (StringSet) set;
} else if (set.isEmpty()) {
return EMPTY;
} else if (set.isCompliantWith(StringSet.class)) {
// Should not throw ClassCastException
return new StringSet(convert(set.getItems(), StringValue.class));
} else {
throw new IllegalArgumentException("Can not convert this set to " + StringSet.class.getSimpleName());
}
}
/**
* Creates a {@link StringSet} from the String representation of its content.
*
* @param content The content.
* @return A new instance of {@link StringSet}.
* @throws SyntacticException When {@code content} can not be parsed as a {@link StringSet}.
*/
public static StringSet of(String content) {
return of(SItemsParsing.toStringValues(content));
}
/**
* Creates a {@link StringSet} from an array of {@link StringValue StringValues}.
*
* @param values The values.
* @return A new instance of {@link StringSet}.
* @throws IllegalArgumentException When a value is {@code null}.
*/
public static StringSet of(StringValue... values) {
return of(Arrays.asList(values));
}
/**
* Creates a {@link StringSet} from a collection of {@link StringValue StringValues}.
*
* @param values The values.
* @return A new instance of {@link UncheckedSet}.
* @throws IllegalArgumentException When a value is {@code null}.
*/
public static StringSet of(Collection values) {
return new StringSet(values);
}
@Override
public long getExtent() {
if (contains(StringValue.PATTERN_UNKNOWN)) {
return CountableSet.UNDEFINED_NUMBER_OF_VALUES;
} else {
return values.size();
}
}
@Override
public StringSet getChecked() {
return this;
}
@Override
public StringSet getBest() {
return this;
}
@Override
public boolean isEmpty() {
return values.isEmpty();
}
@Override
public boolean containsRanges() {
return false;
}
@Override
public boolean isSingleton() {
// No intervals
return values.size() == 1
&& !StringValue.PATTERN_UNKNOWN.equals(values.get(0));
}
@Override
public StringValue getSingletonValue() {
checkIsSingleton();
return values.get(0);
}
@Override
public boolean isValid() {
return true;
}
@Override
public boolean isChecked() {
return true;
}
@Override
public List getItems() {
return values;
}
@Override
public boolean contains(SItem item) {
Checks.isNotNull(item, ITEM);
if (item instanceof StringValue) {
return contains((StringValue) item);
} else {
throw nonSupportedItem(item);
}
}
@Override
public StringSet union(SItem item) {
Checks.isNotNull(item, ITEM);
if (item instanceof StringValue) {
return union((StringValue) item);
} else {
throw nonSupportedItem(item);
}
}
@Override
public StringSet union(SItemSet set) {
Checks.isNotNull(set, SET);
if (set.isCompliantWith(getClass())) {
return union(convert(set));
} else {
throw nonSupportedSet(set);
}
}
@Override
public StringSet intersection(SItem item) {
Checks.isNotNull(item, ITEM);
if (item instanceof StringValue) {
return intersection((StringValue) item);
} else {
throw nonSupportedItem(item);
}
}
@Override
public StringSet intersection(SItemSet set) {
Checks.isNotNull(set, SET);
if (set.isCompliantWith(getClass())) {
return intersection(convert(set));
} else {
throw nonSupportedSet(set);
}
}
@Override
public StringSet remove(SItem item) {
Checks.isNotNull(item, ITEM);
if (item instanceof StringValue) {
return remove((StringValue) item);
} else {
throw nonSupportedItem(item);
}
}
@Override
public StringSet remove(SItemSet set) {
Checks.isNotNull(set, SET);
if (set.isCompliantWith(getClass())) {
return remove(convert(set));
} else {
throw nonSupportedSet(set);
}
}
/**
* @return {@code true} if this set contains {@link StringValue#PATTERN_UNKNOWN}.
*/
public boolean containsPatternAny() {
return values.contains(StringValue.PATTERN_UNKNOWN);
}
@Override
public boolean contains(StringValue value) {
// No special handling of PATTERN_ANY is needed
// We want:
// {...} contains A -> false, not true
return values.contains(value);
}
@Override
public boolean contains(StringSet other) {
// No special handling of PATTERN_ANY is needed
// We want:
// {...} contains {A} -> false, not true
return this.values.containsAll(other.values);
}
@Override
public StringSet union(StringValue value) {
Checks.isNotNull(value, VALUE);
// No special handling of PATTERN_ANY is needed
// We don't want to simplify normal values.
// So, {A, ...} union B -> {A, B, ...}, not {...}
if (contains(value)) {
return this;
}
final List tmp = new ArrayList<>();
tmp.addAll(this.values);
tmp.add(value);
return new StringSet(tmp);
}
@Override
public StringSet union(StringSet set) {
Checks.isNotNull(set, SET);
// No special handling of PATTERN_ANY is needed
// We don't want to simplify normal values.
// So, {A, ...} union {B} -> {A, B, ...}, not {...}
if (contains(set)) {
return this;
}
final List tmp = new ArrayList<>();
tmp.addAll(this.values);
for (final StringValue value : set.values) {
if (!tmp.contains(value)) {
tmp.add(value);
}
}
return new StringSet(tmp);
}
@Override
public StringSet intersection(StringValue value) {
Checks.isNotNull(value, VALUE);
// PATTERN_ANY is handled correctly
// {A, ...} intersection B -> {}
// {A, ...} intersection ... -> {...}
if (contains(value)) {
return of(value);
} else {
return EMPTY;
}
}
@Override
public StringSet intersection(StringSet set) {
Checks.isNotNull(set, SET);
// Special handling of PATTERN_ANY
// We want this:
// {A, ...} intersection {B, ...} -> {...}
// {A, ...} intersection {B} -> {}
// {A} intersection {B, ...} -> {}
// {A, B} intersection {B, C} -> {B}
final List tmp = new ArrayList<>();
for (final StringValue value : getItems()) {
if (set.contains(value)) {
tmp.add(value);
}
}
if (tmp.isEmpty()) {
return EMPTY;
} else {
return new StringSet(tmp);
}
}
@Override
public StringSet remove(StringValue value) {
Checks.isNotNull(value, VALUE);
// Special handling of PATTERN_ANY
// We want this:
// {A,B, ...} remove A -> {B, ...}
// {A,B, ...} remove ... -> {A, B}
// This looks strange, but ... represents all unknown values.
if (!contains(value)) {
return this;
}
final List tmp = new ArrayList<>();
tmp.addAll(this.values);
tmp.remove(value);
return new StringSet(tmp);
}
@Override
public StringSet remove(StringSet set) {
Checks.isNotNull(set, SET);
final List tmp = new ArrayList<>();
tmp.addAll(this.values);
tmp.removeAll(set.values);
if (tmp.isEmpty()) {
return EMPTY;
} else {
return new StringSet(tmp);
}
}
/**
* Creates a lexicographic Comparator based on a specific StringValue comparator.
*
* @param comparator The StringValue comparator.
* @return A new lexicographic Comparator based on {@code comparator}.
*/
public static Comparator lexicographicComparator(Comparator comparator) {
return (s1,
s2) -> {
if (s1 == s2) {
return 0;
}
if (s1 == null || s2 == null) {
return s1 == null ? -1 : 1;
}
return CollectionUtils.compareLexicographic(s1.values, s2.values, comparator);
};
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (isEmpty()
&& object instanceof SItemSet
&& ((SItemSet) object).isEmpty()) {
return true;
}
if (!(object instanceof StringSet)) {
return false;
}
final StringSet other = (StringSet) object;
return values.equals(other.values);
}
@Override
public int hashCode() {
return values.hashCode();
}
@Override
public int compareTo(StringSet o) {
if (this == o) {
return 0;
}
if (o == null) {
return 1;
} else {
return CollectionUtils.compareLexicographic(getItems(), o.getItems());
}
}
}