diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/FlagDefinition.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/FlagDefinition.java index 86d96fe..6b8c327 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/FlagDefinition.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/FlagDefinition.java @@ -5,18 +5,19 @@ import java.util.Optional; /** * Represents a single flag defined in a {@link FlagGraph}. * + * @param The type of the flag's value. * @param A set of this type of flags. */ -public interface FlagDefinition { +public interface FlagDefinition { /** - * The alias of the flag. + * The alias of the flag. A composed flag always has an alias. */ - Optional getAlias(); + Optional alias(); /** - * A set containing the value of the flag, or multiple values if it is a composed flag. + * The value of the flag. A composed flag has no value. */ - S values(); + Optional value(); /** * Test if this flag and all its parents are present in the set. diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/FlagGraph.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/FlagGraph.java index fafa1c0..3e44211 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/FlagGraph.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/FlagGraph.java @@ -1,8 +1,7 @@ package fr.louisdevie.tatsuki; -import java.util.Collection; -import java.util.Optional; -import java.util.Set; +import java.lang.reflect.Array; +import java.util.*; /** * Represents a group of flags of type {@link V} and the relationships between @@ -11,71 +10,128 @@ import java.util.Set; * @param The type of the flag values. * @param The type used to contain the flags. */ -public interface FlagGraph { +public abstract class FlagGraph { + private final HashMap> definitionsByValue; + private final HashMap> definitionsByAlias; + + protected FlagGraph(Collection> c) { + this.definitionsByValue = new HashMap<>(); + this.definitionsByAlias = new HashMap<>(); + + for (var definition : c) { + if (definition.value().isPresent()) { + V value = definition.value().get(); + if (this.definitionsByValue.put(value, definition) != null) { + throw new IllegalArgumentException(String.format("Duplicate value \"%s\"", value)); + } + if (definition.alias().isPresent()) { + String alias = definition.alias().get(); + if (this.definitionsByAlias.put(alias, definition) != null) { + throw new IllegalArgumentException(String.format("Duplicate alias \"%s\"", alias)); + } + } + } else { + String alias = definition.alias().orElseThrow(() -> + new IllegalArgumentException("Invalid flag definition: a composed flag must always have an alias") + ); + if (this.definitionsByAlias.put(alias, definition) != null) { + throw new IllegalArgumentException(String.format("Duplicate alias \"%s\"", alias)); + } + } + } + } + /** * Returns the number of definitions in this graph. */ - int size(); + public int size() { + return this.definitionsByValue.size() + + (int) this.definitionsByAlias.values().stream() + .filter(d -> d.value().isEmpty()) + .count(); + } /** * Returns {@code true} if this graph contains no definitions. */ - boolean isEmpty(); - - /** - * Returns {@code true} if this graph contains a definition with the specified alias. - */ - boolean containsAlias(String alias); + public boolean isEmpty() { + return this.definitionsByValue.isEmpty() && this.definitionsByAlias.isEmpty(); + } /** * Returns {@code true} if this graph contains a definition with the specified value. */ - boolean containsValue(V value); + public boolean containsValue(V value) { + return this.definitionsByValue.containsKey(value); + } + + /** + * Returns {@code true} if this graph contains a definition with the specified alias. + */ + public boolean containsAlias(String alias) { + return this.definitionsByAlias.containsKey(alias); + } /** * Returns {@code true} if the specified definition belongs to this graph. */ - boolean containsDefinition(FlagDefinition definition); - - /** - * Returns the flag definition with the specified alias. - * - * @param alias The alias of the flag. - * - * @return An {@link Optional} of the corresponding definition, or an empty - * {@link Optional} if there is no definition with this alias. - */ - Optional> definitionNamed(String alias); + public boolean containsDefinition(FlagDefinition definition) { + return this.definitionsByValue.containsValue(definition) || this.definitionsByAlias.containsValue(definition); + } /** * Returns the flag definition with the specified value. * * @param value The base value of the flag. - * * @return An {@link Optional} of the corresponding definition, or an empty * {@link Optional} if there is no definition with this value. */ - Optional> definitionOf(V value); + public Optional> definitionOf(V value) { + return Optional.ofNullable(this.definitionsByValue.get(value)); + } + + /** + * Returns the flag definition with the specified alias. + * + * @param alias The alias of the flag. + * @return An {@link Optional} of the corresponding definition, or an empty + * {@link Optional} if there is no definition with this alias. + */ + public Optional> definitionNamed(String alias) { + return Optional.ofNullable(this.definitionsByAlias.get(alias)); + } /** * Returns a {@link Set} view of the aliases defined in this graph. */ - Set aliasSet(); + public Set aliasSet() { + return new ReadOnlyKeySet<>(this.definitionsByAlias); + } /** * Returns a {@link Set} view of the values defined in this graph. */ - Set valueSet(); + public Set valueSet() { + return new ReadOnlyKeySet<>(this.definitionsByValue); + } /** - * Returns a list of all the defined flags. + * Returns a {@link Set} view of all the defined flags. */ - Collection> definitions(); + public Set> definitions() { + return new DefinitionSet(); + } + + /** + * Creates a set of flags from a list of values. + */ + @SuppressWarnings("unchecked") + public abstract S of(V... values); /** * Creates a set of flags from a list of aliases. */ - S named(String... aliases); + public abstract S named(String... aliases); /** * Checks whether the first set of flags includes at least one of the flags @@ -86,7 +142,7 @@ public interface FlagGraph { * @param flags - A set of flags. * @param required - The flags to search for in the first set. */ - boolean hasAny(S flags, S required); + public abstract boolean hasAny(S flags, S required); /** * Checks whether the first set of flags includes all the flags from the @@ -97,7 +153,7 @@ public interface FlagGraph { * @param flags - A set of flags. * @param required - The flags to search for in the first set. */ - boolean hasAll(S flags, S required); + public abstract boolean hasAll(S flags, S required); /** * Filters a flag set so that it only contains the flags that were defined @@ -108,7 +164,7 @@ public interface FlagGraph { * @return A new set of flags. * @see #maximum */ - S minimum(S flags); + public abstract S minimum(S flags); /** * Filters a flag set so that it only contains the flags that were defined @@ -119,5 +175,241 @@ public interface FlagGraph { * @return A new set of flags. * @see #minimum */ - S maximum(S flags); + public abstract S maximum(S flags); + + private static class ReadOnlyKeySet implements Set { + private final Set underlying; + + public ReadOnlyKeySet(HashMap map) { + this.underlying = map.keySet(); + } + + @Override + public int size() { + return this.underlying.size(); + } + + @Override + public boolean isEmpty() { + return this.underlying.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.underlying.contains(o); + } + + @Override + public Iterator iterator() { + return this.underlying.iterator(); + } + + @Override + public Object[] toArray() { + return this.underlying.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return this.underlying.toArray(a); + } + + @Override + public boolean add(K key) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + return this.underlying.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + } + + private static class DefinitionIterator implements Iterator> { + private final Iterator> byValueIterator; + private final Iterator> byAliasIterator; + private FlagDefinition nextDefinition; + + public DefinitionIterator(HashMap> byValue, HashMap> byAlias) { + this.byValueIterator = byValue.values().iterator(); + this.byAliasIterator = byAlias.values().iterator(); + this.lookahead(); + } + + private void lookahead() { + FlagDefinition found = null; + if (this.byValueIterator.hasNext()) { + found = this.byValueIterator.next(); + } + while (found == null && this.byAliasIterator.hasNext()) { + found = this.byAliasIterator.next(); + if (found.alias().isPresent()) { + found = null; + } + } + this.nextDefinition = found; + } + + @Override + public boolean hasNext() { + return this.nextDefinition != null; + } + + @Override + public FlagDefinition next() { + var nextDefinition = this.nextDefinition; + if (nextDefinition == null) { + throw new NoSuchElementException(); + } + this.lookahead(); + return nextDefinition; + } + } + + private class DefinitionSet implements Set> { + @Override + public int size() { + return FlagGraph.this.size(); + } + + @Override + public boolean isEmpty() { + return FlagGraph.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + // noinspection SuspiciousMethodCalls + return FlagGraph.this.definitionsByValue.containsValue(o) || + FlagGraph.this.definitionsByAlias.containsValue(o); + } + + @Override + public Iterator> iterator() { + return new DefinitionIterator<>(FlagGraph.this.definitionsByValue, FlagGraph.this.definitionsByAlias); + } + + @Override + public Object[] toArray() { + int maxSize = FlagGraph.this.definitionsByValue.size() + FlagGraph.this.definitionsByAlias.size(); + Object[] array = new Object[maxSize]; + int i = 0; + for (var definition : FlagGraph.this.definitionsByValue.values()) { + array[i++] = definition; + } + for (var definition : FlagGraph.this.definitionsByAlias.values()) { + if (definition.value().isEmpty()) { + array[i++] = definition; + } + } + return Arrays.copyOf(array, i); + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + int maxSize = FlagGraph.this.definitionsByValue.size() + FlagGraph.this.definitionsByAlias.size(); + T[] array = (T[]) Array.newInstance(a.getClass().componentType(), maxSize); + ; + int i = 0; + for (var definition : FlagGraph.this.definitionsByValue.values()) { + array[i++] = (T) definition; + } + for (var definition : FlagGraph.this.definitionsByAlias.values()) { + if (definition.value().isEmpty()) { + array[i++] = (T) definition; + } + } + if (i > a.length) { + return Arrays.copyOf(array, i); + } else { + Arrays.fill(a, null); + System.arraycopy(array, 0, a, 0, i); + return array; + } + } + + @Override + public boolean add(FlagDefinition vsFlagDefinition) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + boolean result = true; + for (Object o : c) { + result = this.contains(o); + if (!result) break; + } + return result; + } + + @Override + public boolean addAll(Collection> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Set otherSet)) return false; + + int count = 0; + for (var definition : FlagGraph.this.definitionsByValue.values()) { + if (!otherSet.contains(definition)) return false; + count++; + } + for (var definition : FlagGraph.this.definitionsByAlias.values()) { + if (definition.value().isEmpty()) { + if (!otherSet.contains(definition)) return false; + count++; + } + } + return count == otherSet.size(); + } + } } diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagGraph.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagGraph.java index 75d6e78..df8dab0 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagGraph.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagGraph.java @@ -9,56 +9,15 @@ import java.util.List; import java.util.Optional; import java.util.Set; -public class BigBitFlagGraph implements FlagGraph { +public class BigBitFlagGraph extends FlagGraph { - @Override - public int size() { - return 0; + protected BigBitFlagGraph(Collection> c) { + super(c); } @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean containsAlias(String alias) { - return false; - } - - @Override - public boolean containsValue(BigInteger value) { - return false; - } - - @Override - public boolean containsDefinition(FlagDefinition definition) { - return false; - } - - @Override - public Optional> definitionNamed(String alias) { - return Optional.empty(); - } - - @Override - public Optional> definitionOf(BigInteger value) { - return Optional.empty(); - } - - @Override - public Set aliasSet() { - return Set.of(); - } - - @Override - public Set valueSet() { - return Set.of(); - } - - @Override - public Collection> definitions() { - return List.of(); + public BigInteger of(BigInteger... values) { + return null; } @Override diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagsIterator.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagIterator.java similarity index 87% rename from java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagsIterator.java rename to java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagIterator.java index d2e13e2..b5c1067 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagsIterator.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BigBitFlagIterator.java @@ -4,11 +4,11 @@ import java.math.BigInteger; import java.util.Iterator; import java.util.NoSuchElementException; -public class BigBitFlagsIterator implements Iterator { +public class BigBitFlagIterator implements Iterator { private BigInteger value; private BigInteger current; - public BigBitFlagsIterator(BigInteger value) { + public BigBitFlagIterator(BigInteger value) { this.value = value; this.current = BigInteger.ONE; } diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagGraph.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagGraph.java index 0209457..1791459 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagGraph.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagGraph.java @@ -8,57 +8,16 @@ import java.util.List; import java.util.Optional; import java.util.Set; -public class BitFlagGraph implements FlagGraph { +public class BitFlagGraph extends FlagGraph { + protected BitFlagGraph(Collection> c) { + super(c); + } + @Override - public int size() { + public Integer of(Integer... values) { return 0; } - @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean containsAlias(String alias) { - return false; - } - - @Override - public boolean containsValue(Integer value) { - return false; - } - - @Override - public boolean containsDefinition(FlagDefinition definition) { - return false; - } - - @Override - public Optional> definitionNamed(String alias) { - return Optional.empty(); - } - - @Override - public Optional> definitionOf(Integer value) { - return Optional.empty(); - } - - @Override - public Set aliasSet() { - return Set.of(); - } - - @Override - public Set valueSet() { - return Set.of(); - } - - @Override - public Collection> definitions() { - return List.of(); - } - @Override public Integer named(String... aliases) { return 0; @@ -83,4 +42,5 @@ public class BitFlagGraph implements FlagGraph { public Integer maximum(Integer flags) { return 0; } + } diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagsIterator.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagIterator.java similarity index 86% rename from java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagsIterator.java rename to java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagIterator.java index f2c59a4..a6e8bb1 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagsIterator.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlagIterator.java @@ -3,11 +3,11 @@ package fr.louisdevie.tatsuki.bitflags; import java.util.Iterator; import java.util.NoSuchElementException; -public class BitFlagsIterator implements Iterator { +public class BitFlagIterator implements Iterator { private int value; private int current; - public BitFlagsIterator(int value) { + public BitFlagIterator(int value) { this.value = value; this.current = 1; } diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlags.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlags.java index 4a90fc0..da2cefb 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlags.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/BitFlags.java @@ -109,7 +109,7 @@ public final class BitFlags { private record IterableInteger(int value) implements Iterable { public Iterator iterator() { - return new BitFlagsIterator(this.value); + return new BitFlagIterator(this.value); } } @@ -122,7 +122,7 @@ public final class BitFlags { private record IterableLong(long value) implements Iterable { public Iterator iterator() { - return new LongBitFlagsIterator(this.value); + return new LongBitFlagIterator(this.value); } } @@ -135,7 +135,7 @@ public final class BitFlags { private record IterableBigInteger(BigInteger value) implements Iterable { public Iterator iterator() { - return new BigBitFlagsIterator(this.value); + return new BigBitFlagIterator(this.value); } } } diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagGraph.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagGraph.java index 3d3c99c..84b43c5 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagGraph.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagGraph.java @@ -8,56 +8,15 @@ import java.util.List; import java.util.Optional; import java.util.Set; -public class LongBitFlagGraph implements FlagGraph { +public class LongBitFlagGraph extends FlagGraph { - @Override - public int size() { - return 0; + protected LongBitFlagGraph(Collection> c) { + super(c); } @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean containsAlias(String alias) { - return false; - } - - @Override - public boolean containsValue(Long value) { - return false; - } - - @Override - public boolean containsDefinition(FlagDefinition definition) { - return false; - } - - @Override - public Optional> definitionNamed(String alias) { - return Optional.empty(); - } - - @Override - public Optional> definitionOf(Long value) { - return Optional.empty(); - } - - @Override - public Set aliasSet() { - return Set.of(); - } - - @Override - public Set valueSet() { - return Set.of(); - } - - @Override - public Collection> definitions() { - return List.of(); + public Long of(Long... values) { + return 0L; } @Override diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagsIterator.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagIterator.java similarity index 85% rename from java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagsIterator.java rename to java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagIterator.java index 93e4272..f29a27c 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagsIterator.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/bitflags/LongBitFlagIterator.java @@ -3,11 +3,11 @@ package fr.louisdevie.tatsuki.bitflags; import java.util.Iterator; import java.util.NoSuchElementException; -public class LongBitFlagsIterator implements Iterator { +public class LongBitFlagIterator implements Iterator { private long value; private long current; - public LongBitFlagsIterator(long value) { + public LongBitFlagIterator(long value) { this.value = value; this.current = 1L; } diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/FlagSetBuilder.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/FlagSetBuilder.java new file mode 100644 index 0000000..2677f7c --- /dev/null +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/FlagSetBuilder.java @@ -0,0 +1,89 @@ +package fr.louisdevie.tatsuki.builders; + +import fr.louisdevie.tatsuki.builders.syntax.AsOrComposeValues; +import fr.louisdevie.tatsuki.builders.syntax.AsOrRequiresOrDefineOrComposeValues; +import fr.louisdevie.tatsuki.builders.syntax.RequiresOrDefineOrComposeValues; + +import java.util.ArrayList; +import java.util.HashMap; + +public abstract class FlagSetBuilder implements AsOrRequiresOrDefineOrComposeValues, AsOrComposeValues { + private final ArrayList> definitions; + private final HashMap> aliasToDefinition; + private final HashMap> valueToDefinition; + + private PartialFlagDefinition currentDefinition; + + protected FlagSetBuilder() { + this.definitions = new ArrayList<>(); + this.aliasToDefinition = new HashMap<>(); + this.valueToDefinition = new HashMap<>(); + + this.currentDefinition = null; + } + + @Override + public RequiresOrDefineOrComposeValues as(String alias) { + if (this.currentDefinition == null) { + throw new IllegalStateException("Attempt to set an alias while there is no current definition"); + } + this.currentDefinition.setAlias(alias); + return this; + } + + @Override + public RequiresOrDefineOrComposeValues requires(String... aliases) { + throw new RuntimeException("Not implemented"); + } + + @Override + @SuppressWarnings("unchecked") + public RequiresOrDefineOrComposeValues requiresValues(V... values) { + throw new RuntimeException("Not implemented"); + } + + @Override + public AsOrRequiresOrDefineOrComposeValues defineValue(V value) { + this.finishCurrentDefinition(); + this.currentDefinition = new PartialValueFlagDefinition<>(value); + return this; + } + + @Override + public AsOrComposeValues compose(String... aliases) { + throw new RuntimeException("Not implemented"); + } + + @Override + @SuppressWarnings("unchecked") + public AsOrComposeValues composeValues(V... values) { + if (this.currentDefinition != null && !this.currentDefinition.isComposition()) { + this.finishCurrentDefinition(); + } + this.currentDefinition = new PartialComposedFlagDefinition<>(); + + for (V value: values) { + this.currentDefinition.addParent(this.referenceToValue(value)); + } + return this; + } + + protected void finishCurrentDefinition() { + if (this.currentDefinition != null) { + this.definitions.add(this.currentDefinition); + this.currentDefinition = null; + } + } + + protected ArrayList> partialDefinitions() { + return this.definitions; + } + + private PartialFlagDefinition referenceToValue(V value) { + var definition = this.valueToDefinition.get(value); + if (definition == null) { + definition = new ForwardReferenceByValue<>(value); + } + return definition; + } +} diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/ForwardReferenceByValue.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/ForwardReferenceByValue.java new file mode 100644 index 0000000..879ac87 --- /dev/null +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/ForwardReferenceByValue.java @@ -0,0 +1,34 @@ +package fr.louisdevie.tatsuki.builders; + +class ForwardReferenceByValue implements PartialFlagDefinition { + private final V value; + + public ForwardReferenceByValue(V value) { + this.value = value; + } + + @Override + public void setAlias(String alias) { + throw new UnsupportedOperationException("Attempt to modify a forward reference"); + } + + @Override + public void addParent(PartialFlagDefinition parent) { + throw new UnsupportedOperationException("Attempt to modify a forward reference"); + } + + @Override + public boolean isComposition() { + return false; + } + + @Override + public String alias() { + return ""; + } + + @Override + public V value() { + return null; + } +} diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialComposedFlagDefinition.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialComposedFlagDefinition.java new file mode 100644 index 0000000..d4dd123 --- /dev/null +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialComposedFlagDefinition.java @@ -0,0 +1,31 @@ +package fr.louisdevie.tatsuki.builders; + +class PartialComposedFlagDefinition implements PartialFlagDefinition { + public PartialComposedFlagDefinition() { + } + + @Override + public void setAlias(String alias) { + throw new RuntimeException("Not implemented"); + } + + @Override + public void addParent(PartialFlagDefinition parent) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean isComposition() { + return false; + } + + @Override + public String alias() { + return ""; + } + + @Override + public V value() { + return null; + } +} diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialFlagDefinition.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialFlagDefinition.java new file mode 100644 index 0000000..980f8bd --- /dev/null +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialFlagDefinition.java @@ -0,0 +1,13 @@ +package fr.louisdevie.tatsuki.builders; + +public interface PartialFlagDefinition { + void setAlias(String alias); + + void addParent(PartialFlagDefinition parent); + + boolean isComposition(); + + String alias(); + + V value(); +} diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialValueFlagDefinition.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialValueFlagDefinition.java new file mode 100644 index 0000000..fc4c79f --- /dev/null +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/PartialValueFlagDefinition.java @@ -0,0 +1,38 @@ +package fr.louisdevie.tatsuki.builders; + +class PartialValueFlagDefinition implements PartialFlagDefinition { + private final V value; + private String alias; + + public PartialValueFlagDefinition(V value) { + this.value = value; + } + + @Override + public void setAlias(String alias) { + if (alias == null) { + throw new NullPointerException("null flag alias"); + } + this.alias = alias; + } + + @Override + public void addParent(PartialFlagDefinition parent) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean isComposition() { + return false; + } + + @Override + public String alias() { + return this.alias; + } + + @Override + public V value() { + return this.value; + } +} diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/syntax/DefineOrComposeValues.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/syntax/DefineOrComposeValues.java index 4ebf916..68d4316 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/syntax/DefineOrComposeValues.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/syntax/DefineOrComposeValues.java @@ -1,7 +1,7 @@ package fr.louisdevie.tatsuki.builders.syntax; -public interface DefineOrComposeValues extends Root { +public interface DefineOrComposeValues { /** * Define a flag with the given value. * @@ -26,4 +26,9 @@ public interface DefineOrComposeValues extends Root { */ @SuppressWarnings("unchecked") AsOrComposeValues composeValues(V... values); + + /** + * Build a flag set with the flags previously defined. + */ + R build(); } diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/syntax/Root.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/syntax/Root.java deleted file mode 100644 index badf502..0000000 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/builders/syntax/Root.java +++ /dev/null @@ -1,12 +0,0 @@ -package fr.louisdevie.tatsuki.builders.syntax; - -/** - * Builder syntax root. - * @param The result type produced by the builder. - */ -public interface Root { - /** - * Build a flag set with the flags previously defined. - */ - R build(); -} diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/CollectionFlagGraphBuilder.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/CollectionFlagGraphBuilder.java deleted file mode 100644 index c129e06..0000000 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/CollectionFlagGraphBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -package fr.louisdevie.tatsuki.collections; - -import fr.louisdevie.tatsuki.builders.syntax.AsOrComposeValues; -import fr.louisdevie.tatsuki.builders.syntax.AsOrRequiresOrDefineOrComposeValues; -import fr.louisdevie.tatsuki.builders.syntax.DefineOrComposeValues; - -class CollectionFlagGraphBuilder implements DefineOrComposeValues> { - @Override - public AsOrRequiresOrDefineOrComposeValues> defineValue(E value) { - return null; - } - - @Override - public AsOrComposeValues> compose(String... aliases) { - return null; - } - - @Override - public AsOrComposeValues> composeValues(E... values) { - return null; - } - - @Override - public SetFlagGraph build() { - return null; - } -} diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/SetFlagDefinition.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/SetFlagDefinition.java index 4295098..0dadbd4 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/SetFlagDefinition.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/SetFlagDefinition.java @@ -1,19 +1,32 @@ package fr.louisdevie.tatsuki.collections; import fr.louisdevie.tatsuki.FlagDefinition; +import fr.louisdevie.tatsuki.builders.PartialFlagDefinition; import java.util.Optional; import java.util.Set; -class SetFlagDefinition implements FlagDefinition> { - @Override - public Optional getAlias() { - return Optional.empty(); +class SetFlagDefinition implements FlagDefinition> { + private final String alias; + private final E value; + + private SetFlagDefinition(String alias, E value) { + this.alias = alias; + this.value = value; + } + + public static SetFlagDefinition fromPartial(PartialFlagDefinition partialFlagDefinition) { + return new SetFlagDefinition<>(partialFlagDefinition.alias(), partialFlagDefinition.value()); } @Override - public Set values() { - return Set.of(); + public Optional alias() { + return Optional.ofNullable(this.alias); + } + + @Override + public Optional value() { + return Optional.ofNullable(this.value); } @Override diff --git a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/SetFlagGraph.java b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/SetFlagGraph.java index a520971..2e5ce69 100644 --- a/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/SetFlagGraph.java +++ b/java/tatsuki/src/main/java/fr/louisdevie/tatsuki/collections/SetFlagGraph.java @@ -1,71 +1,27 @@ package fr.louisdevie.tatsuki.collections; import fr.louisdevie.tatsuki.FlagGraph; +import fr.louisdevie.tatsuki.builders.FlagSetBuilder; import fr.louisdevie.tatsuki.builders.syntax.DefineOrComposeValues; -import fr.louisdevie.tatsuki.FlagDefinition; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -public class SetFlagGraph implements FlagGraph> { - public static DefineOrComposeValues> builder() { - return new CollectionFlagGraphBuilder<>(); - } +import java.util.*; +import java.util.stream.Collectors; +public class SetFlagGraph extends FlagGraph> { SetFlagGraph(Collection> c) { + super(c); + } + public static DefineOrComposeValues> builder() { + return new Builder<>(); } @Override - public int size() { - return 0; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean containsAlias(String alias) { - return false; - } - - @Override - public boolean containsValue(E value) { - return false; - } - - @Override - public boolean containsDefinition(FlagDefinition> definition) { - return false; - } - - @Override - public Optional>> definitionNamed(String alias) { - return Optional.empty(); - } - - @Override - public Optional>> definitionOf(E value) { - return Optional.empty(); - } - - @Override - public Set aliasSet() { - return Set.of(); - } - - @Override - public Set valueSet() { - return Set.of(); - } - - @Override - public Collection>> definitions() { - return List.of(); + @SuppressWarnings("unchecked") + public Set of(E... values) { + return Arrays.stream(values) + .filter(this::containsValue) + .collect(Collectors.toUnmodifiableSet()); } @Override @@ -92,4 +48,13 @@ public class SetFlagGraph implements FlagGraph> { public Set maximum(Set flags) { return Set.of(); } + + static class Builder extends FlagSetBuilder> { + @Override + public SetFlagGraph build() { + this.finishCurrentDefinition(); + var definitions = this.partialDefinitions().stream().map(SetFlagDefinition::fromPartial).toList(); + return new SetFlagGraph<>(definitions); + } + } } diff --git a/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/FlagGraphTests.java b/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/FlagGraphTests.java new file mode 100644 index 0000000..e169af8 --- /dev/null +++ b/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/FlagGraphTests.java @@ -0,0 +1,146 @@ +package fr.louisdevie.tatsuki; + +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +public class FlagGraphTests { + @Test + public void size() { + final var noFlags = new MockFlagGraph(); + + assertEquals(0, noFlags.size()); + + final var flags = new MockFlagGraph( + new MockFlagDefinition("A", "1"), + new MockFlagDefinition("B", null), + new MockFlagDefinition(null, "2") + ); + + assertEquals(3, flags.size()); + } + + @Test + public void isEmpty() { + final var noFlags = new MockFlagGraph(); + + assertTrue(noFlags.isEmpty()); + + final var flags = new MockFlagGraph( + new MockFlagDefinition("A", "x"), + new MockFlagDefinition("B", null), + new MockFlagDefinition(null, "y") + ); + + assertFalse(flags.isEmpty()); + } + + @Test + public void containsValue() { + final var flags = new MockFlagGraph( + new MockFlagDefinition("A", "x"), + new MockFlagDefinition("B", null), + new MockFlagDefinition(null, "y") + ); + + assertTrue(flags.containsValue("A")); + assertTrue(flags.containsValue("B")); + assertFalse(flags.containsValue("C")); + } + + @Test + public void containsAlias() { + final var flags = new MockFlagGraph( + new MockFlagDefinition("A", "x"), + new MockFlagDefinition("B", null), + new MockFlagDefinition(null, "y") + ); + + assertTrue(flags.containsAlias("x")); + assertTrue(flags.containsAlias("y")); + assertFalse(flags.containsAlias("z")); + } + + @Test + public void containsDefinition() { + final var inGraph = new MockFlagDefinition("A", "x"); + final var notInGraph = new MockFlagDefinition("B", "y"); + final var flags = new MockFlagGraph(inGraph); + + assertTrue(flags.containsDefinition(inGraph)); + assertFalse(flags.containsDefinition(notInGraph)); + } + + @Test + public void definitionOf() { + final var x = new MockFlagDefinition("A", "x"); + final var y = new MockFlagDefinition("B", "y"); + final var flags = new MockFlagGraph(x, y); + + assertTrue(flags.definitionOf("A").isPresent()); + assertSame(x, flags.definitionOf("A").get()); + + assertTrue(flags.definitionOf("B").isPresent()); + assertSame(y, flags.definitionOf("B").get()); + + assertTrue(flags.definitionOf("C").isEmpty()); + } + + @Test + public void definitionNamed() { + final var x = new MockFlagDefinition("A", "x"); + final var y = new MockFlagDefinition("B", "y"); + final var flags = new MockFlagGraph(x, y); + + assertTrue(flags.definitionNamed("x").isPresent()); + assertSame(x, flags.definitionNamed("x").get()); + + assertTrue(flags.definitionNamed("y").isPresent()); + assertSame(y, flags.definitionNamed("y").get()); + + assertTrue(flags.definitionNamed("z").isEmpty()); + } + + @Test + public void aliasSet() { + final var flags = new MockFlagGraph( + new MockFlagDefinition("A", "x"), + new MockFlagDefinition("B", null), + new MockFlagDefinition(null, "y") + ); + + assertEquals(Set.of("x", "y"), flags.aliasSet()); + + assertThrows(UnsupportedOperationException.class, () -> flags.aliasSet().remove("x")); + assertThrows(UnsupportedOperationException.class, () -> flags.aliasSet().add("z")); + } + + @Test + public void valueSet() { + final var flags = new MockFlagGraph( + new MockFlagDefinition("A", "x"), + new MockFlagDefinition("B", null), + new MockFlagDefinition(null, "y") + ); + + assertEquals(Set.of("A", "B"), flags.valueSet()); + + assertThrows(UnsupportedOperationException.class, () -> flags.valueSet().remove("A")); + assertThrows(UnsupportedOperationException.class, () -> flags.valueSet().add("C")); + } + + @Test + public void definitions() { + final var x = new MockFlagDefinition("A", "x"); + final var B = new MockFlagDefinition("B", null); + final var y = new MockFlagDefinition(null, "y"); + final var flags = new MockFlagGraph(x, B, y); + + assertEquals(Set.of(x, B, y), flags.definitions()); + + assertThrows(UnsupportedOperationException.class, () -> flags.valueSet().remove("A")); + assertThrows(UnsupportedOperationException.class, () -> flags.valueSet().add("C")); + } +} diff --git a/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/MockFlagDefinition.java b/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/MockFlagDefinition.java new file mode 100644 index 0000000..2a322ca --- /dev/null +++ b/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/MockFlagDefinition.java @@ -0,0 +1,38 @@ +package fr.louisdevie.tatsuki; + +import java.util.Optional; + +public class MockFlagDefinition implements FlagDefinition { + private final String value; + private final String alias; + + public MockFlagDefinition(String value, String alias) { + this.value = value; + this.alias = alias; + } + + @Override + public Optional alias() { + return Optional.ofNullable(this.alias); + } + + @Override + public Optional value() { + return Optional.ofNullable(this.value); + } + + @Override + public boolean isIn(String set) { + throw new UnsupportedOperationException(); + } + + @Override + public String addTo(String set) { + throw new UnsupportedOperationException(); + } + + @Override + public String removeFrom(String set) { + throw new UnsupportedOperationException(); + } +} diff --git a/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/MockFlagGraph.java b/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/MockFlagGraph.java new file mode 100644 index 0000000..dd3cbdc --- /dev/null +++ b/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/MockFlagGraph.java @@ -0,0 +1,40 @@ +package fr.louisdevie.tatsuki; + +import java.util.Arrays; + +public class MockFlagGraph extends FlagGraph { + @SafeVarargs + public MockFlagGraph(FlagDefinition... definitions) { + super(Arrays.stream(definitions).toList()); + } + + @Override + public String of(String... values) { + throw new UnsupportedOperationException(); + } + + @Override + public String named(String... aliases) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasAny(String flags, String required) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasAll(String flags, String required) { + throw new UnsupportedOperationException(); + } + + @Override + public String minimum(String flags) { + throw new UnsupportedOperationException(); + } + + @Override + public String maximum(String flags) { + throw new UnsupportedOperationException(); + } +} diff --git a/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/collections/CollectionFlagGraphTests.java b/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/collections/SetFlagGraphTests.java similarity index 68% rename from java/tatsuki/src/test/java/fr/louisdevie/tatsuki/collections/CollectionFlagGraphTests.java rename to java/tatsuki/src/test/java/fr/louisdevie/tatsuki/collections/SetFlagGraphTests.java index 2a55d5e..0a5c9b7 100644 --- a/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/collections/CollectionFlagGraphTests.java +++ b/java/tatsuki/src/test/java/fr/louisdevie/tatsuki/collections/SetFlagGraphTests.java @@ -6,7 +6,20 @@ import java.util.Set; import static org.junit.jupiter.api.Assertions.*; -public class CollectionFlagGraphTests { +public class SetFlagGraphTests { + @Test + public void of() { + final var flags = SetFlagGraph.builder() + .defineValue(12).as("a") + .defineValue(45).as("b") + .defineValue(78).as("c") + .build(); + + assertEquals(Set.of(), flags.of()); + assertEquals(Set.of(12), flags.of(12)); + assertEquals(Set.of(12, 78), flags.of(12, 54, 78)); + } + @Test public void named() { final var flags = SetFlagGraph.builder() @@ -16,9 +29,9 @@ public class CollectionFlagGraphTests { .compose("a", "b").as("ab") .build(); - assertEquals(Set.of(), flags.named()) ; - assertEquals(Set.of(12), flags.named("a")) ; - assertEquals(Set.of(12, 45, 78), flags.named("ab", "c")) ; + assertEquals(Set.of(), flags.named()); + assertEquals(Set.of(12), flags.named("a")); + assertEquals(Set.of(12, 45, 78), flags.named("ab", "c")); } @Test @@ -59,11 +72,11 @@ public class CollectionFlagGraphTests { .defineValue(78).requiresValues(45) .build(); - assertEquals(Set.of(), flags.maximum(Set.of())) ; - assertEquals(Set.of(12), flags.maximum(Set.of(12))) ; - assertEquals(Set.of(12, 45), flags.maximum(Set.of(45))) ; - assertEquals(Set.of(12, 45, 78), flags.maximum(Set.of(78))) ; - assertEquals(Set.of(), flags.maximum(Set.of(99))) ; + assertEquals(Set.of(), flags.maximum(Set.of())); + assertEquals(Set.of(12), flags.maximum(Set.of(12))); + assertEquals(Set.of(12, 45), flags.maximum(Set.of(45))); + assertEquals(Set.of(12, 45, 78), flags.maximum(Set.of(78))); + assertEquals(Set.of(), flags.maximum(Set.of(99))); } @Test @@ -74,12 +87,12 @@ public class CollectionFlagGraphTests { .defineValue(78).requiresValues(45) .build(); - assertEquals(Set.of(), flags.minimum(Set.of())) ; - assertEquals(Set.of(12), flags.minimum(Set.of(12))) ; - assertEquals(Set.of(), flags.minimum(Set.of(45))) ; - assertEquals(Set.of(12, 45), flags.minimum(Set.of(12, 45))) ; - assertEquals(Set.of(12), flags.minimum(Set.of(12, 78))) ; - assertEquals(Set.of(12, 45, 78), flags.minimum(Set.of(12, 45, 78))) ; - assertEquals(Set.of(), flags.minimum(Set.of(99))) ; + assertEquals(Set.of(), flags.minimum(Set.of())); + assertEquals(Set.of(12), flags.minimum(Set.of(12))); + assertEquals(Set.of(), flags.minimum(Set.of(45))); + assertEquals(Set.of(12, 45), flags.minimum(Set.of(12, 45))); + assertEquals(Set.of(12), flags.minimum(Set.of(12, 78))); + assertEquals(Set.of(12, 45, 78), flags.minimum(Set.of(12, 45, 78))); + assertEquals(Set.of(), flags.minimum(Set.of(99))); } }