diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/Node.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/Node.java new file mode 100644 index 0000000..e4625f6 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/Node.java @@ -0,0 +1,43 @@ +package org.xbib.datastructures.trie; + +import java.util.HashMap; +import java.util.Map; + +public class Node { + + private Character key; + + private T value; + + private boolean terminal; + + private final Map> children = new HashMap<>(); + + public Character getKey() { + return key; + } + + public void setKey(Character key) { + this.key = key; + } + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + public boolean isTerminal() { + return terminal; + } + + public void setTerminal(boolean terminal) { + this.terminal = terminal; + } + + public Map> getChildren() { + return children; + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/Trie.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/Trie.java new file mode 100644 index 0000000..b8b04aa --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/Trie.java @@ -0,0 +1,24 @@ +package org.xbib.datastructures.trie; + +import java.util.List; +import java.util.Set; + +/** + * https://stevedaskam.wordpress.com/2009/05/28/trie-structures/ + * + * @param + */ +public interface Trie { + + void add(String key, T value); + + T search(String key); + + List startsWith(String prefix); + + boolean contains(String key); + + Set getAllKeys(); + + int size(); +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/TrieImpl.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/TrieImpl.java new file mode 100644 index 0000000..8d5c9d1 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/TrieImpl.java @@ -0,0 +1,127 @@ +package org.xbib.datastructures.trie; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class TrieImpl implements Trie { + + private final Node node = new Node<>(); + + @Override + public void add(String key, T value) { + addNode(node, key, 0, value); + } + + @Override + public T search(String key) { + return findKey(node, key); + } + + @Override + public List startsWith(String prefix) { + List list = new ArrayList<>(); + Node node = this.node; + for (char c : prefix.toCharArray()) { + node = node.getChildren().get(c); + if (node == null) { + break; + } + } + if (node != null) { + getValues(node, list); + } + return list; + } + + @Override + public boolean contains(String key) { + return hasKey(node, key); + } + + @Override + public Set getAllKeys() { + Set keySet = new HashSet<>(); + getKeys(node, "", keySet); + return keySet; + } + + @Override + public int size() { + return getAllKeys().size(); + } + + private void getValues(Node currNode, List valueList) { + if (currNode.isTerminal()) { + valueList.add(currNode.getValue()); + } + Map> children = currNode.getChildren(); + for (Map.Entry> entry : children.entrySet()) { + getValues(entry.getValue(), valueList); + } + } + + private void getKeys(Node currNode, String key, Set keySet) { + if (currNode.isTerminal()) { + keySet.add(key); + } + Map> children = currNode.getChildren(); + for (Map.Entry> entry : children.entrySet()) { + String s = key + entry.getValue().getKey(); + getKeys(entry.getValue(), s, keySet); + } + } + + private T findKey(Node currNode, String key) { + char ch = key.length() > 0 ? key.charAt(0) : '\0'; + if (currNode.getChildren().containsKey(ch)) { + Node nextNode = currNode.getChildren().get(ch); + if (key.length() <= 1) { + if (nextNode.isTerminal()) { + return nextNode.getValue(); + } + } else { + return findKey(nextNode, key.substring(1)); + } + } + return null; + } + + private boolean hasKey(Node currNode, String key) { + char c = key.length() > 0 ? key.charAt(0) : '\0'; + if (currNode.getChildren().containsKey(c)) { + Node nextNode = currNode.getChildren().get(c); + if (key.length() <= 1) { + return nextNode.isTerminal(); + } else { + return hasKey(nextNode, key.substring(1)); + } + } + return false; + } + + private void addNode(Node currNode, String key, int pos, T value) { + Character c = pos < key.length() ? key.charAt(pos) : '\0'; + Node nextNode = currNode.getChildren().get(c); + if (nextNode == null) { + nextNode = new Node<>(); + nextNode.setKey(c); + if (pos < key.length() - 1) { + addNode(nextNode, key, pos + 1, value); + } else { + nextNode.setValue(value); + nextNode.setTerminal(true); + } + currNode.getChildren().put(c, nextNode); + } else { + if (pos < key.length() - 1) { + addNode(nextNode, key, pos + 1, value); + } else { + nextNode.setValue(value); + nextNode.setTerminal(true); + } + } + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/compact/Node.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/compact/Node.java new file mode 100644 index 0000000..94bdadf --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/compact/Node.java @@ -0,0 +1,37 @@ +package org.xbib.datastructures.trie.compact; + +class Node { + + boolean isLeaf; + + char[] value; + Node[] children; + + // temp variable, carrying information about potential mismatch used for inserting + int divergeKeyIndex; + int divergePatternIndex; + + Node copyNode(char[] value) { + Node node = new Node(); + node.value = value; + node.isLeaf = this.isLeaf; + node.children = this.children; + return node; + } + + static Node innerNode(char[] value) { + Node node = new Node(); + node.isLeaf = false; + node.value = value; + node.children = new Node[26]; + return node; + } + + static Node leafNode(char[] value) { + Node node = new Node(); + node.isLeaf = true; + node.value = value; + node.children = new Node[26]; + return node; + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/compact/Trie.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/compact/Trie.java new file mode 100644 index 0000000..dcd82f1 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/compact/Trie.java @@ -0,0 +1,115 @@ +package org.xbib.datastructures.trie.compact; + +import java.util.Arrays; + +/** + * https://leetcode.com/problems/implement-trie-prefix-tree/discuss/467046/Java-Radix-tree-(compact-prefix-tree)-beats-99.7-runtime-and-100-memory + */ + +public class Trie { + + private static int indexOf(char c) { + return c - 'a'; + } + + private final Node root; + + public Trie() { + root = Node.innerNode(new char[0]); + } + + public void insert(String word) { + char[] key = word.toCharArray(); + Node lastNode = search(root, key, 0); + if (lastNode.divergeKeyIndex == key.length) { + if (lastNode.divergePatternIndex == lastNode.value.length) { + lastNode.isLeaf = true; + } else {// we need to reduce length of the compressed pattern in the current node, + // make it node leaf, and create child that carry over the original children/isLeaf + char[] childValue = Arrays.copyOfRange(lastNode.value, + lastNode.divergePatternIndex, lastNode.value.length); + Node childNode = lastNode.copyNode(childValue); + lastNode.value = Arrays.copyOfRange(lastNode.value, + 0, lastNode.divergePatternIndex); + lastNode.isLeaf = true; + lastNode.children = new Node[26]; + lastNode.children[indexOf(childValue[0])] = childNode; + } + } else { + if (lastNode.divergePatternIndex < lastNode.value.length) {// If diverge happens in middle of both array + // we need to reduce length of the compressed pattern in the current node + // create one leaf node for the new key, one child that carry over the original children/isLeaf + Node newLeaf = Node.leafNode(Arrays.copyOfRange(key, lastNode.divergeKeyIndex, key.length)); + Node newChild = lastNode.copyNode(Arrays.copyOfRange(lastNode.value, + lastNode.divergePatternIndex, lastNode.value.length)); + lastNode.children = new Node[26]; + lastNode.children[indexOf(newLeaf.value[0])] = newLeaf; + lastNode.children[indexOf(newChild.value[0])] = newChild; + lastNode.isLeaf = false; + lastNode.value = Arrays.copyOfRange(lastNode.value, 0, lastNode.divergePatternIndex); + } else { // pattern is shorter than the key + // we need to create a left node for the new key, and append it to the original ndoe + Node newLeaf = Node.leafNode(Arrays.copyOfRange(key, lastNode.divergeKeyIndex, key.length)); + lastNode.children[indexOf(newLeaf.value[0])] = newLeaf; + } + } + } + + /** Returns if the word is in the trie. */ + public boolean search(String word) { + char[] key = word.toCharArray(); + Node lastNode = search(root, key, 0); + // if we run out of key and pattern, and node is leaf, then key exists + return lastNode.divergeKeyIndex == key.length && + lastNode.divergePatternIndex == lastNode.value.length && + lastNode.isLeaf; + } + + /** Returns if there is any word in the trie that starts with the given prefix. */ + public boolean startsWith(String prefix) { + char[] key = prefix.toCharArray(); + Node lastNode = search(root, key, 0); + // if we run out of key, than prefix exists + return lastNode.divergeKeyIndex == key.length; + } + + // Return the last node in the search path + private Node search(Node node, char[] key, int begin) { + int patternMismatchIndex = compare(key, begin, node.value); + int divergeKeyIndex = patternMismatchIndex + begin; + + if (divergeKeyIndex >= key.length) {// If we run out of keys (could be match or prefix) + node.divergeKeyIndex = divergeKeyIndex; + node.divergePatternIndex = patternMismatchIndex; + return node; + } else { + if (patternMismatchIndex >= node.value.length) {// if we run out of values(continue search child) + Node nextNode = node.children[indexOf(key[divergeKeyIndex])]; + if (nextNode == null) { // we've reach the end + node.divergeKeyIndex = divergeKeyIndex; + node.divergePatternIndex = patternMismatchIndex; + return node; + } else { + return search(nextNode, key, divergeKeyIndex); + } + } else {// if mismatch happens in the middle of pattern + node.divergeKeyIndex = divergeKeyIndex; + node.divergePatternIndex = patternMismatchIndex; + return node; + } + } + } + + private static int compare(char[] src, int begin, char[] pattern) { + int srcIndex = begin; + int patternIndex = 0; + while (srcIndex < src.length && patternIndex < pattern.length) { + if (src[srcIndex] != pattern[srcIndex - begin]) { + break; // src and pattern diverges + } + srcIndex ++; + patternIndex ++; + } + return patternIndex; + } +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/EmptyIterator.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/EmptyIterator.java new file mode 100644 index 0000000..bce47b8 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/EmptyIterator.java @@ -0,0 +1,44 @@ +package org.xbib.datastructures.trie.limewire; + +import java.util.Iterator; +import java.util.NoSuchElementException; + + +/** + * Provides an unmodifiable empty iterator. EmptyIterator always + * returns that there aren't any more items and throws a + * {@link NoSuchElementException} when attempting to move to the next item. + * + *
+ * try{
+ * EmptyIterator ei = new EmptyIterator();
+ * ei.next();
+ * } catch (Exception e) {
+ * System.out.println("Expected to get NoSuchElementException exception: " + e.toString());
+ * }
+ *
+ * Output:
+ * Expected to get NoSuchElementException exception: java.util.NoSuchElementException
+ * 
+ */ +public class EmptyIterator extends UnmodifiableIterator { + /** + * A constant EmptyIterator. + */ + public final static Iterator EMPTY_ITERATOR = new EmptyIterator(); + + @SuppressWarnings("unchecked") + public static Iterator emptyIterator() { + return EMPTY_ITERATOR; + } + + // inherits javadoc comment + public boolean hasNext() { + return false; + } + + // inherits javadoc comment + public Object next() { + throw new NoSuchElementException(); + } +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/PatriciaTrie.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/PatriciaTrie.java new file mode 100644 index 0000000..f32d24a --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/PatriciaTrie.java @@ -0,0 +1,2336 @@ +package org.xbib.datastructures.trie.limewire; + +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; + +/** + * A PATRICIA Trie. + *

+ * PATRICIA = Practical Algorithm to Retrieve Information Coded in Alphanumeric + *

+ * A PATRICIA Trie is a compressed Trie. Instead of storing all data at the + * edges of the Trie (and having empty internal nodes), PATRICIA stores data + * in every node. This allows for very efficient traversal, insert, delete, + * predecessor, successor, prefix, range, and 'select' operations. All operations + * are performed at worst in O(K) time, where K is the number of bits in the + * largest item in the tree. In practice, operations actually take O(A(K)) + * time, where A(K) is the average number of bits of all items in the tree. + *

+ * Most importantly, PATRICIA requires very few comparisons to keys while + * doing any operation. While performing a lookup, each comparison + * (at most K of them, described above) will perform a single bit comparison + * against the given key, instead of comparing the entire key to another key. + *

+ * The Trie can return operations in lexicographical order using the 'traverse', + * 'prefix', 'submap', or 'iterator' methods. The Trie can also scan for items + * that are 'bitwise' (using an XOR metric) by the 'select' method. Bitwise + * closeness is determined by the {@link KeyAnalyzer} returning true or + * false for a bit being set or not in a given key. + *

+ * This PATRICIA Trie supports both variable length & fixed length keys. + * Some methods, such as getPrefixedBy(...) are suited only to + * variable length keys, whereas getPrefixedByBits(...) is suited + * to fixed-size keys. + *

+ * Additionally see PATRICIA + * for more information. + *

+ * Any methods here that take an Object may throw a + * ClassCastException if the method is expecting an instance of K + * (and it isn't K). + * + *

+ * PatriciaTrie trie = new PatriciaTrie
+ * (new CharSequenceKeyAnalyzer());
+ *
+ * trie.put("Lime", "Lime");
+ * trie.put("LimeWire", "LimeWire");
+ * trie.put("LimeRadio", "LimeRadio");
+ * trie.put("Lax", "Lax");
+ * trie.put("Lake", "Lake");
+ * trie.put("Lovely", "Lovely");
+ *
+ * System.out.println(trie.select("Lo"));
+ * System.out.println(trie.select("Lime"));
+ *
+ * System.out.println(trie.getPrefixedBy("La").toString());
+ *
+ * Output:
+ * Lovely
+ * Lime
+ * {Lake=Lake, Lax=Lax}
+ *
+ * 
+ * + * @author Roger Kapsi + * @author Sam Berlin + */ +public class PatriciaTrie extends AbstractMap implements Trie { + + /** + * The root element of the Trie. + */ + private final TrieEntry root = new TrieEntry(null, null, -1); + /** + * The keyAnalyzer used to analyze bit values of keys. + */ + private final KeyAnalyzer keyAnalyzer; + /** + * The current size (total number of elements) of the Trie. + */ + private int size = 0; + /** + * The number of times this has been modified (to fail-fast the iterators). + */ + private transient int modCount = 0; + /** + * Each of these fields are initialized to contain an instance of the + * appropriate view the first time this view is requested. The views are + * stateless, so there's no reason to create more than one of each. + */ + private transient volatile Set keySet = null; + private transient volatile Collection values = null; + private transient volatile Set> entrySet = null; + + /** + * Constructs a new PatriciaTrie using the given keyAnalyzer. + */ + public PatriciaTrie(KeyAnalyzer keyAnalyzer) { + this.keyAnalyzer = keyAnalyzer; + } + + /** + * Returns true if bitIndex is a valid index + */ + private static boolean isValidBitIndex(int bitIndex) { + return 0 <= bitIndex && bitIndex <= Integer.MAX_VALUE; + } + + /** + * Returns true if bitIndex is a NULL_BIT_KEY + */ + private static boolean isNullBitKey(int bitIndex) { + return bitIndex == KeyAnalyzer.NULL_BIT_KEY; + } + + /** + * Returns true if bitIndex is a EQUAL_BIT_KEY + */ + private static boolean isEqualBitKey(int bitIndex) { + return bitIndex == KeyAnalyzer.EQUAL_BIT_KEY; + } + + /** + * Test two values for equality. Works with null values. + */ + private static boolean valEquals(Object o1, Object o2) { + return (o1 == null ? o2 == null : o1.equals(o2)); + } + + /** + * Returns the KeyAnalyzer that constructed the trie. + */ + public KeyAnalyzer getKeyAnalyzer() { + return keyAnalyzer; + } + + /** + * Returns the KeyAnalyzer as a comparator. + */ + public Comparator comparator() { + return keyAnalyzer; + } + + /** + * Clears the Trie (i.e. removes all elements). + */ + @Override + public void clear() { + root.key = null; + root.bitIndex = -1; + root.value = null; + + root.parent = null; + root.left = root; + root.right = null; + root.predecessor = root; + + size = 0; + incrementModCount(); + } + + /** + * Returns true if the Trie is empty. + */ + @Override + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns the number items in the Trie. + */ + @Override + public int size() { + return size; + } + + /** + * Increments both the size and mod counter. + */ + private void incrementSize() { + size++; + incrementModCount(); + } + + /** + * Decrements the size and increments the mod counter. + */ + private void decrementSize() { + size--; + incrementModCount(); + } + + /** + * Increments the mod counter. + */ + private void incrementModCount() { + modCount++; + } + + /** + * Adds a new pair to the Trie and if a pair already + * exists it will be replaced. In the latter case it will return + * the old value. + */ + @Override + public V put(K key, V value) { + if (key == null) { + throw new NullPointerException("Key cannot be null"); + } + + int keyLength = length(key); + + // The only place to store a key with a length + // of zero bits is the root node + if (keyLength == 0) { + if (root.isEmpty()) { + incrementSize(); + } else { + incrementModCount(); + } + return root.setKeyValue(key, value); + } + + TrieEntry found = getNearestEntryForKey(key, keyLength); + if (key.equals(found.key)) { + if (found.isEmpty()) { // <- must be the root + incrementSize(); + } else { + incrementModCount(); + } + return found.setKeyValue(key, value); + } + + int bitIndex = bitIndex(key, found.key); + if (isValidBitIndex(bitIndex)) { // in 99.999...9% the case + /* NEW KEY+VALUE TUPLE */ + TrieEntry t = new TrieEntry(key, value, bitIndex); + addEntry(t, keyLength); + incrementSize(); + return null; + } else if (isNullBitKey(bitIndex)) { + // A bits of the Key are zero. The only place to + // store such a Key is the root Node! + + /* NULL BIT KEY */ + if (root.isEmpty()) { + incrementSize(); + } else { + incrementModCount(); + } + return root.setKeyValue(key, value); + + } else if (isEqualBitKey(bitIndex)) { + // This is a very special and rare case. + + /* REPLACE OLD KEY+VALUE */ + if (found != root) { + incrementModCount(); + return found.setKeyValue(key, value); + } + } + + throw new IndexOutOfBoundsException("Failed to put: " + key + " -> " + value + ", " + bitIndex); + } + + /** + * Adds the given entry into the Trie. + */ + private TrieEntry addEntry(TrieEntry toAdd, int keyLength) { + TrieEntry current = root.left; + TrieEntry path = root; + while (true) { + if (current.bitIndex >= toAdd.bitIndex || current.bitIndex <= path.bitIndex) { + toAdd.predecessor = toAdd; + + if (!isBitSet(toAdd.key, keyLength, toAdd.bitIndex)) { + toAdd.left = toAdd; + toAdd.right = current; + } else { + toAdd.left = current; + toAdd.right = toAdd; + } + + toAdd.parent = path; + if (current.bitIndex >= toAdd.bitIndex) { + current.parent = toAdd; + } + + // if we inserted an uplink, set the predecessor on it + if (current.bitIndex <= path.bitIndex) { + current.predecessor = toAdd; + } + + if (path == root || !isBitSet(toAdd.key, keyLength, path.bitIndex)) + path.left = toAdd; + else + path.right = toAdd; + return toAdd; + } + + path = current; + if (!isBitSet(toAdd.key, keyLength, current.bitIndex)) + current = current.left; + else + current = current.right; + } + } + + @Override + public Set> entrySet() { + Set> es = entrySet; + return (es != null ? es : (entrySet = new EntrySet())); + } + + /** + * Returns the Value whose Key equals our lookup Key + * or null if no such key exists. + */ + @Override + public V get(Object k) { + TrieEntry entry = getEntry(k); + return entry != null ? entry.getValue() : null; + } + + /** + * Returns the entry associated with the specified key in the + * PatriciaTrie. Returns null if the map contains no mapping + * for this key. + *

+ * This may throw ClassCastException if the object is not of type K. + */ + TrieEntry getEntry(Object k) { + K key = asKey(k); + if (key == null) + return null; + + int keyLength = length(key); + TrieEntry entry = getNearestEntryForKey(key, keyLength); + return !entry.isEmpty() && key.equals(entry.key) ? entry : null; + } + + /** + * Gets the key as a 'K'. + */ + @SuppressWarnings("unchecked") + protected final K asKey(Object key) { + try { + return (K) key; + } catch (ClassCastException cce) { + // Because the type is erased, the cast & return are + // actually doing nothing, making this CCE impossible. + // However, it's still here on the off-chance it may + // work. + return null; + } + } + + /** + * Returns the nearest entry for a given key. This is useful + * for finding knowing if a given key exists (and finding the value + * for it), or for inserting the key. + *

+ * The actual get implementation. This is very similar to + * selectR but with the exception that it might return the + * root Entry even if it's empty. + */ + private TrieEntry getNearestEntryForKey(K key, int keyLength) { + TrieEntry current = root.left; + TrieEntry path = root; + while (true) { + if (current.bitIndex <= path.bitIndex) + return current; + + path = current; + if (!isBitSet(key, keyLength, current.bitIndex)) + current = current.left; + else + current = current.right; + } + } + + /** + * Returns the Value whose Key has the longest prefix + * in common with our lookup key. + */ + @SuppressWarnings("unchecked") + public V select(K key) { + int keyLength = length(key); + TrieEntry[] result = new TrieEntry[1]; + if (!selectR(root.left, -1, key, keyLength, result)) { + TrieEntry e = result[0]; + return e.getValue(); + } + return null; + } + + /** + * This is equivalent to the other selectR() method but without + * its overhead because we're selecting only one best matching + * Entry from the Trie. + */ + private boolean selectR(TrieEntry h, int bitIndex, + final K key, final int keyLength, final TrieEntry[] result) { + + if (h.bitIndex <= bitIndex) { + // If we hit the root Node and it is empty + // we have to look for an alternative best + // matching node. + if (!h.isEmpty()) { + result[0] = h; + return false; + } + return true; + } + + if (!isBitSet(key, keyLength, h.bitIndex)) { + if (selectR(h.left, h.bitIndex, key, keyLength, result)) { + return selectR(h.right, h.bitIndex, key, keyLength, result); + } + } else { + if (selectR(h.right, h.bitIndex, key, keyLength, result)) { + return selectR(h.left, h.bitIndex, key, keyLength, result); + } + } + return false; + } + + @SuppressWarnings("unchecked") + public Map.Entry select(K key, Cursor cursor) { + int keyLength = length(key); + TrieEntry[] result = new TrieEntry[]{null}; + selectR(root.left, -1, key, keyLength, cursor, result); + return result[0]; + } + + private boolean selectR(TrieEntry h, int bitIndex, + final K key, + final int keyLength, + final Cursor cursor, + final TrieEntry[] result) { + + if (h.bitIndex <= bitIndex) { + if (!h.isEmpty()) { + Cursor.SelectStatus ret = cursor.select(h); + switch (ret) { + case REMOVE: + throw new UnsupportedOperationException("cannot remove during select"); + case EXIT: + result[0] = h; + return false; // exit + case REMOVE_AND_EXIT: + TrieEntry entry = new TrieEntry(h.getKey(), h.getValue(), -1); + result[0] = entry; + removeEntry(h); + return false; + case CONTINUE: + // fall through. + } + } + return true; // continue + } + + if (!isBitSet(key, keyLength, h.bitIndex)) { + if (selectR(h.left, h.bitIndex, key, keyLength, cursor, result)) { + return selectR(h.right, h.bitIndex, key, keyLength, cursor, result); + } + } else { + if (selectR(h.right, h.bitIndex, key, keyLength, cursor, result)) { + return selectR(h.left, h.bitIndex, key, keyLength, cursor, result); + } + } + + return false; + } + + /** + * Returns a view of this Trie of all elements that are + * prefixed by the given key. + *

+ * In a fixed-keysize Trie, this is essentially a 'get' operation. + *

+ * For example, if the trie contains 'Lime', 'LimeWire', + * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then + * a lookup of 'Lime' would return 'Lime', 'LimeRadio', and 'LimeWire'. + *

+ * The view that this returns is optimized to have a very efficient + * Iterator. The firstKey, lastKey & size methods must iterate + * over all possible values in order to determine the results. This + * information is cached until the Patricia tree changes. All other + * methods (except Iterator) must compare the given key to the prefix + * to ensure that it is within the range of the view. The Iterator's + * remove method must also relocate the subtree that contains the + * prefixes if the entry holding the subtree is removed or changes. + * Changing the subtree takes O(K) time. + */ + public SortedMap getPrefixedBy(K key) { + return getPrefixedByBits(key, 0, keyAnalyzer.length(key)); + } + + /** + * Returns a view of this Trie of all elements that are + * prefixed by the length of the key. + *

+ * Fixed-keysize Tries will not support this operation + * (because all keys will be the same length). + *

+ * For example, if the trie contains 'Lime', 'LimeWire', + * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then + * a lookup of 'LimePlastics' with a length of 4 would + * return 'Lime', 'LimeRadio', and 'LimeWire'. + *

+ * The view that this returns is optimized to have a very efficient + * Iterator. The firstKey, lastKey & size methods must iterate + * over all possible values in order to determine the results. This + * information is cached until the Patricia tree changes. All other + * methods (except Iterator) must compare the given key to the prefix + * to ensure that it is within the range of the view. The Iterator's + * remove method must also relocate the subtree that contains the + * prefixes if the entry holding the subtree is removed or changes. + * Changing the subtree takes O(K) time. + */ + public SortedMap getPrefixedBy(K key, int length) { + return getPrefixedByBits(key, 0, length * keyAnalyzer.bitsPerElement()); + } + + /** + * Returns a view of this Trie of all elements that are prefixed + * by the key, starting at the given offset and for the given length. + *

+ * Fixed-keysize Tries will not support this operation + * (because all keys are the same length). + *

+ * For example, if the trie contains 'Lime', 'LimeWire', + * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then + * a lookup of 'The Lime Plastics' with an offset of 4 and a + * length of 4 would return 'Lime', 'LimeRadio', and 'LimeWire'. + *

+ * The view that this returns is optimized to have a very efficient + * Iterator. The firstKey, lastKey & size methods must iterate + * over all possible values in order to determine the results. This + * information is cached until the Patricia tree changes. All other + * methods (except Iterator) must compare the given key to the prefix + * to ensure that it is within the range of the view. The Iterator's + * remove method must also relocate the subtree that contains the + * prefixes if the entry holding the subtree is removed or changes. + * Changing the subtree takes O(K) time. + */ + public SortedMap getPrefixedBy(K key, int offset, int length) { + return getPrefixedByBits(key, offset * keyAnalyzer.bitsPerElement(), length * keyAnalyzer.bitsPerElement()); + } + + /** + * Returns a view of this Trie of all elements that are prefixed + * by the number of bits in the given Key. + *

+ * Fixed-keysize Tries can support this operation as a way to do + * lookups of partial keys. That is, if the Trie is storing IP + * addresses, you can lookup all addresses that begin with + * '192.168' by providing the key '192.168.X.X' and a length of 16 + * would return all addresses that begin with '192.168'. + *

+ * The view that this returns is optimized to have a very efficient + * Iterator. The firstKey, lastKey & size methods must iterate + * over all possible values in order to determine the results. This + * information is cached until the Patricia tree changes. All other + * methods (except Iterator) must compare the given key to the prefix + * to ensure that it is within the range of the view. The Iterator's + * remove method must also relocate the subtree that contains the + * prefixes if the entry holding the subtree is removed or changes. + * Changing the subtree takes O(K) time. + */ + public SortedMap getPrefixedByBits(K key, int bitLength) { + return getPrefixedByBits(key, 0, bitLength); + } + + /** + * Returns a view of this map, with entries containing only those that + * are prefixed by a value whose bits matches the bits between 'offset' + * and 'length' in the given key. + *

+ * The view that this returns is optimized to have a very efficient + * Iterator. The firstKey, lastKey & size methods must iterate + * over all possible values in order to determine the results. This + * information is cached until the Patricia tree changes. All other + * methods (except Iterator) must compare the given key to the prefix + * to ensure that it is within the range of the view. The Iterator's + * remove method must also relocate the subtree that contains the + * prefixes if the entry holding the subtree is removed or changes. + * Changing the subtree takes O(K) time. + */ + private SortedMap getPrefixedByBits(K key, int offset, int length) { + int offsetLength = offset + length; + if (offsetLength > length(key)) { + throw new IllegalArgumentException(offset + " + " + length + " > " + length(key)); + } + + if (offsetLength == 0) + return this; + + return new PrefixSubMap(key, offset, length); + } + + /** + * Returns true if this trie contains the specified Key + *

+ * This may throw ClassCastException if the object is not + * of type K. + */ + @Override + public boolean containsKey(Object k) { + K key = asKey(k); + if (key == null) + return false; + + int keyLength = length(key); + TrieEntry entry = getNearestEntryForKey(key, keyLength); + return !entry.isEmpty() && key.equals(entry.key); + } + + /** + * Returns true if this Trie contains the specified value. + */ + @Override + public boolean containsValue(Object o) { + for (V v : values()) + if (valEquals(v, o)) + return true; + return false; + } + + /** + * Removes a Key from the Trie if one exists + *

+ * This may throw ClassCastException if the object is not of type K. + * + * @param k the Key to delete + * @return Returns the deleted Value + */ + @Override + public V remove(Object k) { + K key = asKey(k); + if (key == null) + return null; + + int keyLength = length(key); + TrieEntry current = root.left; + TrieEntry path = root; + while (true) { + if (current.bitIndex <= path.bitIndex) { + if (!current.isEmpty() && key.equals(current.key)) + return removeEntry(current); + else + return null; + } + + path = current; + if (!isBitSet(key, keyLength, current.bitIndex)) + current = current.left; + else + current = current.right; + } + } + + /** + * Removes a single entry from the Trie. + *

+ * If we found a Key (Entry h) then figure out if it's + * an internal (hard to remove) or external Entry (easy + * to remove) + */ + private V removeEntry(TrieEntry h) { + if (h != root) { + if (h.isInternalNode()) { + removeInternalEntry(h); + } else { + removeExternalEntry(h); + } + } + + decrementSize(); + return h.setKeyValue(null, null); + } + + /** + * Removes an external entry from the Trie. + *

+ * If it's an external Entry then just remove it. + * This is very easy and straight forward. + */ + private void removeExternalEntry(TrieEntry h) { + if (h == root) { + throw new IllegalArgumentException("Cannot delete root Entry!"); + } else if (!h.isExternalNode()) { + throw new IllegalArgumentException(h + " is not an external Entry!"); + } + + TrieEntry parent = h.parent; + TrieEntry child = (h.left == h) ? h.right : h.left; + + if (parent.left == h) { + parent.left = child; + } else { + parent.right = child; + } + + // either the parent is changing, or the predecessor is changing. + if (child.bitIndex > parent.bitIndex) { + child.parent = parent; + } else { + child.predecessor = parent; + } + + } + + /** + * Removes an internal entry from the Trie. + *

+ * If it's an internal Entry then "good luck" with understanding + * this code. The Idea is essentially that Entry p takes Entry h's + * place in the trie which requires some re-wiring. + */ + private void removeInternalEntry(TrieEntry h) { + if (h == root) { + throw new IllegalArgumentException("Cannot delete root Entry!"); + } else if (!h.isInternalNode()) { + throw new IllegalArgumentException(h + " is not an internal Entry!"); + } + + TrieEntry p = h.predecessor; + + // Set P's bitIndex + p.bitIndex = h.bitIndex; + + // Fix P's parent, predecessor and child Nodes + { + TrieEntry parent = p.parent; + TrieEntry child = (p.left == h) ? p.right : p.left; + + // if it was looping to itself previously, + // it will now be pointed from it's parent + // (if we aren't removing it's parent -- + // in that case, it remains looping to itself). + // otherwise, it will continue to have the same + // predecessor. + if (p.predecessor == p && p.parent != h) + p.predecessor = p.parent; + + if (parent.left == p) { + parent.left = child; + } else { + parent.right = child; + } + + if (child.bitIndex > parent.bitIndex) { + child.parent = parent; + } + } + + // Fix H's parent and child Nodes + { + // If H is a parent of its left and right child + // then change them to P + if (h.left.parent == h) { + h.left.parent = p; + } + + if (h.right.parent == h) { + h.right.parent = p; + } + + // Change H's parent + if (h.parent.left == h) { + h.parent.left = p; + } else { + h.parent.right = p; + } + } + + // Copy the remaining fields from H to P + //p.bitIndex = h.bitIndex; + p.parent = h.parent; + p.left = h.left; + p.right = h.right; + + // Make sure that if h was pointing to any uplinks, + // p now points to them. + if (isValidUplink(p.left, p)) + p.left.predecessor = p; + if (isValidUplink(p.right, p)) + p.right.predecessor = p; + + } + + /** + * Returns the node lexicographically before the given node (or null if none). + *

+ * This follows four simple branches: + *

+     *  - If the uplink that returned us was a right uplink:
+     *      - If predecessor's left is a valid uplink from predecessor, return it.
+     *      - Else, follow the right path from the predecessor's left.
+     *  - If the uplink that returned us was a left uplink:
+     *      - Loop back through parents until we encounter a node where
+     *        node != node.parent.left.
+     *          - If node.parent.left is uplink from node.parent:
+     *              - If node.parent.left is not root, return it.
+     *              - If it is root & root isEmpty, return null.
+     *              - If it is root & root !isEmpty, return root.
+     *          - If node.parent.left is not uplink from node.parent:
+     *              - Follow right path for first right child from node.parent.left
+     * 
+ */ + private TrieEntry previousEntry(TrieEntry start) { + if (start.predecessor == null) + throw new IllegalArgumentException("must have come from somewhere!"); + + if (start.predecessor.right == start) { + if (isValidUplink(start.predecessor.left, start.predecessor)) { + return start.predecessor.left; + } else { + return followRight(start.predecessor.left); + } + } else { + TrieEntry node = start.predecessor; + while (node.parent != null && node == node.parent.left) + node = node.parent; + if (node.parent == null) // can be null if we're looking up root. + return null; + if (isValidUplink(node.parent.left, node.parent)) { + if (node.parent.left == root) { + if (root.isEmpty()) + return null; + else + return root; + } else { + return node.parent.left; + } + } else { + return followRight(node.parent.left); + } + } + } + + /** + * Returns the entry lexicographically after the given entry. + * If the given entry is null, returns the first node. + */ + private TrieEntry nextEntry(TrieEntry node) { + if (node == null) { + return firstEntry(); + } else { + return nextEntryImpl(node.predecessor, node, null); + } + } + + /** + * Returns the entry lexicographically after the given entry. + * If the given entry is null, returns the first node. + *

+ * This will traverse only within the subtree. If the given node + * is not within the subtree, this will have undefined results. + */ + private TrieEntry nextEntryInSubtree(TrieEntry node, TrieEntry parentOfSubtree) { + if (node == null) { + return firstEntry(); + } else { + return nextEntryImpl(node.predecessor, node, parentOfSubtree); + } + } + + /** + * Scans for the next node, starting at the specified point, and using 'previous' + * as a hint that the last node we returned was 'previous' (so we know not to return + * it again). If 'tree' is non-null, this will limit the search to the given tree. + *

+ * The basic premise is that each iteration can follow the following steps: + *

+     * 1) Scan all the way to the left.
+     *   a) If we already started from this node last time, proceed to Step 2.
+     *   b) If a valid uplink is found, use it.
+     *   c) If the result is an empty node (root not set), break the scan.
+     *   d) If we already returned the left node, break the scan.
+     *
+     * 2) Check the right.
+     *   a) If we already returned the right node, proceed to Step 3.
+     *   b) If it is a valid uplink, use it.
+     *   c) Do Step 1 from the right node.
+     *
+     * 3) Back up through the parents until we encounter find a parent
+     *    that we're not the right child of.
+     *
+     * 4) If there's no right child of that parent, the iteration is finished.
+     *    Otherwise continue to Step 5.
+     *
+     * 5) Check to see if the right child is a valid uplink.
+     *    a) If we already returned that child, proceed to Step 6.
+     *       Otherwise, use it.
+     *
+     * 6) If the right child of the parent is the parent itself, we've
+     *    already found & returned the end of the Trie, so exit.
+     *
+     * 7) Do Step 1 on the parent's right child.
+     * 
+ */ + private TrieEntry nextEntryImpl(TrieEntry start, TrieEntry previous, TrieEntry tree) { + TrieEntry current = start; + + // Only look at the left if this was a recursive or + // the first check, otherwise we know we've already looked + // at the left. + if (previous == null || start != previous.predecessor) { + while (!current.left.isEmpty()) { + // stop traversing if we've already + // returned the left of this node. + if (previous == current.left) { + break; + } + + if (isValidUplink(current.left, current)) { + return current.left; + } + + current = current.left; + } + } + + // If there's no data at all, exit. + if (current.isEmpty()) { + return null; + } + + // If we've already returned the left, + // and the immediate right is null, + // there's only one entry in the Trie + // which is stored at the root. + // + // / ("") <-- root + // \_/ \ + // null <-- 'current' + // + if (current.right == null) + return null; + + // If nothing valid on the left, try the right. + if (previous != current.right) { + // See if it immediately is valid. + if (isValidUplink(current.right, current)) { + return current.right; + } + + // Must search on the right's side if it wasn't initially valid. + return nextEntryImpl(current.right, previous, tree); + } + + // Neither left nor right are valid, find the first parent + // whose child did not come from the right & traverse it. + while (current == current.parent.right) { + // If we're going to traverse to above the subtree, stop. + if (current == tree) + return null; + + current = current.parent; + } + + // If we're on the top of the subtree, we can't go any higher. + if (current == tree) + return null; + + + // If there's no right, the parent must be root, so we're done. + if (current.parent.right == null) { + return null; + } + + + // If the parent's right points to itself, we've found one. + if (previous != current.parent.right && isValidUplink(current.parent.right, current.parent)) { + return current.parent.right; + } + + // If the parent's right is itself, there can't be any more nodes. + if (current.parent.right == current.parent) { + return null; + } + + // We need to traverse down the parent's right's path. + return nextEntryImpl(current.parent.right, previous, tree); + } + + /** + * Returns each entry as a string. + */ + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("Trie[").append(size()).append("]={\n"); + for (Iterator> i = newEntryIterator(); i.hasNext(); ) { + buffer.append(" ").append(i.next().toString()).append("\n"); + } + buffer.append("}\n"); + return buffer.toString(); + } + + public Map.Entry traverse(Cursor cursor) { + TrieEntry entry = nextEntry(null); + while (entry != null) { + TrieEntry current = entry; + Cursor.SelectStatus ret = cursor.select(current); + entry = nextEntry(current); + switch (ret) { + case EXIT: + return current; + case REMOVE: + removeEntry(current); + break; // out of switch, stay in while loop + case REMOVE_AND_EXIT: + Map.Entry value = new TrieEntry(current.getKey(), current.getValue(), -1); + removeEntry(current); + return value; + case CONTINUE: // do nothing. + } + } + + return null; + } + + /** + * Returns true if 'next' is a valid uplink coming from 'from'. + */ + private boolean isValidUplink(TrieEntry next, TrieEntry from) { + return next != null && next.bitIndex <= from.bitIndex && !next.isEmpty(); + } + + /** + * Returns the length of the key, or 0 if the key is null. + */ + private int length(K key) { + if (key == null) { + return 0; + } + + return keyAnalyzer.length(key); + } + + /** + * Returns whether or not the given bit on the + * key is set, or false if the key is null + */ + private boolean isBitSet(K key, int keyLength, int bitIndex) { + if (key == null) { // root's might be null! + return false; + } + return keyAnalyzer.isBitSet(key, keyLength, bitIndex); + } + + /** + * Utility method for calling + * keyAnalyzer.bitIndex(key, 0, length(key), foundKey, 0, length(foundKey)) + */ + private int bitIndex(K key, K foundKey) { + return keyAnalyzer.bitIndex(key, 0, length(key), foundKey, 0, length(foundKey)); + } + + Iterator newKeyIterator() { + return new KeyIterator(); + } + + Iterator newValueIterator() { + return new ValueIterator(); + } + + Iterator> newEntryIterator() { + return new EntryIterator(); + } + + /** + * Returns a set view of the keys contained in this map. The set is + * backed by the map, so changes to the map are reflected in the set, and + * vice-versa. The set supports element removal, which removes the + * corresponding mapping from this map, via the Iterator.remove, + * Set.remove, removeAll, retainAll, and + * clear operations. It does not support the add or + * addAll operations. + * + * @return a set view of the keys contained in this map. + */ + @Override + public Set keySet() { + Set ks = keySet; + return (ks != null ? ks : (keySet = new KeySet())); + } + + /** + * Returns a collection view of the values contained in this map. The + * collection is backed by the map, so changes to the map are reflected in + * the collection, and vice-versa. The collection supports element + * removal, which removes the corresponding mapping from this map, via the + * Iterator.remove, Collection.remove, + * removeAll, retainAll, and clear operations. + * It does not support the add or addAll operations. + * + * @return a collection view of the values contained in this map. + */ + @Override + public Collection values() { + Collection vs = values; + return (vs != null ? vs : (values = new Values())); + } + + /** + * Returns the first entry the Trie is storing. + *

+ * This is implemented by going always to the left until + * we encounter a valid uplink. That uplink is the first key. + */ + private TrieEntry firstEntry() { + // if Trie is empty, no first node. + if (isEmpty()) + return null; + + return followLeft(root); + } + + /** + * Goes left through the tree until it finds a valid node. + */ + private TrieEntry followLeft(TrieEntry node) { + while (true) { + TrieEntry child = node.left; + // if we hit root and it didn't have a node, go right instead. + if (child.isEmpty()) + child = node.right; + + if (child.bitIndex <= node.bitIndex) + return child; + + node = child; + } + } + + /** + * Returns the last entry the Trie is storing. + *

+ * This is implemented by going always to the right until + * we encounter a valid uplink. That uplink is the last key. + */ + private TrieEntry lastEntry() { + return followRight(root.left); + } + + /** + * Traverses down the right path until it finds an uplink. + */ + protected TrieEntry followRight(TrieEntry node) { + // if Trie is empty, no last entry. + if (node.right == null) + return null; + + // Go as far right as possible, until we encounter an uplink. + while (node.right.bitIndex > node.bitIndex) + node = node.right; + + return node.right; + } + + public K firstKey() { + return firstEntry().getKey(); + } + + public SortedMap headMap(K toKey) { + return new SubMap(null, toKey); + } + + public K lastKey() { + TrieEntry entry = lastEntry(); + if (entry != null) + return entry.getKey(); + else + return null; + } + + public SortedMap subMap(K fromKey, K toKey) { + return new SubMap(fromKey, toKey); + } + + public SortedMap tailMap(K fromKey) { + return new SubMap(fromKey, null); + } + + /** + * Returns an entry strictly higher than the given key, + * or null if no such entry exists. + */ + protected TrieEntry higherEntry(K key) { + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + int keyLength = length(key); + + if (keyLength == 0) { + if (!root.isEmpty()) { + // If data in root, and more after -- return it. + if (size() > 1) { + return nextEntry(root); + } else { // If no more after, no higher entry. + return null; + } + } else { + // Root is empty & we want something after empty, return first. + return firstEntry(); + } + } + + TrieEntry found = getNearestEntryForKey(key, keyLength); + if (key.equals(found.key)) + return nextEntry(found); + + int bitIndex = bitIndex(key, found.key); + if (isValidBitIndex(bitIndex)) { + TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, keyLength); + incrementSize(); // must increment because remove will decrement + TrieEntry ceil = nextEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return ceil; + } else if (isNullBitKey(bitIndex)) { + if (!root.isEmpty()) + return firstEntry(); + else if (size() > 1) + return nextEntry(firstEntry()); + else + return null; + } else if (isEqualBitKey(bitIndex)) { + return nextEntry(found); + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the least key greater + * than or equal to the given key, or null if there is no such key. + */ + protected TrieEntry ceilingEntry(K key) { + // Basically: + // Follow the steps of adding an entry, but instead... + // + // - If we ever encounter a situation where we found an equal + // key, we return it immediately. + // + // - If we hit an empty root, return the first iterable item. + // + // - If we have to add a new item, we temporarily add it, + // find the successor to it, then remove the added item. + // + // These steps ensure that the returned value is either the + // entry for the key itself, or the first entry directly after + // the key. + + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + int keyLength = length(key); + + if (keyLength == 0) { + if (!root.isEmpty()) + return root; + else + return firstEntry(); + } + + TrieEntry found = getNearestEntryForKey(key, keyLength); + if (key.equals(found.key)) + return found; + + int bitIndex = bitIndex(key, found.key); + if (isValidBitIndex(bitIndex)) { + TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, keyLength); + incrementSize(); // must increment because remove will decrement + TrieEntry ceil = nextEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return ceil; + } else if (isNullBitKey(bitIndex)) { + if (!root.isEmpty()) + return root; + else + return firstEntry(); + } else if (isEqualBitKey(bitIndex)) { + return found; + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the greatest key + * strictly less than the given key, or null if there is no such key. + */ + protected TrieEntry lowerEntry(K key) { + // Basically: + // Follow the steps of adding an entry, but instead... + // + // - If we ever encounter a situation where we found an equal + // key, we return it's previousEntry immediately. + // + // - If we hit root (empty or not), return null. + // + // - If we have to add a new item, we temporarily add it, + // find the previousEntry to it, then remove the added item. + // + // These steps ensure that the returned value is always just before + // the key or null (if there was nothing before it). + + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + int keyLength = length(key); + + if (keyLength == 0) { + return null; // there can never be anything before root. + } + + TrieEntry found = getNearestEntryForKey(key, keyLength); + if (key.equals(found.key)) + return previousEntry(found); + + int bitIndex = bitIndex(key, found.key); + if (isValidBitIndex(bitIndex)) { + TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, keyLength); + incrementSize(); // must increment because remove will decrement + TrieEntry prior = previousEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return prior; + } else if (isNullBitKey(bitIndex)) { + return null; + } else if (isEqualBitKey(bitIndex)) { + return previousEntry(found); + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the greatest key + * less than or equal to the given key, or null if there is no such key. + */ + protected TrieEntry floorEntry(K key) { + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + int keyLength = length(key); + + if (keyLength == 0) { + if (!root.isEmpty()) + return root; + else + return null; + } + + TrieEntry found = getNearestEntryForKey(key, keyLength); + if (key.equals(found.key)) + return found; + + int bitIndex = bitIndex(key, found.key); + if (isValidBitIndex(bitIndex)) { + TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, keyLength); + incrementSize(); // must increment because remove will decrement + TrieEntry floor = previousEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return floor; + } else if (isNullBitKey(bitIndex)) { + if (!root.isEmpty()) + return root; + else + return null; + } else if (isEqualBitKey(bitIndex)) { + return found; + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Finds the subtree that contains the prefix. + *

+ * This is very similar to getR but with the difference that + * we stop the lookup if h.bitIndex > keyLength. + */ + private TrieEntry subtree(K prefix, int offset, int length) { + TrieEntry current = root.left; + TrieEntry path = root; + while (current.bitIndex > path.bitIndex && length >= current.bitIndex) { + path = current; + if (!isBitSet(prefix, length + offset, current.bitIndex + offset)) { + current = current.left; + } else { + current = current.right; + } + } + // Make sure the entry is valid for a subtree. + TrieEntry entry = current.isEmpty() ? path : current; + // If entry is root, it can't be empty. + if (entry.isEmpty()) { + return null; + } + int offsetLength = offset + length; + // if root && length of root is less than length of lookup, + // there's nothing. + // (this prevents returning the whole subtree if root has an empty + // string and we want to lookup things with "\0") + if (entry == root && length(entry.getKey()) < offsetLength) { + return null; + } + // Found key's length-th bit differs from our key + // which means it cannot be the prefix... + if (isBitSet(prefix, offsetLength, offsetLength) != + isBitSet(entry.key, length(entry.key), length)) { + return null; + } + // ... or there are less than 'length' equal bits + int bitIndex = keyAnalyzer.bitIndex(prefix, offset, length, + entry.key, 0, length(entry.getKey())); + if (bitIndex >= 0 && bitIndex < length) { + return null; + } + return entry; + } + + /** + * Defines the interface to analyze {@link Trie} keys on a bit + * level. KeyAnalyzer's + * methods return the length of the key in bits, whether or not a bit is + * set, and bits per element in the key. + *

+ * Additionally, a method determines if a key is a prefix of another key and + * returns the bit index where one key is different from another key (if + * the key and found key are equal than the return value is EQUAL_BIT_KEY). + *

+ * KeyAnalyzer defines:
+ * + * + * + *
NULL_BIT_KEYWhen key's bits are all zero
EQUAL_BIT_KEY When keys are the same
+ */ + public interface KeyAnalyzer extends Comparator { + + /** + * Returned by bitIndex if key's bits are all 0. + */ + int NULL_BIT_KEY = -1; + + /** + * Returned by bitIndex if key and found key are + * equal. This is a very very specific case and + * shouldn't happen on a regular basis. + */ + int EQUAL_BIT_KEY = -2; + + /** + * Returns the length of the Key in bits. + */ + int length(K key); + + /** + * Returns whether or not a bit is set. + */ + boolean isBitSet(K key, int keyLength, int bitIndex); + + /** + * Returns the n-th different bit between key and found. + * This starts the comparison in key at 'keyStart' and goes + * for 'keyLength' bits, and compares to the found key + * starting at 'foundStart' and going for 'foundLength' bits. + */ + int bitIndex(K key, int keyStart, int keyLength, + K found, int foundStart, int foundLength); + + /** + * Returns the number of bits per element in the key. + * This is only useful for variable-length keys, such as Strings. + */ + int bitsPerElement(); + + /** + * Determines whether or not the given prefix (from offset to length) + * is a prefix of the given key. + */ + boolean isPrefix(K prefix, int offset, int length, K key); + } + + /** + * The actual Trie nodes. + */ + private static class TrieEntry implements Map.Entry { + + private K key; + private V value; + + /** + * The index this entry is comparing. + */ + private int bitIndex; + + /** + * The parent of this entry. + */ + private TrieEntry parent; + /** + * The left child of this entry. + */ + private TrieEntry left; + /** + * The right child of this entry. + */ + private TrieEntry right; + /** + * The entry who uplinks to this entry. + */ + private TrieEntry predecessor; + + private TrieEntry(K key, V value, int bitIndex) { + this.key = key; + this.value = value; + + this.bitIndex = bitIndex; + + this.parent = null; + this.left = this; + this.right = null; + this.predecessor = this; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry) o; + Object k1 = getKey(); + Object k2 = e.getKey(); + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + Object v1 = getValue(); + Object v2 = e.getValue(); + return v1 == v2 || (v1 != null && v1.equals(v2)); + } + return false; + } else { + return false; + } + } + + /** + * Whether or not the entry is storing a key. + * Only the root can potentially be empty, all other + * nodes must have a key. + */ + public boolean isEmpty() { + return key == null; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + V o = this.value; + this.value = value; + return o; + } + + /** + * Replaces the old key and value with the new ones. + * Returns the old value. + */ + private V setKeyValue(K key, V value) { + this.key = key; + return setValue(value); + } + + /** + * Neither the left nor right child is a loopback + */ + private boolean isInternalNode() { + return left != this && right != this; + } + + /** + * Either the left or right child is a loopback + */ + private boolean isExternalNode() { + return !isInternalNode(); + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + + if (bitIndex == -1) { + buffer.append("RootEntry("); + } else { + buffer.append("Entry("); + } + + buffer.append("key=").append(getKey()).append(" [").append(bitIndex).append("], "); + buffer.append("value=").append(getValue()).append(", "); + //buffer.append("bitIndex=").append(bitIndex).append(", "); + + if (parent != null) { + if (parent.bitIndex == -1) { + buffer.append("parent=").append("ROOT"); + } else { + buffer.append("parent=").append(parent.getKey()).append(" [").append(parent.bitIndex).append("]"); + } + } else { + buffer.append("parent=").append("null"); + } + buffer.append(", "); + + if (left != null) { + if (left.bitIndex == -1) { + buffer.append("left=").append("ROOT"); + } else { + buffer.append("left=").append(left.getKey()).append(" [").append(left.bitIndex).append("]"); + } + } else { + buffer.append("left=").append("null"); + } + buffer.append(", "); + + if (right != null) { + if (right.bitIndex == -1) { + buffer.append("right=").append("ROOT"); + } else { + buffer.append("right=").append(right.getKey()).append(" [").append(right.bitIndex).append("]"); + } + } else { + buffer.append("right=").append("null"); + } + buffer.append(", "); + + if (predecessor != null) { + if (predecessor.bitIndex == -1) { + buffer.append("predecessor=").append("ROOT"); + } else { + buffer.append("predecessor=").append(predecessor.getKey()).append(" [").append(predecessor.bitIndex).append("]"); + } + } + buffer.append(")"); + return buffer.toString(); + } + } + + /** + * An iterator that stores a single TrieEntry. + */ + private class SingletonIterator implements Iterator> { + private final TrieEntry entry; + private int hit = 0; + + public SingletonIterator(TrieEntry entry) { + this.entry = entry; + } + + public boolean hasNext() { + return hit == 0; + } + + public Map.Entry next() { + if (hit != 0) + throw new NoSuchElementException(); + hit++; + return entry; + } + + public void remove() { + if (hit != 1) + throw new IllegalStateException(); + hit++; + PatriciaTrie.this.removeEntry(entry); + } + + } + + /** + * An iterator for the entries. + */ + private abstract class NodeIterator implements Iterator { + protected int expectedModCount = modCount; // For fast-fail + protected TrieEntry next; // the next node to return + protected TrieEntry current; // the current entry we're on + + // Starts iteration from the beginning. + protected NodeIterator() { + next = PatriciaTrie.this.nextEntry(null); + } + + // Starts iteration at the given entry. + protected NodeIterator(TrieEntry firstEntry) { + next = firstEntry; + } + + public boolean hasNext() { + return next != null; + } + + TrieEntry nextEntry() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + TrieEntry e = next; + if (e == null) + throw new NoSuchElementException(); + + next = findNext(e); + current = e; + return e; + } + + protected TrieEntry findNext(TrieEntry prior) { + return PatriciaTrie.this.nextEntry(prior); + } + + public void remove() { + if (current == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + + TrieEntry node = current; + current = null; + PatriciaTrie.this.removeEntry(node); + + expectedModCount = modCount; + } + } + + private class ValueIterator extends NodeIterator { + public V next() { + return nextEntry().value; + } + } + + private class KeyIterator extends NodeIterator { + public K next() { + return nextEntry().getKey(); + } + } + + private class EntryIterator extends NodeIterator> { + public Map.Entry next() { + return nextEntry(); + } + } + + /** + * An iterator for iterating over a prefix search. + */ + private class PrefixEntryIterator extends NodeIterator> { + // values to reset the subtree if we remove it. + protected final K prefix; + protected final int offset; + protected final int length; + protected boolean lastOne; + + protected TrieEntry subtree; // the subtree to search within + + // Starts iteration at the given entry & search only within the given subtree. + PrefixEntryIterator(TrieEntry startScan, K prefix, int offset, int length) { + subtree = startScan; + next = PatriciaTrie.this.followLeft(startScan); + this.prefix = prefix; + this.offset = offset; + this.length = length; + } + + public Map.Entry next() { + Map.Entry entry = nextEntry(); + if (lastOne) + next = null; + return entry; + } + + @Override + protected TrieEntry findNext(TrieEntry prior) { + return PatriciaTrie.this.nextEntryInSubtree(prior, subtree); + } + + @Override + public void remove() { + // If the current entry we're removing is the subtree + // then we need to find a new subtree parent. + boolean needsFixing = false; + int bitIdx = subtree.bitIndex; + if (current == subtree) + needsFixing = true; + + super.remove(); + + // If the subtree changed its bitIndex or we + // removed the old subtree, get a new one. + if (bitIdx != subtree.bitIndex || needsFixing) + subtree = subtree(prefix, offset, length); + + // If the subtree's bitIndex is less than the + // length of our prefix, it's the last item + // in the prefix tree. + if (length >= subtree.bitIndex) + lastOne = true; + } + + } + + /** + * An iterator for submaps. + */ + private class SubMapEntryIterator extends NodeIterator> { + private final K firstExcludedKey; + + SubMapEntryIterator(TrieEntry first, TrieEntry firstExcluded) { + super(first); + firstExcludedKey = + (firstExcluded == null ? null : firstExcluded.key); + } + + @Override + public boolean hasNext() { + return next != null && next.key != firstExcludedKey; + } + + public Map.Entry next() { + if (next == null || next.key == firstExcludedKey) + throw new NoSuchElementException(); + return nextEntry(); + } + } + + private class EntrySet extends AbstractSet> { + @Override + public Iterator> iterator() { + return newEntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + + TrieEntry candidate = getEntry(((Map.Entry) o).getKey()); + return candidate != null && candidate.equals(o); + } + + @Override + public boolean remove(Object o) { + int size = size(); + PatriciaTrie.this.remove(o); + return size != size(); + } + + @Override + public int size() { + return size; + } + + @Override + public void clear() { + PatriciaTrie.this.clear(); + } + } + + private class KeySet extends AbstractSet { + @Override + public Iterator iterator() { + return newKeyIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + return containsKey(o); + } + + @Override + public boolean remove(Object o) { + int size = size(); + PatriciaTrie.this.remove(o); + return size != size(); + } + + @Override + public void clear() { + PatriciaTrie.this.clear(); + } + } + + private class Values extends AbstractCollection { + @Override + public Iterator iterator() { + return newValueIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + + @Override + public void clear() { + PatriciaTrie.this.clear(); + } + + @Override + public boolean remove(Object o) { + for (Iterator i = iterator(); i.hasNext(); ) { + V v = i.next(); + if (valEquals(v, o)) { + i.remove(); + return true; + } + } + return false; + } + } + + /** + * A submap used for prefix views over the Trie. + */ + private class PrefixSubMap extends SubMap { + protected final K prefix; + protected final int offset; + protected final int length; + protected int size; + private transient int keyModCount = 0; + + PrefixSubMap(K prefix, int offset, int length) { + this.prefix = prefix; + this.offset = offset; + this.length = length; + fromInclusive = false; + } + + @Override + public K firstKey() { + fixup(); + TrieEntry e; + if (fromKey == null) { + e = firstEntry(); + } else { + e = higherEntry(fromKey); + } + + K first = e != null ? e.getKey() : null; + if (e == null || !keyAnalyzer.isPrefix(prefix, offset, length, first)) + throw new NoSuchElementException(); + return first; + } + + @Override + public K lastKey() { + fixup(); + TrieEntry e; + if (toKey == null) { + e = lastEntry(); + } else { + e = lowerEntry(toKey); + } + + K last = e != null ? e.getKey() : null; + if (e == null || !keyAnalyzer.isPrefix(prefix, offset, length, last)) + throw new NoSuchElementException(); + return last; + } + + @Override + protected boolean inRange(K key) { + return keyAnalyzer.isPrefix(prefix, offset, length, key); + } + + @Override + protected boolean inRange2(K key) { + return keyAnalyzer.isPrefix(prefix, offset, length, key); + } + + @Override + protected boolean inToRange(K key, boolean forceInclusive) { + return keyAnalyzer.isPrefix(prefix, offset, length, key); + } + + @Override + protected boolean inFromRange(K key, boolean forceInclusive) { + return keyAnalyzer.isPrefix(prefix, offset, length, key); + } + + private void fixup() { + // The trie has changed since we last + // found our toKey / fromKey + if (modCount != keyModCount) { + Iterator> iter = entrySet().iterator(); + size = 0; + + Map.Entry entry = null; + if (iter.hasNext()) { + entry = iter.next(); + size = 1; + } + + fromKey = entry == null ? null : entry.getKey(); + if (fromKey != null) { + TrieEntry prior = previousEntry((TrieEntry) entry); + fromKey = prior == null ? null : prior.getKey(); + } + + toKey = fromKey; + + while (iter.hasNext()) { + size++; + entry = iter.next(); + } + + toKey = entry == null ? null : entry.getKey(); + + if (toKey != null) { + entry = nextEntry((TrieEntry) entry); + toKey = entry == null ? null : entry.getKey(); + } + + keyModCount = modCount; + } + } + + @Override + protected Set> newSubMapEntrySet() { + return new PrefixEntrySetView(); + } + + private class PrefixEntrySetView extends SubMap.EntrySetView { + private TrieEntry prefixStart; + private int iterModCount = 0; + + @Override + public int size() { + fixup(); + return PrefixSubMap.this.size; + } + + @Override + public Iterator> iterator() { + if (modCount != iterModCount) { + prefixStart = subtree(prefix, offset, length); + iterModCount = modCount; + } + + if (prefixStart == null) { + return EmptyIterator.emptyIterator(); + } else if (length >= prefixStart.bitIndex) { + return new SingletonIterator(prefixStart); + } else { + return new PrefixEntryIterator(prefixStart, prefix, offset, length); + } + } + } + } + + private class SubMap extends AbstractMap implements SortedMap { + + /** + * The key to start from, null if the beginning. + */ + protected K fromKey; + + /** + * The key to end at, null if till the end. + */ + protected K toKey; + + /** + * Whether or not the 'from' is inclusive. + */ + protected boolean fromInclusive; + + /** + * Whether or not the 'to' is inclusive. + */ + protected boolean toInclusive; + private transient Set> entrySet; + + /** + * Constructs a blank SubMap -- this should ONLY be used + * by subclasses that wish to lazily construct their + * fromKey or toKey + */ + protected SubMap() { + } + + SubMap(K fromKey, K toKey) { + if (fromKey == null && toKey == null) + throw new IllegalArgumentException("must have a from or to!"); + if (fromKey != null && toKey != null && keyAnalyzer.compare(fromKey, toKey) > 0) + throw new IllegalArgumentException("fromKey > toKey"); + this.fromKey = fromKey; + this.toKey = toKey; + fromInclusive = true; + } + + @Override + public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean containsKey(Object key) { + return inRange((K) key) && PatriciaTrie.this.containsKey(key); + } + + @Override + @SuppressWarnings("unchecked") + public V remove(Object key) { + if (!inRange((K) key)) + return null; + return PatriciaTrie.this.remove(key); + } + + @Override + @SuppressWarnings("unchecked") + public V get(Object key) { + if (!inRange((K) key)) + return null; + return PatriciaTrie.this.get(key); + } + + @Override + public V put(K key, V value) { + if (!inRange(key)) + throw new IllegalArgumentException("key out of range"); + return PatriciaTrie.this.put(key, value); + } + + public Comparator comparator() { + return keyAnalyzer; + } + + public K firstKey() { + TrieEntry e; + if (fromKey == null) { + e = firstEntry(); + } else { + if (fromInclusive) + e = ceilingEntry(fromKey); + else + e = higherEntry(fromKey); + } + + K first = e != null ? e.getKey() : null; + if (e == null || toKey != null && !inToRange(first, false)) + throw new NoSuchElementException(); + return first; + } + + public K lastKey() { + TrieEntry e; + if (toKey == null) { + e = lastEntry(); + } else { + if (toInclusive) + e = floorEntry(toKey); + else + e = lowerEntry(toKey); + } + + K last = e != null ? e.getKey() : null; + if (e == null || fromKey != null && !inFromRange(last, false)) + throw new NoSuchElementException(); + return last; + } + + @Override + public Set> entrySet() { + if (entrySet == null) + entrySet = newSubMapEntrySet(); + return entrySet; + } + + protected Set> newSubMapEntrySet() { + return new EntrySetView(); + } + + public SortedMap subMap(K fromKey, K toKey) { + if (!inRange2(fromKey)) + throw new IllegalArgumentException("fromKey out of range"); + if (!inRange2(toKey)) + throw new IllegalArgumentException("toKey out of range"); + return new SubMap(fromKey, toKey); + } + + public SortedMap headMap(K toKey) { + if (!inRange2(toKey)) + throw new IllegalArgumentException("toKey out of range"); + return new SubMap(fromKey, toKey); + } + + public SortedMap tailMap(K fromKey) { + if (!inRange2(fromKey)) + throw new IllegalArgumentException("fromKey out of range"); + return new SubMap(fromKey, toKey); + } + + protected boolean inRange(K key) { + return (fromKey == null || inFromRange(key, false)) && + (toKey == null || inToRange(key, false)); + } + + // This form allows the high endpoint (as well as all legit keys) + protected boolean inRange2(K key) { + return (fromKey == null || inFromRange(key, false)) && + (toKey == null || inToRange(key, true)); + } + + protected boolean inToRange(K key, boolean forceInclusive) { + int ret = keyAnalyzer.compare(key, toKey); + if (toInclusive || forceInclusive) + return ret <= 0; + else + return ret < 0; + } + + protected boolean inFromRange(K key, boolean forceInclusive) { + int ret = keyAnalyzer.compare(key, fromKey); + if (fromInclusive || forceInclusive) + return ret >= 0; + else + return ret > 0; + } + + class EntrySetView extends AbstractSet> { + private transient int size = -1, sizeModCount; + + @Override + public int size() { + if (size == -1 || sizeModCount != PatriciaTrie.this.modCount) { + size = 0; + sizeModCount = PatriciaTrie.this.modCount; + Iterator i = iterator(); + while (i.hasNext()) { + size++; + i.next(); + } + } + return size; + } + + @Override + public boolean isEmpty() { + return !iterator().hasNext(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry) o; + K key = entry.getKey(); + if (!inRange(key)) + return false; + TrieEntry node = getEntry(key); + return node != null && + valEquals(node.getValue(), entry.getValue()); + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry) o; + K key = entry.getKey(); + if (!inRange(key)) + return false; + TrieEntry node = getEntry(key); + if (node != null && valEquals(node.getValue(), entry.getValue())) { + removeEntry(node); + return true; + } + return false; + } + + @Override + public Iterator> iterator() { + return new SubMapEntryIterator( + (fromKey == null ? firstEntry() : ceilingEntry(fromKey)), + (toKey == null ? null : ceilingEntry(toKey))); + } + } + } +} + + + + + + + diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/Trie.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/Trie.java new file mode 100644 index 0000000..e4131dd --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/Trie.java @@ -0,0 +1,157 @@ +package org.xbib.datastructures.trie.limewire; + +import java.util.Map; +import java.util.SortedMap; + +/** + * Defines the interface for a prefix tree, an ordered tree data structure. For + * more information, see Tries. + * + * @author Roger Kapsi + * @author Sam Berlin + */ +public interface Trie extends SortedMap { + + /** + * Returns a view of this Trie of all elements that are + * prefixed by the given key. + *

+ * In a fixed-keysize Trie, this is essentially a 'get' operation. + *

+ * For example, if the Trie contains 'Lime', 'LimeWire', + * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then + * a lookup of 'Lime' would return 'Lime', 'LimeRadio', and 'LimeWire'. + */ + SortedMap getPrefixedBy(K key); + + /** + * Returns a view of this Trie of all elements that are + * prefixed by the length of the key. + *

+ * Fixed-keysize Tries will not support this operation + * (because all keys will be the same length). + *

+ * For example, if the Trie contains 'Lime', 'LimeWire', + * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then + * a lookup of 'LimePlastics' with a length of 4 would + * return 'Lime', 'LimeRadio', and 'LimeWire'. + */ + SortedMap getPrefixedBy(K key, int length); + + /** + * Returns a view of this Trie of all elements that are prefixed + * by the key, starting at the given offset and for the given length. + *

+ * Fixed-keysize Tries will not support this operation + * (because all keys are the same length). + *

+ * For example, if the Trie contains 'Lime', 'LimeWire', + * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then + * a lookup of 'The Lime Plastics' with an offset of 4 and a + * length of 4 would return 'Lime', 'LimeRadio', and 'LimeWire'. + */ + SortedMap getPrefixedBy(K key, int offset, int length); + + /** + * Returns a view of this Trie of all elements that are prefixed + * by the number of bits in the given Key. + *

+ * Fixed-keysize Tries can support this operation as a way to do + * lookups of partial keys. That is, if the Trie is storing IP + * addresses, you can lookup all addresses that begin with + * '192.168' by providing the key '192.168.X.X' and a length of 16 + * would return all addresses that begin with '192.168'. + */ + SortedMap getPrefixedByBits(K key, int bitLength); + + /** + * Returns the value for the entry whose key is closest in a bitwise + * XOR metric to the given key. This is NOT lexicographic closeness. + * For example, given the keys:

+ * D = 1000100
+ * H = 1001000
+ * L = 1001100
+ *

+ * If the Trie contained 'H' and 'L', a lookup of 'D' would return 'L', + * because the XOR distance between D & L is smaller than the XOR distance + * between D & H. + */ + V select(K key); + + /** + * Iterates through the Trie, starting with the entry whose bitwise + * value is closest in an XOR metric to the given key. After the closest + * entry is found, the Trie will call select on that entry and continue + * calling select for each entry (traversing in order of XOR closeness, + * NOT lexicographically) until the cursor returns + * Cursor.SelectStatus.EXIT.

+ * The cursor can return Cursor.SelectStatus.CONTINUE to + * continue traversing.

+ * Cursor.SelectStatus.REMOVE_AND_EXIT is used to remove the current element + * and stop traversing. + *

+ * Note: The {@link Cursor.SelectStatus#REMOVE} operation is not supported. + * + * @return The entry the cursor returned EXIT on, or null if it continued + * till the end. + */ + Map.Entry select(K key, Cursor cursor); + + /** + * Traverses the Trie in lexicographical order. Cursor.select + * will be called on each entry.

+ * The traversal will stop when the cursor returns Cursor.SelectStatus.EXIT.

+ * Cursor.SelectStatus.CONTINUE is used to continue traversing.

+ * Cursor.SelectStatus.REMOVE is used to remove the element that was + * selected and continue traversing.

+ * Cursor.SelectStatus.REMOVE_AND_EXIT is used to remove the current element + * and stop traversing. + * + * @return The entry the cursor returned EXIT on, or null if it continued + * till the end. + */ + Map.Entry traverse(Cursor cursor); + + /** + * An interface used by a {@link Trie}. A {@link Trie} selects items by + * closeness and passes the items to the Cursor. You can then + * decide what to do with the key-value pair and the return value + * from {@link #select(java.util.Map.Entry)} tells the Trie + * what to do next. + *

+ * Cursor returns status/selection status might be: + * + * + * + * + * + * + *
Return ValueStatus
EXITFinish the Trie operation
CONTINUELook at the next element in the traversal
REMOVE_AND_EXITRemove the entry and stop iterating
REMOVERemove the entry and continue iterating
+ *

+ * Note: {@link Trie#select(Object, Trie.Cursor)} does + * not support REMOVE. + * + * @param Key Type + * @param Key Value + */ + interface Cursor { + + /** + * Notification that the Trie is currently looking at the given entry. + * Return EXIT to finish the Trie operation, + * CONTINUE to look at the next entry, REMOVE + * to remove the entry and continue iterating, or + * REMOVE_AND_EXIT to remove the entry and stop iterating. + * Not all operations support REMOVE. + */ + SelectStatus select(Map.Entry entry); + + /** + * The mode during selection. + */ + enum SelectStatus { + EXIT, CONTINUE, REMOVE, REMOVE_AND_EXIT + } + } +} + diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/UnmodifiableIterator.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/UnmodifiableIterator.java new file mode 100644 index 0000000..7b9c2e4 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/limewire/UnmodifiableIterator.java @@ -0,0 +1,21 @@ +package org.xbib.datastructures.trie.limewire; + +import java.util.Iterator; + +/** + * A convenience class to aid in developing iterators that cannot be modified. + */ +public abstract class UnmodifiableIterator implements Iterator { + /** + * Throws UnsupportedOperationException. + */ + public final void remove() { + throw new UnsupportedOperationException(); + } +} + + + + + + diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractKeyAnalyzer.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractKeyAnalyzer.java new file mode 100644 index 0000000..b7dd716 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractKeyAnalyzer.java @@ -0,0 +1,13 @@ +package org.xbib.datastructures.trie.patricia; + +/** + * An abstract implementation of {@link KeyAnalyzer}. + */ +public abstract class AbstractKeyAnalyzer implements KeyAnalyzer { + + @SuppressWarnings("unchecked") + @Override + public int compare(K o1, K o2) { + return ((Comparable) o1).compareTo(o2); + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractPatriciaTrie.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractPatriciaTrie.java new file mode 100644 index 0000000..36d9397 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractPatriciaTrie.java @@ -0,0 +1,1105 @@ +package org.xbib.datastructures.trie.patricia; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * This class implements the base PATRICIA algorithm and everything that + * is related to the {@link Map} interface. + */ +abstract class AbstractPatriciaTrie extends AbstractTrie { + + /** + * The root node of the {@link PrefixTree}. + */ + final TrieEntry root = new TrieEntry<>(null, null, -1); + /** + * The number of times this {@link PrefixTree} has been modified. + * It's used to detect concurrent modifications and fail-fast + * the {@link Iterator}s. + */ + transient int modCount = 0; + /** + * Each of these fields are initialized to contain an instance of the + * appropriate view the first time this view is requested. The views are + * stateless, so there's no reason to create more than one of each. + */ + private transient volatile Set keySet; + + private transient volatile Collection values; + + private transient volatile Set> entrySet; + + /** + * The current size of the {@link PrefixTree} + */ + private int size = 0; + + public AbstractPatriciaTrie() { + super(); + } + + public AbstractPatriciaTrie(KeyAnalyzer keyAnalyzer) { + super(keyAnalyzer); + } + + public AbstractPatriciaTrie(Map m) { + super(); + putAll(m); + } + + public AbstractPatriciaTrie(KeyAnalyzer keyAnalyzer, + Map m) { + super(keyAnalyzer); + putAll(m); + } + + /** + * Returns true if 'next' is a valid uplink coming from 'from'. + */ + static boolean isValidUplink(TrieEntry next, TrieEntry from) { + return next != null && next.bitIndex <= from.bitIndex && !next.isEmpty(); + } + + @Override + public void clear() { + root.key = null; + root.bitIndex = -1; + root.value = null; + root.parent = null; + root.left = root; + root.right = null; + root.predecessor = root; + size = 0; + incrementModCount(); + } + + @Override + public int size() { + return size; + } + + /** + * A helper method to increment the {@link PrefixTree} size + * and the modification counter. + */ + void incrementSize() { + size++; + incrementModCount(); + } + + /** + * A helper method to decrement the {@link PrefixTree} size + * and increment the modification counter. + */ + void decrementSize() { + size--; + incrementModCount(); + } + + /** + * A helper method to increment the modification counter. + */ + private void incrementModCount() { + ++modCount; + } + + @Override + public V put(K key, V value) { + if (key == null) { + throw new NullPointerException("Key cannot be null"); + } + int lengthInBits = lengthInBits(key); + // The only place to store a key with a length + // of zero bits is the root node + if (lengthInBits == 0) { + if (root.isEmpty()) { + incrementSize(); + } else { + incrementModCount(); + } + return root.setKeyValue(key, value); + } + TrieEntry found = getNearestEntryForKey(key); + if (compareKeys(key, found.key)) { + if (found.isEmpty()) { // <- must be the root + incrementSize(); + } else { + incrementModCount(); + } + return found.setKeyValue(key, value); + } + + int bitIndex = bitIndex(key, found.key); + if (bitIndex != KeyAnalyzer.OUT_OF_BOUNDS_BIT_KEY) { + if (0 <= bitIndex) { // in 99.999...9% the case + /* NEW KEY+VALUE TUPLE */ + TrieEntry t = new TrieEntry<>(key, value, bitIndex); + addEntry(t); + incrementSize(); + return null; + } else if (bitIndex == KeyAnalyzer.NULL_BIT_KEY) { + // A bits of the Key are zero. The only place to + // store such a Key is the root Node! + /* NULL BIT KEY */ + if (root.isEmpty()) { + incrementSize(); + } else { + incrementModCount(); + } + return root.setKeyValue(key, value); + } else if (bitIndex == KeyAnalyzer.EQUAL_BIT_KEY) { + // This is a very special and rare case. + /* REPLACE OLD KEY+VALUE */ + if (found != root) { + incrementModCount(); + return found.setKeyValue(key, value); + } + } + } + throw new IndexOutOfBoundsException("Failed to put: " + key + " -> " + value + ", " + bitIndex); + } + + /** + * Adds the given {@link TrieEntry} to the {@link PrefixTree} + */ + TrieEntry addEntry(TrieEntry entry) { + TrieEntry current = root.left; + TrieEntry path = root; + while (true) { + if (current.bitIndex >= entry.bitIndex + || current.bitIndex <= path.bitIndex) { + entry.predecessor = entry; + + if (!isBitSet(entry.key, entry.bitIndex)) { + entry.left = entry; + entry.right = current; + } else { + entry.left = current; + entry.right = entry; + } + + entry.parent = path; + if (current.bitIndex >= entry.bitIndex) { + current.parent = entry; + } + + // if we inserted an uplink, set the predecessor on it + if (current.bitIndex <= path.bitIndex) { + current.predecessor = entry; + } + + if (path == root || !isBitSet(entry.key, path.bitIndex)) { + path.left = entry; + } else { + path.right = entry; + } + + return entry; + } + + path = current; + + if (!isBitSet(entry.key, current.bitIndex)) { + current = current.left; + } else { + current = current.right; + } + } + } + + @Override + public V get(Object k) { + TrieEntry entry = getEntry(k); + return entry != null ? entry.getValue() : null; + } + + /** + * Returns the entry associated with the specified key in the + * AbstractPatriciaTrie. Returns null if the map contains no mapping + * for this key. + *

+ * This may throw ClassCastException if the object is not of type K. + */ + @SuppressWarnings("unchecked") + TrieEntry getEntry(Object k) { + K key = (K) k; + if (key == null) { + return null; + } + + TrieEntry entry = getNearestEntryForKey(key); + return !entry.isEmpty() && compareKeys(key, entry.key) ? entry : null; + } + + @Override + public Map.Entry select(K key) { + Reference> reference + = new Reference>(); + if (!selectR(root.left, -1, key, reference)) { + return reference.get(); + } + return null; + } + + @Override + public Map.Entry select(K key, Cursor cursor) { + Reference> reference + = new Reference>(); + selectR(root.left, -1, key, cursor, reference); + return reference.get(); + } + + private boolean selectR(TrieEntry h, int bitIndex, + final K key, final Reference> reference) { + if (h.bitIndex <= bitIndex) { + // If we hit the root Node and it is empty + // we have to look for an alternative best + // matching node. + if (!h.isEmpty()) { + reference.set(h); + return false; + } + return true; + } + + if (!isBitSet(key, h.bitIndex)) { + if (selectR(h.left, h.bitIndex, key, reference)) { + return selectR(h.right, h.bitIndex, key, reference); + } + } else { + if (selectR(h.right, h.bitIndex, key, reference)) { + return selectR(h.left, h.bitIndex, key, reference); + } + } + return false; + } + + /** + * + */ + private boolean selectR(TrieEntry h, int bitIndex, + final K key, final Cursor cursor, + final Reference> reference) { + + if (h.bitIndex <= bitIndex) { + if (!h.isEmpty()) { + Cursor.Decision decision = cursor.select(h); + switch (decision) { + case REMOVE: + throw new UnsupportedOperationException( + "Cannot remove during select"); + case EXIT: + reference.set(h); + return false; // exit + case REMOVE_AND_EXIT: + TrieEntry entry = new TrieEntry( + h.getKey(), h.getValue(), -1); + reference.set(entry); + removeEntry(h); + return false; + case CONTINUE: + // fall through. + } + } + return true; // continue + } + + if (!isBitSet(key, h.bitIndex)) { + if (selectR(h.left, h.bitIndex, key, cursor, reference)) { + return selectR(h.right, h.bitIndex, key, cursor, reference); + } + } else { + if (selectR(h.right, h.bitIndex, key, cursor, reference)) { + return selectR(h.left, h.bitIndex, key, cursor, reference); + } + } + + return false; + } + + @Override + public Map.Entry traverse(Cursor cursor) { + TrieEntry entry = nextEntry(null); + while (entry != null) { + TrieEntry current = entry; + Cursor.Decision decision = cursor.select(current); + entry = nextEntry(current); + switch (decision) { + case EXIT: + return current; + case REMOVE: + removeEntry(current); + break; // out of switch, stay in while loop + case REMOVE_AND_EXIT: + Map.Entry value = new TrieEntry( + current.getKey(), current.getValue(), -1); + removeEntry(current); + return value; + case CONTINUE: // do nothing. + } + } + + return null; + } + + @SuppressWarnings("unchecked") + @Override + public boolean containsKey(Object k) { + if (k == null) { + return false; + } + K key = (K) k; + TrieEntry entry = getNearestEntryForKey(key); + return !entry.isEmpty() && compareKeys(key, entry.key); + } + + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = new EntrySet(); + } + return entrySet; + } + + @Override + public Set keySet() { + if (keySet == null) { + keySet = new KeySet(); + } + return keySet; + } + + @Override + public Collection values() { + if (values == null) { + values = new Values(); + } + return values; + } + + @SuppressWarnings("unchecked") + @Override + public V remove(Object k) { + if (k == null) { + return null; + } + K key = (K) k; + TrieEntry current = root.left; + TrieEntry path = root; + while (true) { + if (current.bitIndex <= path.bitIndex) { + if (!current.isEmpty() && compareKeys(key, current.key)) { + return removeEntry(current); + } else { + return null; + } + } + + path = current; + + if (!isBitSet(key, current.bitIndex)) { + current = current.left; + } else { + current = current.right; + } + } + } + + /** + * Returns the nearest entry for a given key. This is useful + * for finding knowing if a given key exists (and finding the value + * for it), or for inserting the key. + *

+ * The actual get implementation. This is very similar to + * selectR but with the exception that it might return the + * root Entry even if it's empty. + */ + TrieEntry getNearestEntryForKey(K key) { + TrieEntry current = root.left; + TrieEntry path = root; + while (true) { + if (current.bitIndex <= path.bitIndex) { + return current; + } + + path = current; + if (!isBitSet(key, current.bitIndex)) { + current = current.left; + } else { + current = current.right; + } + } + } + + /** + * Removes a single entry from the {@link PrefixTree}. + *

+ * If we found a Key (Entry h) then figure out if it's + * an internal (hard to remove) or external Entry (easy + * to remove) + */ + V removeEntry(TrieEntry h) { + if (h != root) { + if (h.isInternalNode()) { + removeInternalEntry(h); + } else { + removeExternalEntry(h); + } + } + + decrementSize(); + return h.setKeyValue(null, null); + } + + /** + * Removes an external entry from the {@link PrefixTree}. + *

+ * If it's an external Entry then just remove it. + * This is very easy and straight forward. + */ + private void removeExternalEntry(TrieEntry h) { + if (h == root) { + throw new IllegalArgumentException("Cannot delete root Entry!"); + } else if (!h.isExternalNode()) { + throw new IllegalArgumentException(h + " is not an external Entry!"); + } + + TrieEntry parent = h.parent; + TrieEntry child = (h.left == h) ? h.right : h.left; + + if (parent.left == h) { + parent.left = child; + } else { + parent.right = child; + } + + // either the parent is changing, or the predecessor is changing. + if (child.bitIndex > parent.bitIndex) { + child.parent = parent; + } else { + child.predecessor = parent; + } + + } + + /** + * Removes an internal entry from the {@link PrefixTree}. + *

+ * If it's an internal Entry then "good luck" with understanding + * this code. The Idea is essentially that Entry p takes Entry h's + * place in the trie which requires some re-wiring. + */ + private void removeInternalEntry(TrieEntry h) { + if (h == root) { + throw new IllegalArgumentException("Cannot delete root Entry!"); + } else if (!h.isInternalNode()) { + throw new IllegalArgumentException(h + " is not an internal Entry!"); + } + + TrieEntry p = h.predecessor; + + // Set P's bitIndex + p.bitIndex = h.bitIndex; + + // Fix P's parent, predecessor and child Nodes + { + TrieEntry parent = p.parent; + TrieEntry child = (p.left == h) ? p.right : p.left; + + // if it was looping to itself previously, + // it will now be pointed from it's parent + // (if we aren't removing it's parent -- + // in that case, it remains looping to itself). + // otherwise, it will continue to have the same + // predecessor. + if (p.predecessor == p && p.parent != h) { + p.predecessor = p.parent; + } + + if (parent.left == p) { + parent.left = child; + } else { + parent.right = child; + } + + if (child.bitIndex > parent.bitIndex) { + child.parent = parent; + } + } + + // Fix H's parent and child Nodes + { + // If H is a parent of its left and right child + // then change them to P + if (h.left.parent == h) { + h.left.parent = p; + } + + if (h.right.parent == h) { + h.right.parent = p; + } + + // Change H's parent + if (h.parent.left == h) { + h.parent.left = p; + } else { + h.parent.right = p; + } + } + + // Copy the remaining fields from H to P + //p.bitIndex = h.bitIndex; + p.parent = h.parent; + p.left = h.left; + p.right = h.right; + + // Make sure that if h was pointing to any uplinks, + // p now points to them. + if (isValidUplink(p.left, p)) { + p.left.predecessor = p; + } + + if (isValidUplink(p.right, p)) { + p.right.predecessor = p; + } + } + + /** + * Returns the entry lexicographically after the given entry. + * If the given entry is null, returns the first node. + */ + TrieEntry nextEntry(TrieEntry node) { + if (node == null) { + return firstEntry(); + } else { + return nextEntryImpl(node.predecessor, node, null); + } + } + + /** + * Scans for the next node, starting at the specified point, and using 'previous' + * as a hint that the last node we returned was 'previous' (so we know not to return + * it again). If 'tree' is non-null, this will limit the search to the given tree. + *

+ * The basic premise is that each iteration can follow the following steps: + *

+ * 1) Scan all the way to the left. + * a) If we already started from this node last time, proceed to Step 2. + * b) If a valid uplink is found, use it. + * c) If the result is an empty node (root not set), break the scan. + * d) If we already returned the left node, break the scan. + *

+ * 2) Check the right. + * a) If we already returned the right node, proceed to Step 3. + * b) If it is a valid uplink, use it. + * c) Do Step 1 from the right node. + *

+ * 3) Back up through the parents until we encounter find a parent + * that we're not the right child of. + *

+ * 4) If there's no right child of that parent, the iteration is finished. + * Otherwise continue to Step 5. + *

+ * 5) Check to see if the right child is a valid uplink. + * a) If we already returned that child, proceed to Step 6. + * Otherwise, use it. + *

+ * 6) If the right child of the parent is the parent itself, we've + * already found & returned the end of the Trie, so exit. + *

+ * 7) Do Step 1 on the parent's right child. + */ + TrieEntry nextEntryImpl(TrieEntry start, + TrieEntry previous, TrieEntry tree) { + + TrieEntry current = start; + + // Only look at the left if this was a recursive or + // the first check, otherwise we know we've already looked + // at the left. + if (previous == null || start != previous.predecessor) { + while (!current.left.isEmpty()) { + // stop traversing if we've already + // returned the left of this node. + if (previous == current.left) { + break; + } + + if (isValidUplink(current.left, current)) { + return current.left; + } + + current = current.left; + } + } + + // If there's no data at all, exit. + if (current.isEmpty()) { + return null; + } + + // If we've already returned the left, + // and the immediate right is null, + // there's only one entry in the Trie + // which is stored at the root. + // + // / ("") <-- root + // \_/ \ + // null <-- 'current' + // + if (current.right == null) { + return null; + } + + // If nothing valid on the left, try the right. + if (previous != current.right) { + // See if it immediately is valid. + if (isValidUplink(current.right, current)) { + return current.right; + } + + // Must search on the right's side if it wasn't initially valid. + return nextEntryImpl(current.right, previous, tree); + } + + // Neither left nor right are valid, find the first parent + // whose child did not come from the right & traverse it. + while (current == current.parent.right) { + // If we're going to traverse to above the subtree, stop. + if (current == tree) { + return null; + } + + current = current.parent; + } + + // If we're on the top of the subtree, we can't go any higher. + if (current == tree) { + return null; + } + + // If there's no right, the parent must be root, so we're done. + if (current.parent.right == null) { + return null; + } + + // If the parent's right points to itself, we've found one. + if (previous != current.parent.right + && isValidUplink(current.parent.right, current.parent)) { + return current.parent.right; + } + + // If the parent's right is itself, there can't be any more nodes. + if (current.parent.right == current.parent) { + return null; + } + + // We need to traverse down the parent's right's path. + return nextEntryImpl(current.parent.right, previous, tree); + } + + /** + * Returns the first entry the {@link PrefixTree} is storing. + *

+ * This is implemented by going always to the left until + * we encounter a valid uplink. That uplink is the first key. + */ + TrieEntry firstEntry() { + // if Trie is empty, no first node. + if (isEmpty()) { + return null; + } + + return followLeft(root); + } + + /** + * Goes left through the tree until it finds a valid node. + */ + TrieEntry followLeft(TrieEntry node) { + while (true) { + TrieEntry child = node.left; + // if we hit root and it didn't have a node, go right instead. + if (child.isEmpty()) { + child = node.right; + } + + if (child.bitIndex <= node.bitIndex) { + return child; + } + + node = child; + } + } + + /** + * A {@link Reference} allows us to return something through a Method's + * argument list. An alternative would be to an Array with a length of + * one (1) but that leads to compiler warnings. Computationally and memory + * wise there's no difference (except for the need to load the + * {@link Reference} Class but that happens only once). + */ + private static class Reference { + + private E item; + + public void set(E item) { + this.item = item; + } + + public E get() { + return item; + } + } + + /** + * A {@link PrefixTree} is a set of {@link TrieEntry} nodes + */ + static class TrieEntry extends BasicEntry { + + private static final long serialVersionUID = 4596023148184140013L; + + /** + * The index this entry is comparing. + */ + protected int bitIndex; + + /** + * The parent of this entry. + */ + protected TrieEntry parent; + + /** + * The left child of this entry. + */ + protected TrieEntry left; + + /** + * The right child of this entry. + */ + protected TrieEntry right; + + /** + * The entry who uplinks to this entry. + */ + protected TrieEntry predecessor; + + public TrieEntry(K key, V value, int bitIndex) { + super(key, value); + + this.bitIndex = bitIndex; + + this.parent = null; + this.left = this; + this.right = null; + this.predecessor = this; + } + + /** + * Whether or not the entry is storing a key. + * Only the root can potentially be empty, all other + * nodes must have a key. + */ + public boolean isEmpty() { + return key == null; + } + + /** + * Neither the left nor right child is a loopback + */ + public boolean isInternalNode() { + return left != this && right != this; + } + + /** + * Either the left or right child is a loopback + */ + public boolean isExternalNode() { + return !isInternalNode(); + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + + if (bitIndex == -1) { + buffer.append("RootEntry("); + } else { + buffer.append("Entry("); + } + + buffer.append("key=").append(getKey()).append(" [").append(bitIndex).append("], "); + buffer.append("value=").append(getValue()).append(", "); + //buffer.append("bitIndex=").append(bitIndex).append(", "); + + if (parent != null) { + if (parent.bitIndex == -1) { + buffer.append("parent=").append("ROOT"); + } else { + buffer.append("parent=").append(parent.getKey()).append(" [").append(parent.bitIndex).append("]"); + } + } else { + buffer.append("parent=").append("null"); + } + buffer.append(", "); + + if (left != null) { + if (left.bitIndex == -1) { + buffer.append("left=").append("ROOT"); + } else { + buffer.append("left=").append(left.getKey()).append(" [").append(left.bitIndex).append("]"); + } + } else { + buffer.append("left=").append("null"); + } + buffer.append(", "); + + if (right != null) { + if (right.bitIndex == -1) { + buffer.append("right=").append("ROOT"); + } else { + buffer.append("right=").append(right.getKey()).append(" [").append(right.bitIndex).append("]"); + } + } else { + buffer.append("right=").append("null"); + } + buffer.append(", "); + + if (predecessor != null) { + if (predecessor.bitIndex == -1) { + buffer.append("predecessor=").append("ROOT"); + } else { + buffer.append("predecessor=").append(predecessor.getKey()).append(" [").append(predecessor.bitIndex).append("]"); + } + } + + buffer.append(")"); + return buffer.toString(); + } + } + + + /** + * This is a entry set view of the {@link PrefixTree} as returned + * by {@link Map#entrySet()} + */ + private class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + TrieEntry candidate = getEntry(((Map.Entry) o).getKey()); + return candidate != null && candidate.equals(o); + } + + @Override + public boolean remove(Object o) { + int size = size(); + AbstractPatriciaTrie.this.remove(o); + return size != size(); + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + /** + * An {@link Iterator} that returns {@link Entry} Objects + */ + private class EntryIterator extends TrieIterator> { + @Override + public Map.Entry next() { + return nextEntry(); + } + } + } + + /** + * This is a key set view of the {@link PrefixTree} as returned + * by {@link Map#keySet()} + */ + private class KeySet extends AbstractSet { + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public boolean contains(Object o) { + return containsKey(o); + } + + @Override + public boolean remove(Object o) { + int size = size(); + AbstractPatriciaTrie.this.remove(o); + return size != size(); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + /** + * An {@link Iterator} that returns Key Objects + */ + private class KeyIterator extends TrieIterator { + @Override + public K next() { + return nextEntry().getKey(); + } + } + } + + /** + * This is a value view of the {@link PrefixTree} as returned + * by {@link Map#values()} + */ + private class Values extends AbstractCollection { + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + @Override + public boolean remove(Object o) { + for (Iterator it = iterator(); it.hasNext(); ) { + V value = it.next(); + if ((value == null ? o == null : value.equals(o))) { + it.remove(); + return true; + } + } + return false; + } + + /** + * An {@link Iterator} that returns Value Objects + */ + private class ValueIterator extends TrieIterator { + @Override + public V next() { + return nextEntry().getValue(); + } + } + } + + /** + * An iterator for the entries. + */ + abstract class TrieIterator implements Iterator { + + /** + * For fast-fail + */ + protected int expectedModCount = AbstractPatriciaTrie.this.modCount; + + protected TrieEntry next; // the next node to return + protected TrieEntry current; // the current entry we're on + + /** + * Starts iteration from the root + */ + protected TrieIterator() { + next = AbstractPatriciaTrie.this.nextEntry(null); + } + + /** + * Starts iteration at the given entry + */ + protected TrieIterator(TrieEntry firstEntry) { + next = firstEntry; + } + + /** + * Returns the next {@link TrieEntry} + */ + protected TrieEntry nextEntry() { + if (expectedModCount != AbstractPatriciaTrie.this.modCount) { + throw new ConcurrentModificationException(); + } + + TrieEntry e = next; + if (e == null) { + throw new NoSuchElementException(); + } + + next = findNext(e); + current = e; + return e; + } + + /** + * @see PatriciaTrie#nextEntry(TrieEntry) + */ + protected TrieEntry findNext(TrieEntry prior) { + return AbstractPatriciaTrie.this.nextEntry(prior); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public void remove() { + if (current == null) { + throw new IllegalStateException(); + } + + if (expectedModCount != AbstractPatriciaTrie.this.modCount) { + throw new ConcurrentModificationException(); + } + + TrieEntry node = current; + current = null; + AbstractPatriciaTrie.this.removeEntry(node); + + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractTrie.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractTrie.java new file mode 100644 index 0000000..58e98bf --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/AbstractTrie.java @@ -0,0 +1,193 @@ +package org.xbib.datastructures.trie.patricia; + +import java.util.AbstractMap; +import java.util.Map; +import java.util.Objects; + +/** + * This class provides some basic {@link PrefixTree} functionality and + * utility methods for actual {@link PrefixTree} implementations. + */ +abstract class AbstractTrie extends AbstractMap implements PrefixTree { + + /** + * The {@link KeyAnalyzer} that's being used to build the + * PATRICIA {@link PrefixTree} + */ + protected final KeyAnalyzer keyAnalyzer; + + public AbstractTrie() { + this(DefaultKeyAnalyzer.singleton()); + } + + /** + * Constructs a new {@link PrefixTree} using the given {@link KeyAnalyzer} + */ + public AbstractTrie(KeyAnalyzer keyAnalyzer) { + this.keyAnalyzer = Objects.requireNonNull(keyAnalyzer, "keyAnalyzer"); + } + + /** + * Returns the {@link KeyAnalyzer} that constructed the {@link PrefixTree}. + */ + public KeyAnalyzer getKeyAnalyzer() { + return keyAnalyzer; + } + + @Override + public K selectKey(K key) { + Map.Entry entry = select(key); + return entry != null ? entry.getKey() : null; + } + + @Override + public V selectValue(K key) { + Map.Entry entry = select(key); + return entry != null ? entry.getValue() : null; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("Trie[").append(size()).append("]={\n"); + for (Map.Entry entry : entrySet()) { + buffer.append(" ").append(entry).append("\n"); + } + buffer.append("}\n"); + return buffer.toString(); + } + + /** + * Returns the length of the given key in bits + * + * @see KeyAnalyzer#lengthInBits(Object) + */ + final int lengthInBits(K key) { + if (key == null) { + return 0; + } + return keyAnalyzer.lengthInBits(key); + } + + /** + * Returns whether or not the given bit on the + * key is set or false if the key is null. + * + * @see KeyAnalyzer#isBitSet(Object, int) + */ + final boolean isBitSet(K key, int bitIndex) { + if (key == null) { // root's might be null! + return false; + } + return keyAnalyzer.isBitSet(key, bitIndex); + } + + /** + * Utility method for calling {@link KeyAnalyzer#bitIndex(Object, Object)} + */ + final int bitIndex(K key, K otherKey) { + if (key != null && otherKey != null) { + return keyAnalyzer.bitIndex(key, otherKey); + } else if (key != null) { + return bitIndex(key); + } else if (otherKey != null) { + return bitIndex(otherKey); + } + return KeyAnalyzer.NULL_BIT_KEY; + } + + private int bitIndex(K key) { + int lengthInBits = lengthInBits(key); + for (int i = 0; i < lengthInBits; i++) { + if (isBitSet(key, i)) { + return i; + } + } + return KeyAnalyzer.NULL_BIT_KEY; + } + + /** + * An utility method for calling {@link KeyAnalyzer#compare(Object, Object)} + */ + final boolean compareKeys(K key, K other) { + if (key == null) { + return (other == null); + } else if (other == null) { + return false; + } + return keyAnalyzer.compare(key, other) == 0; + } + + /** + * A basic implementation of {@link Entry} + */ + abstract static class BasicEntry implements Map.Entry { + + protected K key; + + protected V value; + + private transient int hashCode = 0; + + public BasicEntry(K key) { + this.key = key; + } + + public BasicEntry(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * Replaces the current key and value with the provided + * key & value + */ + public V setKeyValue(K key, V value) { + this.key = key; + this.hashCode = 0; + return setValue(value); + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + V previous = this.value; + this.value = value; + return previous; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = (key != null ? key.hashCode() : 0); + } + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry other = (Map.Entry) o; + return (key == null ? other.getKey() == null : key.equals(other.getKey())) && + (value == null ? other.getValue() == null : value.equals(other.getValue())); + } + + @Override + public String toString() { + return key + "=" + value; + } + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/Cursor.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/Cursor.java new file mode 100644 index 0000000..c308fad --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/Cursor.java @@ -0,0 +1,57 @@ +package org.xbib.datastructures.trie.patricia; + +import java.util.Map; +import java.util.Map.Entry; + +/** + * A {@link Cursor} can be used to traverse a {@link PrefixTree}, visit each node + * step by step and make {@link Decision}s on each step how to continue with + * traversing the {@link PrefixTree}. + */ +public interface Cursor { + + /** + * Called for each {@link Entry} in the {@link PrefixTree}. Return + * {@link Decision#EXIT} to finish the {@link PrefixTree} operation, + * {@link Decision#CONTINUE} to go to the next {@link Entry}, + * {@link Decision#REMOVE} to remove the {@link Entry} and + * continue iterating or {@link Decision#REMOVE_AND_EXIT} to + * remove the {@link Entry} and stop iterating. + *

+ * Note: Not all operations support {@link Decision#REMOVE}. + */ + Decision select(Map.Entry entry); + + /** + * The {@link Decision} tells the {@link Cursor} what to do on each step + * while traversing the {@link PrefixTree}. + *

+ * NOTE: Not all operations that work with a {@link Cursor} support all + * {@link Decision} types + */ + enum Decision { + + /** + * Exit the traverse operation + */ + EXIT, + + /** + * Continue with the traverse operation + */ + CONTINUE, + + /** + * Remove the previously returned element + * from the {@link PrefixTree} and continue + */ + REMOVE, + + /** + * Remove the previously returned element + * from the {@link PrefixTree} and exit from the + * traverse operation + */ + REMOVE_AND_EXIT + } +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/DefaultKeyAnalyzer.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/DefaultKeyAnalyzer.java new file mode 100644 index 0000000..821be8a --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/DefaultKeyAnalyzer.java @@ -0,0 +1,34 @@ +package org.xbib.datastructures.trie.patricia; + +/** + * An implementation of {@link KeyAnalyzer} that assumes all keys have the {@link Key} interface implemented. + */ +public class DefaultKeyAnalyzer> extends AbstractKeyAnalyzer { + @SuppressWarnings("rawtypes") + private static final DefaultKeyAnalyzer INSTANCE = new DefaultKeyAnalyzer(); + + @SuppressWarnings("unchecked") + public static KeyAnalyzer singleton() { + return (KeyAnalyzer) INSTANCE; + } + + @Override + public int lengthInBits(K key) { + return key.lengthInBits(); + } + + @Override + public boolean isBitSet(K key, int bitIndex) { + return key.isBitSet(bitIndex); + } + + @Override + public int bitIndex(K key, K otherKey) { + return key.bitIndex(otherKey); + } + + @Override + public boolean isPrefix(K key, K prefix) { + return key.isPrefixedBy(prefix); + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/Key.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/Key.java new file mode 100644 index 0000000..39136fe --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/Key.java @@ -0,0 +1,30 @@ +package org.xbib.datastructures.trie.patricia; + +/** + * An interface that {@link PatriciaTrie} keys may implement. + * + * @see KeyAnalyzer + * @see DefaultKeyAnalyzer + */ +public interface Key { + + /** + * Returns the key's length in bits. + */ + int lengthInBits(); + + /** + * Returns {@code true} if the given bit is set. + */ + boolean isBitSet(int bitIndex); + + /** + * Returns the index of the first bit that is different in the two keys. + */ + int bitIndex(K otherKey); + + /** + * Returns {@code true} if this key is prefixed by the given key. + */ + boolean isPrefixedBy(K prefix); +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/KeyAnalyzer.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/KeyAnalyzer.java new file mode 100644 index 0000000..00a1b46 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/KeyAnalyzer.java @@ -0,0 +1,49 @@ +package org.xbib.datastructures.trie.patricia; + +import java.util.Comparator; + +/** + * The {@link KeyAnalyzer} provides bit-level access to keys + * for the {@link PatriciaTrie}. + */ +public interface KeyAnalyzer extends Comparator { + + /** + * Returned by {@link #bitIndex(Object, Object)} if a key's + * bits were all zero (0). + */ + int NULL_BIT_KEY = -1; + + /** + * Returned by {@link #bitIndex(Object, Object)} if a the + * bits of two keys were all equal. + */ + int EQUAL_BIT_KEY = -2; + + /** + * Returned by {@link #bitIndex(Object, Object)} if a keys + * indices are out of bounds. + */ + int OUT_OF_BOUNDS_BIT_KEY = -3; + + /** + * Returns the key's length in bits. + */ + int lengthInBits(K key); + + /** + * Returns {@code true} if a key's bit it set at the given index. + */ + boolean isBitSet(K key, int bitIndex); + + /** + * Returns the index of the first bit that is different in the two keys. + */ + int bitIndex(K key, K otherKey); + + /** + * Returns {@code true} if the second argument is a + * prefix of the first argument. + */ + boolean isPrefix(K key, K prefix); +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/PatriciaTrie.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/PatriciaTrie.java new file mode 100644 index 0000000..785fbd5 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/PatriciaTrie.java @@ -0,0 +1,1203 @@ +package org.xbib.datastructures.trie.patricia; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; + +/** + *

PATRICIA {@link PrefixTree}

+ * + * Practical Algorithm to Retrieve Information Coded in Alphanumeric + * + *

A PATRICIA {@link PrefixTree} is a compressed {@link PrefixTree}. Instead of storing + * all data at the edges of the {@link PrefixTree} (and having empty internal nodes), + * PATRICIA stores data in every node. This allows for very efficient traversal, + * insert, delete, predecessor, successor, prefix, range, and {@link #select(Object)} + * operations. All operations are performed at worst in O(K) time, where K + * is the number of bits in the largest item in the tree. In practice, + * operations actually take O(A(K)) time, where A(K) is the average number of + * bits of all items in the tree. + * + *

Most importantly, PATRICIA requires very few comparisons to keys while + * doing any operation. While performing a lookup, each comparison (at most + * K of them, described above) will perform a single bit comparison against + * the given key, instead of comparing the entire key to another key. + * + *

The {@link PrefixTree} can return operations in lexicographical order using the + * {@link #traverse(Cursor)}, 'prefix', 'submap', or 'iterator' methods. The + * {@link PrefixTree} can also scan for items that are 'bitwise' (using an XOR + * metric) by the 'select' method. Bitwise closeness is determined by the + * {@link KeyAnalyzer} returning true or false for a bit being set or not in + * a given key. + * + *

Any methods here that take an {@link Object} argument may throw a + * {@link ClassCastException} if the method is expecting an instance of K + * and it isn't K. + * + * @see Radix Tree + * @see PATRICIA + * @see Crit-Bit Tree + */ +public class PatriciaTrie extends AbstractPatriciaTrie { + + public PatriciaTrie() { + super(); + } + + public PatriciaTrie(Map m) { + super(m); + } + + public PatriciaTrie(KeyAnalyzer keyAnalyzer) { + super(keyAnalyzer); + } + + public PatriciaTrie(KeyAnalyzer keyAnalyzer, + Map m) { + super(keyAnalyzer, m); + } + + @Override + public Comparator comparator() { + return keyAnalyzer; + } + + @Override + public SortedMap prefixMap(K prefix) { + int lengthInBits = lengthInBits(prefix); + if (lengthInBits == 0) { + return this; + } + + return new PrefixRangeMap(prefix); + } + + @Override + public K firstKey() { + return firstEntry().getKey(); + } + + @Override + public K lastKey() { + TrieEntry entry = lastEntry(); + if (entry != null) { + return entry.getKey(); + } + return null; + } + + @Override + public SortedMap headMap(K toKey) { + return new RangeEntryMap(null, toKey); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return new RangeEntryMap(fromKey, toKey); + } + + @Override + public SortedMap tailMap(K fromKey) { + return new RangeEntryMap(fromKey, null); + } + + /** + * Returns an entry strictly higher than the given key, + * or null if no such entry exists. + */ + private TrieEntry higherEntry(K key) { + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + if (!root.isEmpty()) { + // If data in root, and more after -- return it. + if (size() > 1) { + return nextEntry(root); + } else { // If no more after, no higher entry. + return null; + } + } else { + // Root is empty & we want something after empty, return first. + return firstEntry(); + } + } + TrieEntry found = getNearestEntryForKey(key); + if (compareKeys(key, found.key)) { + return nextEntry(found); + } + int bitIndex = bitIndex(key, found.key); + if (0 <= bitIndex) { + TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added); + incrementSize(); // must increment because remove will decrement + TrieEntry ceil = nextEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return ceil; + } else if (bitIndex == KeyAnalyzer.NULL_BIT_KEY) { + if (!root.isEmpty()) { + return firstEntry(); + } else if (size() > 1) { + return nextEntry(firstEntry()); + } else { + return null; + } + } else if (bitIndex == KeyAnalyzer.EQUAL_BIT_KEY) { + return nextEntry(found); + } + // we should have exited above + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the least key greater + * than or equal to the given key, or null if there is no such key. + */ + TrieEntry ceilingEntry(K key) { + // Basically: + // Follow the steps of adding an entry, but instead... + // + // - If we ever encounter a situation where we found an equal + // key, we return it immediately. + // + // - If we hit an empty root, return the first iterable item. + // + // - If we have to add a new item, we temporarily add it, + // find the successor to it, then remove the added item. + // + // These steps ensure that the returned value is either the + // entry for the key itself, or the first entry directly after + // the key. + + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + if (!root.isEmpty()) { + return root; + } else { + return firstEntry(); + } + } + TrieEntry found = getNearestEntryForKey(key); + if (compareKeys(key, found.key)) { + return found; + } + int bitIndex = bitIndex(key, found.key); + if (0 <= bitIndex) { + TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added); + incrementSize(); // must increment because remove will decrement + TrieEntry ceil = nextEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return ceil; + } else if (bitIndex == KeyAnalyzer.NULL_BIT_KEY) { + if (!root.isEmpty()) { + return root; + } else { + return firstEntry(); + } + } else if (bitIndex == KeyAnalyzer.EQUAL_BIT_KEY) { + return found; + } + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the greatest key + * strictly less than the given key, or null if there is no such key. + */ + TrieEntry lowerEntry(K key) { + // Basically: + // Follow the steps of adding an entry, but instead... + // + // - If we ever encounter a situation where we found an equal + // key, we return it's previousEntry immediately. + // + // - If we hit root (empty or not), return null. + // + // - If we have to add a new item, we temporarily add it, + // find the previousEntry to it, then remove the added item. + // + // These steps ensure that the returned value is always just before + // the key or null (if there was nothing before it). + + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + int lengthInBits = lengthInBits(key); + if (lengthInBits == 0) { + return null; // there can never be anything before root. + } + TrieEntry found = getNearestEntryForKey(key); + if (compareKeys(key, found.key)) { + return previousEntry(found); + } + int bitIndex = bitIndex(key, found.key); + if (0 <= bitIndex) { + TrieEntry added = new TrieEntry<>(key, null, bitIndex); + addEntry(added); + incrementSize(); // must increment because remove will decrement + TrieEntry prior = previousEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return prior; + } else if (bitIndex == KeyAnalyzer.NULL_BIT_KEY) { + return null; + } else if (bitIndex == KeyAnalyzer.EQUAL_BIT_KEY) { + return previousEntry(found); + } + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the greatest key + * less than or equal to the given key, or null if there is no such key. + */ + TrieEntry floorEntry(K key) { + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + int lengthInBits = lengthInBits(key); + if (lengthInBits == 0) { + if (!root.isEmpty()) { + return root; + } else { + return null; + } + } + TrieEntry found = getNearestEntryForKey(key); + if (compareKeys(key, found.key)) { + return found; + } + int bitIndex = bitIndex(key, found.key); + if (0 <= bitIndex) { + TrieEntry added = new TrieEntry<>(key, null, bitIndex); + addEntry(added); + incrementSize(); // must increment because remove will decrement + TrieEntry floor = previousEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return floor; + } else if (bitIndex == KeyAnalyzer.NULL_BIT_KEY) { + if (!root.isEmpty()) { + return root; + } else { + return null; + } + } else if (bitIndex == KeyAnalyzer.EQUAL_BIT_KEY) { + return found; + } + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Finds the subtree that contains the prefix. + *

+ * This is very similar to getR but with the difference that + * we stop the lookup if h.bitIndex > lengthInBits. + */ + private TrieEntry subtree(K prefix) { + int lengthInBits = lengthInBits(prefix); + + TrieEntry current = root.left; + TrieEntry path = root; + while (current.bitIndex > path.bitIndex && lengthInBits >= current.bitIndex) { + path = current; + if (!isBitSet(prefix, current.bitIndex)) { + current = current.left; + } else { + current = current.right; + } + } + // Make sure the entry is valid for a subtree. + TrieEntry entry = current.isEmpty() ? path : current; + + // If entry is root, it can't be empty. + if (entry.isEmpty()) { + return null; + } + + // if root && length of root is less than length of lookup, + // there's nothing. + // (this prevents returning the whole subtree if root has an empty + // string and we want to lookup things with "\0") + if (entry == root && lengthInBits(entry.getKey()) < lengthInBits) { + return null; + } + + // Found key's length-th bit differs from our key + // which means it cannot be the prefix... + if (isBitSet(prefix, lengthInBits) + != isBitSet(entry.key, lengthInBits)) { + return null; + } + + // ... or there are less than 'length' equal bits + int bitIndex = bitIndex(prefix, entry.key); + if (bitIndex >= 0 && bitIndex < lengthInBits) { + return null; + } + + return entry; + } + + /** + * Returns the last entry the {@link PrefixTree} is storing. + * + *

This is implemented by going always to the right until + * we encounter a valid uplink. That uplink is the last key. + */ + private TrieEntry lastEntry() { + return followRight(root.left); + } + + /** + * Traverses down the right path until it finds an uplink. + */ + private TrieEntry followRight(TrieEntry node) { + // if Trie is empty, no last entry. + if (node.right == null) { + return null; + } + + // Go as far right as possible, until we encounter an uplink. + while (node.right.bitIndex > node.bitIndex) { + node = node.right; + } + + return node.right; + } + + /** + * Returns the node lexicographically before the given node (or null if none). + *

+ * This follows four simple branches: + * - If the uplink that returned us was a right uplink: + * - If predecessor's left is a valid uplink from predecessor, return it. + * - Else, follow the right path from the predecessor's left. + * - If the uplink that returned us was a left uplink: + * - Loop back through parents until we encounter a node where + * node != node.parent.left. + * - If node.parent.left is uplink from node.parent: + * - If node.parent.left is not root, return it. + * - If it is root & root isEmpty, return null. + * - If it is root & root !isEmpty, return root. + * - If node.parent.left is not uplink from node.parent: + * - Follow right path for first right child from node.parent.left + * + * @param start + */ + private TrieEntry previousEntry(TrieEntry start) { + if (start.predecessor == null) { + throw new IllegalArgumentException("must have come from somewhere!"); + } + + if (start.predecessor.right == start) { + if (isValidUplink(start.predecessor.left, start.predecessor)) { + return start.predecessor.left; + } else { + return followRight(start.predecessor.left); + } + } else { + TrieEntry node = start.predecessor; + while (node.parent != null && node == node.parent.left) { + node = node.parent; + } + + if (node.parent == null) { // can be null if we're looking up root. + return null; + } + + if (isValidUplink(node.parent.left, node.parent)) { + if (node.parent.left == root) { + if (root.isEmpty()) { + return null; + } else { + return root; + } + + } else { + return node.parent.left; + } + } else { + return followRight(node.parent.left); + } + } + } + + /** + * Returns the entry lexicographically after the given entry. + * If the given entry is null, returns the first node. + *

+ * This will traverse only within the subtree. If the given node + * is not within the subtree, this will have undefined results. + */ + private TrieEntry nextEntryInSubtree(TrieEntry node, + TrieEntry parentOfSubtree) { + if (node == null) { + return firstEntry(); + } else { + return nextEntryImpl(node.predecessor, node, parentOfSubtree); + } + } + + private boolean isPrefix(K key, K prefix) { + return keyAnalyzer.isPrefix(key, prefix); + } + + /** + * A range view of the {@link PrefixTree} + */ + private abstract class RangeMap extends AbstractMap + implements SortedMap { + + /** + * The {@link #entrySet()} view + */ + private transient volatile Set> entrySet; + + /** + * Creates and returns an {@link #entrySet()} + * view of the {@link RangeMap} + */ + protected abstract Set> createEntrySet(); + + /** + * Returns the FROM Key + */ + protected abstract K getFromKey(); + + /** + * Whether or not the {@link #getFromKey()} is in the range + */ + protected abstract boolean isFromInclusive(); + + /** + * Returns the TO Key + */ + protected abstract K getToKey(); + + /** + * Whether or not the {@link #getToKey()} is in the range + */ + protected abstract boolean isToInclusive(); + + + @Override + public Comparator comparator() { + return PatriciaTrie.this.comparator(); + } + + @Override + public boolean containsKey(Object key) { + if (!inRange((K) key)) { + return false; + } + + return PatriciaTrie.this.containsKey(key); + } + + @Override + public V remove(Object key) { + if (!inRange((K) key)) { + return null; + } + return PatriciaTrie.this.remove(key); + } + + @Override + public V get(Object key) { + if (!inRange((K) key)) { + return null; + } + return PatriciaTrie.this.get(key); + } + + @Override + public V put(K key, V value) { + if (!inRange(key)) { + throw new IllegalArgumentException( + "Key is out of range: " + key); + } + return PatriciaTrie.this.put(key, value); + } + + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = createEntrySet(); + } + return entrySet; + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + if (!inRange2(fromKey)) { + throw new IllegalArgumentException("FromKey is out of range: " + fromKey); + } + if (!inRange2(toKey)) { + throw new IllegalArgumentException("ToKey is out of range: " + toKey); + } + return createRangeMap(fromKey, isFromInclusive(), toKey, isToInclusive()); + } + + @Override + public SortedMap headMap(K toKey) { + if (!inRange2(toKey)) { + throw new IllegalArgumentException("ToKey is out of range: " + toKey); + } + return createRangeMap(getFromKey(), isFromInclusive(), toKey, isToInclusive()); + } + + @Override + public SortedMap tailMap(K fromKey) { + if (!inRange2(fromKey)) { + throw new IllegalArgumentException("FromKey is out of range: " + fromKey); + } + return createRangeMap(fromKey, isFromInclusive(), getToKey(), isToInclusive()); + } + + /** + * Returns true if the provided key is greater than TO and + * less than FROM + */ + protected boolean inRange(K key) { + K fromKey = getFromKey(); + K toKey = getToKey(); + return (fromKey == null || inFromRange(key, false)) + && (toKey == null || inToRange(key, false)); + } + + /** + * This form allows the high endpoint (as well as all legit keys) + */ + protected boolean inRange2(K key) { + K fromKey = getFromKey(); + K toKey = getToKey(); + return (fromKey == null || inFromRange(key, false)) + && (toKey == null || inToRange(key, true)); + } + + /** + * Returns true if the provided key is in the FROM range + * of the {@link RangeMap} + */ + protected boolean inFromRange(K key, boolean forceInclusive) { + K fromKey = getFromKey(); + boolean fromInclusive = isFromInclusive(); + int ret = keyAnalyzer.compare(key, fromKey); + if (fromInclusive || forceInclusive) { + return ret >= 0; + } else { + return ret > 0; + } + } + + /** + * Returns true if the provided key is in the TO range + * of the {@link RangeMap} + */ + protected boolean inToRange(K key, boolean forceInclusive) { + K toKey = getToKey(); + boolean toInclusive = isToInclusive(); + int ret = keyAnalyzer.compare(key, toKey); + if (toInclusive || forceInclusive) { + return ret <= 0; + } else { + return ret < 0; + } + } + + /** + * Creates and returns a sub-range view of the current {@link RangeMap} + */ + protected abstract SortedMap createRangeMap(K fromKey, + boolean fromInclusive, K toKey, boolean toInclusive); + } + + /** + * A {@link RangeMap} that deals with {@link Entry}s + */ + private class RangeEntryMap extends RangeMap { + + /** + * The key to start from, null if the beginning. + */ + protected final K fromKey; + + /** + * The key to end at, null if till the end. + */ + protected final K toKey; + + /** + * Whether or not the 'from' is inclusive. + */ + protected final boolean fromInclusive; + + /** + * Whether or not the 'to' is inclusive. + */ + protected final boolean toInclusive; + + /** + * Creates a {@link RangeEntryMap} with the fromKey included and + * the toKey excluded from the range + */ + protected RangeEntryMap(K fromKey, K toKey) { + this(fromKey, true, toKey, false); + } + + /** + * Creates a {@link RangeEntryMap} + */ + protected RangeEntryMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + + if (fromKey == null && toKey == null) { + throw new IllegalArgumentException("must have a from or to!"); + } + + if (fromKey != null && toKey != null + && keyAnalyzer.compare(fromKey, toKey) > 0) { + throw new IllegalArgumentException("fromKey > toKey"); + } + + this.fromKey = fromKey; + this.fromInclusive = fromInclusive; + this.toKey = toKey; + this.toInclusive = toInclusive; + } + + + @Override + public K firstKey() { + Map.Entry e = null; + if (fromKey == null) { + e = firstEntry(); + } else { + if (fromInclusive) { + e = ceilingEntry(fromKey); + } else { + e = higherEntry(fromKey); + } + } + + K first = e != null ? e.getKey() : null; + if (e == null || toKey != null && !inToRange(first, false)) { + throw new NoSuchElementException(); + } + return first; + } + + + @Override + public K lastKey() { + Map.Entry e; + if (toKey == null) { + e = lastEntry(); + } else { + if (toInclusive) { + e = floorEntry(toKey); + } else { + e = lowerEntry(toKey); + } + } + + K last = e != null ? e.getKey() : null; + if (e == null || fromKey != null && !inFromRange(last, false)) { + throw new NoSuchElementException(); + } + return last; + } + + @Override + protected Set> createEntrySet() { + return new RangeEntrySet(this); + } + + @Override + public K getFromKey() { + return fromKey; + } + + @Override + public K getToKey() { + return toKey; + } + + @Override + public boolean isFromInclusive() { + return fromInclusive; + } + + @Override + public boolean isToInclusive() { + return toInclusive; + } + + @Override + protected SortedMap createRangeMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); + } + } + + /** + * A {@link Set} view of a {@link RangeMap} + */ + private class RangeEntrySet extends AbstractSet> { + + private final RangeMap delegate; + + private int size = -1; + + private int expectedModCount = -1; + + /** + * Creates a {@link RangeEntrySet} + */ + public RangeEntrySet(RangeMap delegate) { + if (delegate == null) { + throw new NullPointerException("delegate"); + } + + this.delegate = delegate; + } + + @Override + public Iterator> iterator() { + K fromKey = delegate.getFromKey(); + K toKey = delegate.getToKey(); + + TrieEntry first = null; + if (fromKey == null) { + first = firstEntry(); + } else { + first = ceilingEntry(fromKey); + } + + TrieEntry last = null; + if (toKey != null) { + last = ceilingEntry(toKey); + } + + return new EntryIterator(first, last); + } + + @Override + public int size() { + if (size == -1 || expectedModCount != PatriciaTrie.this.modCount) { + size = 0; + + for (Iterator it = iterator(); it.hasNext(); it.next()) { + ++size; + } + + expectedModCount = PatriciaTrie.this.modCount; + } + return size; + } + + @Override + public boolean isEmpty() { + return !iterator().hasNext(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + @SuppressWarnings("unchecked") + Map.Entry entry = (Map.Entry) o; + K key = entry.getKey(); + if (!delegate.inRange(key)) { + return false; + } + TrieEntry node = getEntry(key); + return node != null && (node.getValue() == null ? entry.getValue() == null : node.getValue().equals(entry.getValue())); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + @SuppressWarnings("unchecked") + Map.Entry entry = (Map.Entry) o; + K key = entry.getKey(); + if (!delegate.inRange(key)) { + return false; + } + TrieEntry node = getEntry(key); + if (node != null && (node.getValue() == null ? entry.getValue() == null : node.getValue().equals(entry.getValue()))) { + removeEntry(node); + return true; + } + return false; + } + + /** + * An {@link Iterator} for {@link RangeEntrySet}s. + */ + private final class EntryIterator extends TrieIterator> { + + private final K excludedKey; + + /** + * Creates a {@link EntryIterator} + */ + private EntryIterator(TrieEntry first, TrieEntry last) { + super(first); + this.excludedKey = (last != null ? last.getKey() : null); + } + + @Override + public boolean hasNext() { + return next != null && !(next.key == null ? excludedKey == null : next.key.equals(excludedKey)); + } + + @Override + public Map.Entry next() { + if (next == null || (next.key == null ? excludedKey == null : next.key.equals(excludedKey))) { + throw new NoSuchElementException(); + } + return nextEntry(); + } + } + } + + /** + * A submap used for prefix views over the {@link PrefixTree}. + */ + private class PrefixRangeMap extends RangeMap { + + private final K prefix; + + private K fromKey = null; + + private K toKey = null; + + private int expectedModCount = -1; + + private int size = -1; + + /** + * Creates a {@link PrefixRangeMap} + */ + private PrefixRangeMap(K prefix) { + this.prefix = prefix; + } + + /** + * This method does two things. It determinates the FROM + * and TO range of the {@link PrefixRangeMap} and the number + * of elements in the range. This method must be called every + * time the {@link PrefixTree} has changed. + */ + private int fixup() { + // The trie has changed since we last + // found our toKey / fromKey + if (size == -1 || PatriciaTrie.this.modCount != expectedModCount) { + Iterator> it = entrySet().iterator(); + size = 0; + + Map.Entry entry = null; + if (it.hasNext()) { + entry = it.next(); + size = 1; + } + + fromKey = entry == null ? null : entry.getKey(); + if (fromKey != null) { + TrieEntry prior = previousEntry((TrieEntry) entry); + fromKey = prior == null ? null : prior.getKey(); + } + + toKey = fromKey; + + while (it.hasNext()) { + ++size; + entry = it.next(); + } + + toKey = entry == null ? null : entry.getKey(); + + if (toKey != null) { + entry = nextEntry((TrieEntry) entry); + toKey = entry == null ? null : entry.getKey(); + } + + expectedModCount = PatriciaTrie.this.modCount; + } + + return size; + } + + @Override + public K firstKey() { + fixup(); + + Map.Entry e = null; + if (fromKey == null) { + e = firstEntry(); + } else { + e = higherEntry(fromKey); + } + + K first = e != null ? e.getKey() : null; + if (e == null || !isPrefix(first, prefix)) { + throw new NoSuchElementException(); + } + + return first; + } + + @Override + public K lastKey() { + fixup(); + + Map.Entry e = null; + if (toKey == null) { + e = lastEntry(); + } else { + e = lowerEntry(toKey); + } + + K last = e != null ? e.getKey() : null; + if (e == null || !isPrefix(last, prefix)) { + throw new NoSuchElementException(); + } + + return last; + } + + /** + * Returns true if this {@link PrefixRangeMap}'s key is a prefix + * of the provided key. + */ + @Override + protected boolean inRange(K key) { + return isPrefix(key, prefix); + } + + /** + * Same as {@link #inRange(Object)} + */ + @Override + protected boolean inRange2(K key) { + return inRange(key); + } + + /** + * Returns true if the provided Key is in the FROM range + * of the {@link PrefixRangeMap} + */ + @Override + protected boolean inFromRange(K key, boolean forceInclusive) { + return isPrefix(key, prefix); + } + + /** + * Returns true if the provided Key is in the TO range + * of the {@link PrefixRangeMap} + */ + @Override + protected boolean inToRange(K key, boolean forceInclusive) { + return isPrefix(key, prefix); + } + + @Override + protected Set> createEntrySet() { + return new PrefixRangeEntrySet(this); + } + + @Override + public K getFromKey() { + return fromKey; + } + + @Override + public K getToKey() { + return toKey; + } + + @Override + public boolean isFromInclusive() { + return false; + } + + @Override + public boolean isToInclusive() { + return false; + } + + @Override + protected SortedMap createRangeMap( + K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); + } + } + + /** + * A prefix {@link RangeEntrySet} view of the {@link PrefixTree} + */ + private final class PrefixRangeEntrySet extends RangeEntrySet { + + private final PrefixRangeMap delegate; + + private TrieEntry prefixStart; + + private int expectedModCount = -1; + + /** + * Creates a {@link PrefixRangeEntrySet} + */ + public PrefixRangeEntrySet(PrefixRangeMap delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.fixup(); + } + + @Override + public Iterator> iterator() { + if (PatriciaTrie.this.modCount != expectedModCount) { + prefixStart = subtree(delegate.prefix); + expectedModCount = PatriciaTrie.this.modCount; + } + + if (prefixStart == null) { + Set> empty = Collections.emptySet(); + return empty.iterator(); + } else if (lengthInBits(delegate.prefix) >= prefixStart.bitIndex) { + return new SingletonIterator(prefixStart); + } else { + return new EntryIterator(prefixStart, delegate.prefix); + } + } + + /** + * An {@link Iterator} that holds a single {@link TrieEntry}. + */ + private final class SingletonIterator implements Iterator> { + + private final TrieEntry entry; + + private int hit = 0; + + public SingletonIterator(TrieEntry entry) { + this.entry = entry; + } + + @Override + public boolean hasNext() { + return hit == 0; + } + + @Override + public Map.Entry next() { + if (hit != 0) { + throw new NoSuchElementException(); + } + + ++hit; + return entry; + } + + + @Override + public void remove() { + if (hit != 1) { + throw new IllegalStateException(); + } + + ++hit; + PatriciaTrie.this.removeEntry(entry); + } + } + + /** + * An {@link Iterator} for iterating over a prefix search. + */ + private final class EntryIterator extends TrieIterator> { + + // values to reset the subtree if we remove it. + final K prefix; + boolean lastOne; + + TrieEntry subtree; // the subtree to search within + + /** + * Starts iteration at the given entry & search only + * within the given subtree. + */ + EntryIterator(TrieEntry startScan, K prefix) { + subtree = startScan; + next = PatriciaTrie.this.followLeft(startScan); + this.prefix = prefix; + } + + @Override + public Map.Entry next() { + Map.Entry entry = nextEntry(); + if (lastOne) { + next = null; + } + return entry; + } + + @Override + protected TrieEntry findNext(TrieEntry prior) { + return PatriciaTrie.this.nextEntryInSubtree(prior, subtree); + } + + @Override + public void remove() { + // If the current entry we're removing is the subtree + // then we need to find a new subtree parent. + boolean needsFixing = false; + int bitIdx = subtree.bitIndex; + if (current == subtree) { + needsFixing = true; + } + + super.remove(); + + // If the subtree changed its bitIndex or we + // removed the old subtree, get a new one. + if (bitIdx != subtree.bitIndex || needsFixing) { + subtree = subtree(prefix); + } + + // If the subtree's bitIndex is less than the + // length of our prefix, it's the last item + // in the prefix tree. + if (lengthInBits(prefix) >= subtree.bitIndex) { + lastOne = true; + } + } + } + } +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/PrefixTree.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/PrefixTree.java new file mode 100644 index 0000000..49ce5fa --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/patricia/PrefixTree.java @@ -0,0 +1,121 @@ +package org.xbib.datastructures.trie.patricia; + +import java.util.Map; +import java.util.SortedMap; + +/** + * Defines the interface for a prefix tree, an ordered tree data structure. For + * more information, see Tries. + */ +public interface PrefixTree extends SortedMap { + + /** + * Returns the {@link Map.Entry} whose key is closest in a bitwise XOR + * metric to the given key. This is NOT lexicographic closeness. + * For example, given the keys: + * + *

    + *
  1. D = 1000100 + *
  2. H = 1001000 + *
  3. L = 1001100 + *
+ *

+ * If the {@link PrefixTree} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @return The {@link Map.Entry} whose key is closest in a bitwise XOR metric + * to the provided key. + */ + Map.Entry select(K key); + + /** + * Returns the key that is closest in a bitwise XOR metric to the + * provided key. This is NOT lexicographic closeness! + *

+ * For example, given the keys: + * + *

    + *
  1. D = 1000100 + *
  2. H = 1001000 + *
  3. L = 1001100 + *
+ *

+ * If the {@link PrefixTree} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @return The key that is closest in a bitwise XOR metric to the provided key. + */ + K selectKey(K key); + + /** + * Returns the value whose key is closest in a bitwise XOR metric to + * the provided key. This is NOT lexicographic closeness! + *

+ * For example, given the keys: + * + *

    + *
  1. D = 1000100 + *
  2. H = 1001000 + *
  3. L = 1001100 + *
+ *

+ * If the {@link PrefixTree} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @return The value whose key is closest in a bitwise XOR metric + * to the provided key. + */ + V selectValue(K key); + + /** + * Iterates through the {@link PrefixTree}, starting with the entry whose bitwise + * value is closest in an XOR metric to the given key. After the closest + * entry is found, the {@link PrefixTree} will call select on that entry and continue + * calling select for each entry (traversing in order of XOR closeness, + * NOT lexicographically) until the cursor returns {@link Cursor.Decision#EXIT}. + * + *

The cursor can return {@link Cursor.Decision#CONTINUE} to continue traversing. + * + *

{@link Cursor.Decision#REMOVE_AND_EXIT} is used to remove the current element + * and stop traversing. + * + *

Note: The {@link Cursor.Decision#REMOVE} operation is not supported. + * + * @return The entry the cursor returned {@link Cursor.Decision#EXIT} on, or null + * if it continued till the end. + */ + Map.Entry select(K key, Cursor cursor); + + /** + * Traverses the {@link PrefixTree} in lexicographical order. + * {@link Cursor#select(java.util.Map.Entry)} will be called on each entry. + * + *

The traversal will stop when the cursor returns {@link Cursor.Decision#EXIT}, + * {@link Cursor.Decision#CONTINUE} is used to continue traversing and + * {@link Cursor.Decision#REMOVE} is used to remove the element that was selected + * and continue traversing. + * + *

{@link Cursor.Decision#REMOVE_AND_EXIT} is used to remove the current element + * and stop traversing. + * + * @return The entry the cursor returned {@link Cursor.Decision#EXIT} on, or null + * if it continued till the end. + */ + Map.Entry traverse(Cursor cursor); + + /** + * Returns a view of this {@link PrefixTree} of all elements that are prefixed + * by the given key. + * + *

In a {@link PrefixTree} with fixed size keys, this is essentially a + * {@link #get(Object)} operation. + * + *

For example, if the {@link PrefixTree} contains 'Anna', 'Anael', + * 'Analu', 'Andreas', 'Andrea', 'Andres', and 'Anatole', then + * a lookup of 'And' would return 'Andreas', 'Andrea', and 'Andres'. + */ + SortedMap prefixMap(K prefix); +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/DuplicateKeyException.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/DuplicateKeyException.java new file mode 100644 index 0000000..d57f3a3 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/DuplicateKeyException.java @@ -0,0 +1,10 @@ +package org.xbib.datastructures.trie.radix; + +/** + * Exception thrown if a duplicate key is inserted in a {@link RadixTree} + */ +public class DuplicateKeyException extends RuntimeException { + public DuplicateKeyException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/Node.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/Node.java new file mode 100644 index 0000000..1875a62 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/Node.java @@ -0,0 +1,74 @@ +package org.xbib.datastructures.trie.radix; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a node of a Radix tree {@link RadixTreeImpl} + * + * @param + */ +class Node { + + private String key; + + private List> children; + + private boolean real; + + private T value; + + public Node() { + key = ""; + children = new ArrayList<>(); + real = false; + } + + public T getValue() { + return value; + } + + public void setValue(T data) { + this.value = data; + } + + public String getKey() { + return key; + } + + public void setKey(String value) { + this.key = value; + } + + public boolean isReal() { + return real; + } + + public void setReal(boolean datanode) { + this.real = datanode; + } + + public List> getChildren() { + return children; + } + + public void setChildren(List> childern) { + this.children = childern; + } + + public int getNumberOfMatchingCharacters(String key) { + int numberOfMatchingCharacters = 0; + while (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters < this.getKey().length()) { + if (key.charAt(numberOfMatchingCharacters) != this.getKey().charAt(numberOfMatchingCharacters)) { + break; + } + numberOfMatchingCharacters++; + } + return numberOfMatchingCharacters; + } + + @Override + public String toString() { + return key; + } +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/RadixTree.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/RadixTree.java new file mode 100644 index 0000000..29ad5a6 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/RadixTree.java @@ -0,0 +1,77 @@ +package org.xbib.datastructures.trie.radix; + +import java.util.List; + +/** + * This interface represent the operation of a radix tree. A radix tree, + * Patricia trie/tree, or crit bit tree is a specialized set data structure + * based on the trie that is used to store a set of strings. In contrast with a + * regular trie, the edges of a Patricia trie are labelled with sequences of + * characters rather than with single characters. These can be strings of + * characters, bit strings such as integers or IP addresses, or generally + * arbitrary sequences of objects in lexicographical order. Sometimes the names + * radix tree and crit bit tree are only applied to trees storing integers and + * Patricia trie is retained for more general inputs, but the structure works + * the same way in all cases. + * + * https://code.google.com/archive/p/radixtree/ + */ +public interface RadixTree { + /** + * Insert a new string key and its value to the tree. + * + * @param key The string key of the object + * @param value The value that need to be stored corresponding to the given + * key. + */ + void insert(String key, T value); + + /** + * Delete a key and its associated value from the tree. + * + * @param key The key of the node that need to be deleted + * @return true if deleted + */ + boolean delete(String key); + + /** + * Find a value based on its corresponding key. + * + * @param key The key for which to search the tree. + * @return The value corresponding to the key. null if iot can not find the key + */ + T search(String key); + + /** + * Find an existing entry and replace it's value. If no existing entry, do nothing + * + * @param key The key for which to search the tree. + * @param value The value to set for the entry + * @return true if an entry was found for the given key, false if not found + */ + boolean replace(String key, T value); + + /** + * Check if the tree contains any entry corresponding to the given key. + * + * @param key The key that needto be searched in the tree. + * @return retun true if the key is present in the tree otherwise false + */ + boolean contains(String key); + + /** + * Search for all the keys that start with given prefix. limiting the results based on the supplied limit. + * + * @param prefix The prefix for which keys need to be search + * @param recordLimit The limit for the results + * @return The list of values those key start with the given prefix + */ + List searchPrefix(String prefix, int recordLimit); + + /** + * Return the size of the Radix tree + * + * @return the size of the tree + */ + long getSize(); +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/RadixTreeImpl.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/RadixTreeImpl.java new file mode 100644 index 0000000..4c3c949 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/RadixTreeImpl.java @@ -0,0 +1,275 @@ +package org.xbib.datastructures.trie.radix; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * Implementation for Radix tree {@link RadixTree} + */ +public class RadixTreeImpl implements RadixTree { + + protected Node root; + + protected long size; + + public RadixTreeImpl() { + root = new Node<>(); + root.setKey(""); + size = 0; + } + + @Override + public T search(String key) { + Visitor visitor = new VisitorImpl<>() { + @Override + public void visit(String key, Node parent, Node node) { + if (node.isReal()) { + result = node.getValue(); + } + } + }; + visit(key, visitor); + return visitor.getResult(); + } + + @Override + public boolean replace(String key, final T value) { + Visitor visitor = new VisitorImpl<>() { + public void visit(String key, Node parent, Node node) { + if (node.isReal()) { + node.setValue(value); + result = value; + } else { + result = null; + } + } + }; + visit(key, visitor); + return visitor.getResult() != null; + } + + @Override + public boolean delete(String key) { + Visitor visitor = new VisitorImpl<>(Boolean.FALSE) { + public void visit(String key, Node parent, Node node) { + result = node.isReal(); + if (result) { + if (node.getChildren().size() == 0) { + Iterator> it = parent.getChildren().iterator(); + while (it.hasNext()) { + if (it.next().getKey().equals(node.getKey())) { + it.remove(); + break; + } + } + if (parent.getChildren().size() == 1 && !parent.isReal()) { + mergeNodes(parent, parent.getChildren().get(0)); + } + } else if (node.getChildren().size() == 1) { + mergeNodes(node, node.getChildren().get(0)); + } else { + node.setReal(false); + } + } + } + + /** + * Merge a child into its parent node. Operation only valid if it is + * only child of the parent node and parent node is not a real node. + * + * @param parent The parent Node + * @param child The child Node + */ + private void mergeNodes(Node parent, + Node child) { + parent.setKey(parent.getKey() + child.getKey()); + parent.setReal(child.isReal()); + parent.setValue(child.getValue()); + parent.setChildren(child.getChildren()); + } + }; + visit(key, visitor); + if (visitor.getResult()) { + size--; + } + return visitor.getResult(); + } + + @Override + public void insert(String key, T value) throws DuplicateKeyException { + insert(key, root, value); + size++; + } + + /** + * Recursively insert the key in the radix tree. + * + * @param key The key to be inserted + * @param node The current node + * @param value The value associated with the key + * @throws DuplicateKeyException If the key already exists in the database. + */ + private void insert(String key, Node node, T value) + throws DuplicateKeyException { + int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key); + if (node.getKey().equals("") || numberOfMatchingCharacters == 0 || + (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) { + boolean flag = false; + String newText = key.substring(numberOfMatchingCharacters); + for (Node child : node.getChildren()) { + if (child.getKey().startsWith(newText.charAt(0) + "")) { + flag = true; + insert(newText, child, value); + break; + } + } + if (!flag) { + Node n = new Node<>(); + n.setKey(newText); + n.setReal(true); + n.setValue(value); + node.getChildren().add(n); + } + } else if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters == node.getKey().length()) { + if (node.isReal()) { + throw new DuplicateKeyException("Duplicate key"); + } + node.setReal(true); + node.setValue(value); + } + else if (numberOfMatchingCharacters > 0 && numberOfMatchingCharacters < node.getKey().length()) { + Node n1 = new Node<>(); + n1.setKey(node.getKey().substring(numberOfMatchingCharacters)); + n1.setReal(node.isReal()); + n1.setValue(node.getValue()); + n1.setChildren(node.getChildren()); + node.setKey(key.substring(0, numberOfMatchingCharacters)); + node.setReal(false); + node.setChildren(new ArrayList<>()); + node.getChildren().add(n1); + if (numberOfMatchingCharacters < key.length()) { + Node n2 = new Node<>(); + n2.setKey(key.substring(numberOfMatchingCharacters)); + n2.setReal(true); + n2.setValue(value); + node.getChildren().add(n2); + } else { + node.setValue(value); + node.setReal(true); + } + } else { + Node n = new Node<>(); + n.setKey(node.getKey().substring(numberOfMatchingCharacters)); + n.setChildren(node.getChildren()); + n.setReal(node.isReal()); + n.setValue(node.getValue()); + node.setKey(key); + node.setReal(true); + node.setValue(value); + node.getChildren().add(n); + } + } + + @Override + public List searchPrefix(String key, int recordLimit) { + List keys = new ArrayList<>(); + Node node = searchPefix(key, root); + if (node != null) { + if (node.isReal()) { + keys.add(node.getValue()); + } + getNodes(node, keys, recordLimit); + } + return keys; + } + + private void getNodes(Node parent, List keys, int limit) { + Queue> queue = new LinkedList<>(parent.getChildren()); + while (!queue.isEmpty()) { + Node node = queue.remove(); + if (node.isReal()) { + keys.add(node.getValue()); + } + if (keys.size() == limit) { + break; + } + queue.addAll(node.getChildren()); + } + } + + private Node searchPefix(String key, Node node) { + Node result = null; + int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key); + if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters <= node.getKey().length()) { + result = node; + } else if (node.getKey().equals("") + || (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) { + String newText = key.substring(numberOfMatchingCharacters); + for (Node child : node.getChildren()) { + if (child.getKey().startsWith(newText.charAt(0) + "")) { + result = searchPefix(newText, child); + break; + } + } + } + return result; + } + + @Override + public boolean contains(String key) { + Visitor visitor = new VisitorImpl<>(Boolean.FALSE) { + @Override + public void visit(String key, Node parent, Node node) { + result = node.isReal(); + } + }; + visit(key, visitor); + return visitor.getResult(); + } + + /** + * visit the node those key matches the given key + * + * @param key The key that need to be visited + * @param visitor The visitor object + */ + public void visit(String key, Visitor visitor) { + if (root != null) { + visit(key, visitor, null, root); + } + } + + /** + * recursively visit the tree based on the supplied "key". calls the Visitor + * for the node those key matches the given prefix + * + * @param prefix The key o prefix to search in the tree + * @param visitor The Visitor that will be called if a node with "key" as its + * key is found + * @param node The Node from where onward to search + */ + private void visit(String prefix, Visitor visitor, + Node parent, Node node) { + int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(prefix); + if (numberOfMatchingCharacters == prefix.length() && numberOfMatchingCharacters == node.getKey().length()) { + visitor.visit(prefix, parent, node); + } else if (node.getKey().equals("") + || (numberOfMatchingCharacters < prefix.length() && numberOfMatchingCharacters >= node.getKey().length())) { // OR we need to + String newText = prefix.substring(numberOfMatchingCharacters); + for (Node child : node.getChildren()) { + if (child.getKey().startsWith(newText.charAt(0) + "")) { + visit(newText, visitor, node, child); + break; + } + } + } + } + + @Override + public long getSize() { + return size; + } +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/Visitor.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/Visitor.java new file mode 100644 index 0000000..84ae1a0 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/Visitor.java @@ -0,0 +1,25 @@ +package org.xbib.datastructures.trie.radix; + +/** + * The visitor interface that is used by {@link RadixTreeImpl} for perfroming + * task on a searched node. + */ +public interface Visitor { + /** + * This method gets called by {@link RadixTreeImpl#visit(String, Visitor) visit} + * when it finds a node matching the key given to it. + * + * @param key The key that matched the node + * @param parent The parent of the node being visited + * @param node The node that is being visited + */ + void visit(String key, Node parent, Node node); + + /** + * The visitor can store any type of result object, depending on the context of + * what it is being used for. + * + * @return The result captured by the visitor. + */ + R getResult(); +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/VisitorImpl.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/VisitorImpl.java new file mode 100644 index 0000000..13e07b5 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/VisitorImpl.java @@ -0,0 +1,23 @@ +package org.xbib.datastructures.trie.radix; + +/** + * A simple standard implementation for a {@link Visitor}. + */ +public abstract class VisitorImpl implements Visitor { + + protected R result; + + public VisitorImpl() { + this.result = null; + } + + public VisitorImpl(R initialValue) { + this.result = initialValue; + } + + public R getResult() { + return result; + } + + abstract public void visit(String key, Node parent, Node node); +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/Node.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/Node.java new file mode 100644 index 0000000..b82a911 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/Node.java @@ -0,0 +1,43 @@ +package org.xbib.datastructures.trie.radix.pruning; + +import java.util.List; + +public class Node +{ + private List children; + + //Does this node represent the last character in a word? + //0: no word; >0: is word (termFrequencyCount) + private long termFrequencyCount; + + private long termFrequencyCountChildMax; + + public Node(long termfrequencyCount) + { + this.termFrequencyCount = termfrequencyCount; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public long getTermFrequencyCount() { + return termFrequencyCount; + } + + public void setTermFrequencyCount(long termFrequencyCount) { + this.termFrequencyCount = termFrequencyCount; + } + + public long getTermFrequencyCountChildMax() { + return termFrequencyCountChildMax; + } + + public void setTermFrequencyCountChildMax(long termFrequencyCountChildMax) { + this.termFrequencyCountChildMax = termFrequencyCountChildMax; + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/NodeChild.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/NodeChild.java new file mode 100644 index 0000000..ec3654b --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/NodeChild.java @@ -0,0 +1,34 @@ +package org.xbib.datastructures.trie.radix.pruning; + +public class NodeChild { + private String key; + + private Node node; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Node getNode() { + return node; + } + + public void setNode(Node node) { + this.node = node; + } + + @Override + public String toString() { + return "NodeChild [key=" + key + ", node=" + node + "]"; + } + + public NodeChild(String key, Node node) { + super(); + this.key = key; + this.node = node; + } +} diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/PruningRadixTrie.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/PruningRadixTrie.java new file mode 100644 index 0000000..4e7569f --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/PruningRadixTrie.java @@ -0,0 +1,204 @@ +package org.xbib.datastructures.trie.radix.pruning; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class PruningRadixTrie { + + public long termCount = 0; + + private final Node trie; + + public PruningRadixTrie() { + this.trie = new Node(0); + } + + public void addTerm(String term, long termFrequencyCount) { + List nodeList = new ArrayList<>(); + addTerm(trie, term, termFrequencyCount, 0, 0, nodeList); + } + + public void updateMaxCounts(List nodeList, long termFrequencyCount) { + for (Node node : nodeList) { + if (termFrequencyCount > node.getTermFrequencyCountChildMax()) { + node.setTermFrequencyCountChildMax(termFrequencyCount); + } + } + } + + public void addTerm(Node curr, String term, long termFrequencyCount, int id, int level, List nodeList) + { + try { + nodeList.add(curr); + int common = 0; + List currChildren = curr.getChildren(); + if (currChildren != null) { + for (int j = 0; j < currChildren.size(); j++) { + String key = currChildren.get(j).getKey(); + Node node = currChildren.get(j).getNode(); + + for (int i = 0; i < Math.min(term.length(), key.length()); i++) { + if (term.charAt(i) == key.charAt(i)) common = i + 1; + else break; + } + + if (common > 0) { + //term already existed + //existing ab + //new ab + if ((common == term.length()) && (common == key.length())) { + if (node.getTermFrequencyCount() == 0) termCount++; + node.setTermFrequencyCount(node.getTermFrequencyCount() + termFrequencyCount); + updateMaxCounts(nodeList, node.getTermFrequencyCount()); + } + //new is subkey + //existing abcd + //new ab + //if new is shorter (== common), then node(count) and only 1. children add (clause2) + else if (common == term.length()) { + //insert second part of oldKey as child + Node child = new Node(termFrequencyCount); + List l = new ArrayList<>(); + l.add(new NodeChild(key.substring(common), node)); + child.setChildren(l); + + child.setTermFrequencyCountChildMax( + Math.max(node.getTermFrequencyCountChildMax(), node.getTermFrequencyCount())); + updateMaxCounts(nodeList, termFrequencyCount); + + //insert first part as key, overwrite old node + currChildren.set(j, new NodeChild(term.substring(0, common), child)); + //sort children descending by termFrequencyCountChildMax to start lookup with most promising branch + Collections.sort(currChildren, Comparator.comparing( + (NodeChild e) -> e.getNode().getTermFrequencyCountChildMax()).reversed()); + //increment termcount by 1 + termCount++; + } + //if oldkey shorter (==common), then recursive addTerm (clause1) + //existing: te + //new: test + else if (common == key.length()) { + addTerm(node, term.substring(common), termFrequencyCount, id, level + 1, nodeList); + } + //old and new have common substrings + //existing: test + //new: team + else { + //insert second part of oldKey and of s as child + Node child = new Node(0);//count + List l = new ArrayList<>(); + l.add(new NodeChild(key.substring(common), node)); + l.add(new NodeChild(term.substring(common), new Node(termFrequencyCount))); + child.setChildren(l); + + child.setTermFrequencyCountChildMax( + Math.max(node.getTermFrequencyCountChildMax(), Math.max(termFrequencyCount, node.getTermFrequencyCount()))); + updateMaxCounts(nodeList, termFrequencyCount); + + //insert first part as key, overwrite old node + currChildren.set(j, new NodeChild(term.substring(0, common), child)); + //sort children descending by termFrequencyCountChildMax to start lookup with most promising branch + Collections.sort(currChildren, Comparator.comparing( + (NodeChild e) -> e.getNode().getTermFrequencyCountChildMax()).reversed()); + //increment termcount by 1 + termCount++; + } + return; + } + } + } + + // initialize dictionary if first key is inserted + if (currChildren == null) { + List l = new ArrayList<>(); + l.add(new NodeChild(term, new Node(termFrequencyCount))); + curr.setChildren(l); + } + else { + currChildren.add(new NodeChild(term, new Node(termFrequencyCount))); + //sort children descending by termFrequencyCountChildMax to start lookup with most promising branch + currChildren.sort(Comparator.comparing( + (NodeChild e) -> e.getNode().getTermFrequencyCountChildMax()).reversed()); + } + termCount++; + updateMaxCounts(nodeList, termFrequencyCount); + } catch (Exception e) { System.out.println("exception: " + term + " " + e.getMessage()); } + } + + public void findAllChildTerms(String prefix, int topK, String prefixString, List results, Boolean pruning) { + findAllChildTerms(prefix, trie, topK, prefixString, results, pruning); + } + + public void findAllChildTerms(String prefix, Node curr, int topK, String prefixString, List results, Boolean pruning) + { + try { + //pruning/early termination in radix trie lookup + if (pruning && (topK > 0) && (results.size() == topK) && + (curr.getTermFrequencyCountChildMax() <= results.get(topK - 1).getTermFrequencyCount())) { + return; + } + + //test for common prefix (with possibly different suffix) + boolean noPrefix = prefix.equals(""); + + if (curr.getChildren() != null) { + for (NodeChild nodeChild : curr.getChildren()) { + String key = nodeChild.getKey(); + Node node = nodeChild.getNode(); + //pruning/early termination in radix trie lookup + if (pruning && (topK > 0) && (results.size() == topK) && + (node.getTermFrequencyCount() <= results.get(topK - 1).getTermFrequencyCount()) && + (node.getTermFrequencyCountChildMax() <= results.get(topK - 1).getTermFrequencyCount())) { + if (!noPrefix) break; + else continue; + } + + if (noPrefix || key.startsWith(prefix)) { + if (node.getTermFrequencyCount() > 0) { + if (topK > 0) addTopKSuggestion(prefixString + key, node.getTermFrequencyCount(), topK, results); + else results.add(new TermAndFrequency(prefixString + key, node.getTermFrequencyCount())); + } + + if ((node.getChildren() != null) && (node.getChildren().size() > 0)) { + findAllChildTerms("", node, topK, prefixString + key, results, pruning); + } + if (!noPrefix) break; + } else if (prefix.startsWith(key)) { + if ((node.getChildren() != null) && (node.getChildren().size() > 0)) { + findAllChildTerms(prefix.substring(key.length()), node, topK, prefixString + key, results, pruning); + } + break; + } + } + } + } catch (Exception e) { System.out.println("exception: " + prefix + " " + e.getMessage()); } + } + + public List getTopkTermsForPrefix(String prefix, int topK) { + return getTopkTermsForPrefix(prefix, topK, true); + } + + public List getTopkTermsForPrefix(String prefix, int topK, Boolean pruning) { + List results = new ArrayList<>(); + findAllChildTerms(prefix, topK, "", results, pruning); + return results; + } + + public void addTopKSuggestion(String term, long termFrequencyCount, int topK, List results) + { + //at the end/highest index is the lowest value + // > : old take precedence for equal rank + // >= : new take precedence for equal rank + if ((results.size() < topK) || (termFrequencyCount >= results.get(topK - 1).getTermFrequencyCount())) { + TermAndFrequency termAndFrequency = new TermAndFrequency(term, termFrequencyCount); + int index = Collections.binarySearch(results, termAndFrequency, Comparator.comparing( + TermAndFrequency::getTermFrequencyCount).reversed()); // descending order + if (index < 0) results.add(~index, termAndFrequency); + else results.add(index, termAndFrequency); + + if (results.size() > topK) results.remove(topK); + } + } +} \ No newline at end of file diff --git a/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/TermAndFrequency.java b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/TermAndFrequency.java new file mode 100644 index 0000000..fd2fb81 --- /dev/null +++ b/datastructures-trie/src/main/java/org/xbib/datastructures/trie/radix/pruning/TermAndFrequency.java @@ -0,0 +1,56 @@ +package org.xbib.datastructures.trie.radix.pruning; + +public class TermAndFrequency { + private String term; + private long termFrequencyCount; + + public String getTerm() { + return term; + } + public void setTerm(String term) { + this.term = term; + } + public long getTermFrequencyCount() { + return termFrequencyCount; + } + public void setTermFrequencyCount(long termFrequencyCount) { + this.termFrequencyCount = termFrequencyCount; + } + @Override + public String toString() { + return "TermAndFrequency [term=" + term + ", termFrequencyCount=" + termFrequencyCount + "]"; + } + public TermAndFrequency(String term, long termFrequencyCount) { + super(); + this.term = term; + this.termFrequencyCount = termFrequencyCount; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((term == null) ? 0 : term.hashCode()); + result = prime * result + (int) (termFrequencyCount ^ (termFrequencyCount >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TermAndFrequency other = (TermAndFrequency) obj; + if (term == null) { + if (other.term != null) + return false; + } else if (!term.equals(other.term)) + return false; + if (termFrequencyCount != other.termFrequencyCount) + return false; + return true; + } +} diff --git a/datastructures-trie/src/test/java/org/xbib/datastructures/trie/TrieTest.java b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/TrieTest.java new file mode 100644 index 0000000..71877ef --- /dev/null +++ b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/TrieTest.java @@ -0,0 +1,57 @@ +package org.xbib.datastructures.trie; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class TrieTest { + + @Test + public void testEmptyTrie() { + Trie trie = new TrieImpl<>(); + Integer result = trie.search("Value"); + assertNull(result); + } + + @Test + public void testEmptyKey() { + Trie trie = new TrieImpl<>(); + trie.add("", 100); + Integer result = trie.search(""); + assertEquals(result, (Integer) 100); + trie.add("", 200); + result = trie.search(""); + assertEquals(result, (Integer) 200); + } + + @Test + public void testSingletonTrie() { + Trie trie = new TrieImpl<>(); + trie.add("key", "value"); + String result = trie.search("key"); + assertNotEquals(result, "key"); + } + + @Test + public void testLargeInsertionAndSearch() { + Trie trie = new TrieImpl<>(); + List keys = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + Random random = new Random(); + Long value = random.nextLong(); + String key = value.toString(); + trie.add(key, value); + keys.add(key); + } + for (String key : keys) { + Long value = trie.search(key); + assertEquals(key, value.toString()); + } + } +} diff --git a/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/CharacterKeyAnalyzer.java b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/CharacterKeyAnalyzer.java new file mode 100644 index 0000000..f18c97e --- /dev/null +++ b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/CharacterKeyAnalyzer.java @@ -0,0 +1,83 @@ +package org.xbib.datastructures.trie.patricia; + +/** + * A {@link KeyAnalyzer} for {@link Character}s. + */ +public class CharacterKeyAnalyzer extends AbstractKeyAnalyzer { + + /** + * A {@link CharacterKeyAnalyzer} that uses all bits (16) of a {@code char}. + */ + public static final CharacterKeyAnalyzer CHAR = new CharacterKeyAnalyzer(Character.SIZE); + + /** + * A {@link CharacterKeyAnalyzer} that uses only the lower 8bits of a {@code char}. + */ + public static final CharacterKeyAnalyzer BYTE = new CharacterKeyAnalyzer(Byte.SIZE); + + public static final CharacterKeyAnalyzer INSTANCE = CHAR; + + private final int size; + + private final int msb; + + private CharacterKeyAnalyzer(int size) { + this(size, 1 << size - 1); + } + + private CharacterKeyAnalyzer(int size, int msb) { + this.size = size; + this.msb = msb; + } + + /** + * Returns a bit mask where the given bit is set + */ + private int mask(int bit) { + return msb >>> bit; + } + + private char valueOf(Character ch) { + char value = ch; + if (size == Byte.SIZE) { + value &= 0xFF; + } + return value; + } + + @Override + public int lengthInBits(Character key) { + return size; + } + + @Override + public boolean isBitSet(Character key, int bitIndex) { + return (key & mask(bitIndex)) != 0; + } + + @Override + public int bitIndex(Character key, Character otherKey) { + char ch1 = valueOf(key); + char ch2 = valueOf(otherKey); + + if (ch1 == 0) { + return NULL_BIT_KEY; + } + + if (ch1 != ch2) { + int xor = ch1 ^ ch2; + for (int i = 0; i < size; i++) { + if ((xor & mask(i)) != 0) { + return i; + } + } + } + + return KeyAnalyzer.EQUAL_BIT_KEY; + } + + @Override + public boolean isPrefix(Character key, Character prefix) { + return key.equals(prefix); + } +} diff --git a/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/IntegerKeyAnalyzer.java b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/IntegerKeyAnalyzer.java new file mode 100644 index 0000000..e5315b9 --- /dev/null +++ b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/IntegerKeyAnalyzer.java @@ -0,0 +1,57 @@ +package org.xbib.datastructures.trie.patricia; + +/** + * A {@link KeyAnalyzer} for {@link Integer}s. + */ +public class IntegerKeyAnalyzer extends AbstractKeyAnalyzer { + + public static final IntegerKeyAnalyzer INSTANCE = new IntegerKeyAnalyzer(); + + /** + * A bit mask where the first bit is 1 and the others are zero + */ + private static final int MSB = 1 << Integer.SIZE - 1; + + /** + * Returns a bit mask where the given bit is set + */ + private static int mask(int bit) { + return MSB >>> bit; + } + + @Override + public int lengthInBits(Integer key) { + return Integer.SIZE; + } + + @Override + public boolean isBitSet(Integer key, int bitIndex) { + return (key & mask(bitIndex)) != 0; + } + + @Override + public int bitIndex(Integer key, Integer otherKey) { + int keyValue = key.intValue(); + if (keyValue == 0) { + return NULL_BIT_KEY; + } + + int otherValue = otherKey.intValue(); + + if (keyValue != otherValue) { + int xorValue = keyValue ^ otherValue; + for (int i = 0; i < Integer.SIZE; i++) { + if ((xorValue & mask(i)) != 0) { + return i; + } + } + } + + return KeyAnalyzer.EQUAL_BIT_KEY; + } + + @Override + public boolean isPrefix(Integer key, Integer prefix) { + return key.equals(prefix); + } +} diff --git a/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/PatriciaTrieTest.java b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/PatriciaTrieTest.java new file mode 100644 index 0000000..0169602 --- /dev/null +++ b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/PatriciaTrieTest.java @@ -0,0 +1,1043 @@ +package org.xbib.datastructures.trie.patricia; + +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.SortedMap; +import java.util.StringTokenizer; +import java.util.TreeMap; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class PatriciaTrieTest { + + private static void assertEqualArrays(Object[] a, Object[] b) { + assertArrayEquals(a, b); + } + + @Test + public void testSimple() { + PatriciaTrie intTrie = new PatriciaTrie<>(IntegerKeyAnalyzer.INSTANCE); + assertTrue(intTrie.isEmpty()); + assertEquals(0, intTrie.size()); + intTrie.put(1, "One"); + assertEquals(1, intTrie.size()); + assertEquals("One", intTrie.remove(1)); + assertNull(intTrie.remove(1)); + assertTrue(intTrie.isEmpty()); + assertEquals(0, intTrie.size()); + intTrie.put(1, "One"); + assertEquals("One", intTrie.get(1)); + assertEquals("One", intTrie.put(1, "NotOne")); + assertEquals(1, intTrie.size()); + assertEquals("NotOne", intTrie.get(1)); + assertEquals("NotOne", intTrie.remove(1)); + assertNull(intTrie.put(1, "One")); + } + + @Test + public void testCeilingEntry() { + PatriciaTrie charTrie = new PatriciaTrie<>(CharacterKeyAnalyzer.CHAR); + charTrie.put('c', "c"); + charTrie.put('p', "p"); + charTrie.put('l', "l"); + charTrie.put('t', "t"); + charTrie.put('k', "k"); + charTrie.put('a', "a"); + charTrie.put('y', "y"); + charTrie.put('r', "r"); + charTrie.put('u', "u"); + charTrie.put('o', "o"); + charTrie.put('w', "w"); + charTrie.put('i', "i"); + charTrie.put('e', "e"); + charTrie.put('x', "x"); + charTrie.put('q', "q"); + charTrie.put('b', "b"); + charTrie.put('j', "j"); + charTrie.put('s', "s"); + charTrie.put('n', "n"); + charTrie.put('v', "v"); + charTrie.put('g', "g"); + charTrie.put('h', "h"); + charTrie.put('m', "m"); + charTrie.put('z', "z"); + charTrie.put('f', "f"); + charTrie.put('d', "d"); + + Object[] results = new Object[]{ + 'a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", + 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", + 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", + 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", + 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", + 'z', "z" + }; + + for (int i = 0; i < results.length; i++) { + Map.Entry found = charTrie.ceilingEntry((Character) results[i]); + assertNotNull(found); + assertEquals(results[i], found.getKey()); + assertEquals(results[++i], found.getValue()); + } + + // Remove some & try again... + charTrie.remove('a'); + charTrie.remove('z'); + charTrie.remove('q'); + charTrie.remove('l'); + charTrie.remove('p'); + charTrie.remove('m'); + charTrie.remove('u'); + + Map.Entry found = charTrie.ceilingEntry('u'); + assertNotNull(found); + assertEquals((Character) 'v', found.getKey()); + + found = charTrie.ceilingEntry('a'); + assertNotNull(found); + assertEquals((Character) 'b', found.getKey()); + + found = charTrie.ceilingEntry('z'); + assertNull(found); + + found = charTrie.ceilingEntry('q'); + assertNotNull(found); + assertEquals((Character) 'r', found.getKey()); + + found = charTrie.ceilingEntry('l'); + assertNotNull(found); + assertEquals((Character) 'n', found.getKey()); + + found = charTrie.ceilingEntry('p'); + assertNotNull(found); + assertEquals((Character) 'r', found.getKey()); + + found = charTrie.ceilingEntry('m'); + assertNotNull(found); + assertEquals((Character) 'n', found.getKey()); + + found = charTrie.ceilingEntry('\0'); + assertNotNull(found); + assertEquals((Character) 'b', found.getKey()); + + charTrie.put('\0', ""); + found = charTrie.ceilingEntry('\0'); + assertNotNull(found); + assertEquals((Character) '\0', found.getKey()); + } + + @Test + public void testLowerEntry() { + PatriciaTrie charTrie = new PatriciaTrie(CharacterKeyAnalyzer.CHAR); + charTrie.put('c', "c"); + charTrie.put('p', "p"); + charTrie.put('l', "l"); + charTrie.put('t', "t"); + charTrie.put('k', "k"); + charTrie.put('a', "a"); + charTrie.put('y', "y"); + charTrie.put('r', "r"); + charTrie.put('u', "u"); + charTrie.put('o', "o"); + charTrie.put('w', "w"); + charTrie.put('i', "i"); + charTrie.put('e', "e"); + charTrie.put('x', "x"); + charTrie.put('q', "q"); + charTrie.put('b', "b"); + charTrie.put('j', "j"); + charTrie.put('s', "s"); + charTrie.put('n', "n"); + charTrie.put('v', "v"); + charTrie.put('g', "g"); + charTrie.put('h', "h"); + charTrie.put('m', "m"); + charTrie.put('z', "z"); + charTrie.put('f', "f"); + charTrie.put('d', "d"); + + Object[] results = new Object[]{ + 'a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", + 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", + 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", + 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", + 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", + 'z', "z" + }; + + for (int i = 0; i < results.length; i += 2) { + //System.out.println("Looking for: " + results[i]); + Map.Entry found = charTrie.lowerEntry((Character) results[i]); + if (i == 0) { + assertNull(found); + } else { + assertNotNull(found); + assertEquals(results[i - 2], found.getKey()); + assertEquals(results[i - 1], found.getValue()); + } + } + + Map.Entry found = charTrie.lowerEntry((char) ('z' + 1)); + assertNotNull(found); + assertEquals((Character) 'z', found.getKey()); + + + // Remove some & try again... + charTrie.remove('a'); + charTrie.remove('z'); + charTrie.remove('q'); + charTrie.remove('l'); + charTrie.remove('p'); + charTrie.remove('m'); + charTrie.remove('u'); + + found = charTrie.lowerEntry('u'); + assertNotNull(found); + assertEquals((Character) 't', found.getKey()); + + found = charTrie.lowerEntry('v'); + assertNotNull(found); + assertEquals((Character) 't', found.getKey()); + + found = charTrie.lowerEntry('a'); + assertNull(found); + + found = charTrie.lowerEntry('z'); + assertNotNull(found); + assertEquals((Character) 'y', found.getKey()); + + found = charTrie.lowerEntry((char) ('z' + 1)); + assertNotNull(found); + assertEquals((Character) 'y', found.getKey()); + + found = charTrie.lowerEntry('q'); + assertNotNull(found); + assertEquals((Character) 'o', found.getKey()); + + found = charTrie.lowerEntry('r'); + assertNotNull(found); + assertEquals((Character) 'o', found.getKey()); + + found = charTrie.lowerEntry('p'); + assertNotNull(found); + assertEquals((Character) 'o', found.getKey()); + + found = charTrie.lowerEntry('l'); + assertNotNull(found); + assertEquals((Character) 'k', found.getKey()); + + found = charTrie.lowerEntry('m'); + assertNotNull(found); + assertEquals((Character) 'k', found.getKey()); + + found = charTrie.lowerEntry('\0'); + assertNull(found); + + charTrie.put('\0', ""); + found = charTrie.lowerEntry('\0'); + assertNull(found); + } + + @Test + public void testIteration() { + PatriciaTrie intTrie = new PatriciaTrie(IntegerKeyAnalyzer.INSTANCE); + intTrie.put(1, "One"); + intTrie.put(5, "Five"); + intTrie.put(4, "Four"); + intTrie.put(2, "Two"); + intTrie.put(3, "Three"); + intTrie.put(15, "Fifteen"); + intTrie.put(13, "Thirteen"); + intTrie.put(14, "Fourteen"); + intTrie.put(16, "Sixteen"); + + TestCursor cursor = new TestCursor( + 1, "One", 2, "Two", 3, "Three", 4, "Four", 5, "Five", 13, "Thirteen", + 14, "Fourteen", 15, "Fifteen", 16, "Sixteen"); + + cursor.starting(); + intTrie.traverse(cursor); + cursor.finished(); + + cursor.starting(); + for (Map.Entry entry : intTrie.entrySet()) + cursor.select(entry); + cursor.finished(); + + cursor.starting(); + for (Integer integer : intTrie.keySet()) + cursor.checkKey(integer); + cursor.finished(); + + cursor.starting(); + for (String string : intTrie.values()) + cursor.checkValue(string); + cursor.finished(); + + PatriciaTrie charTrie = new PatriciaTrie(CharacterKeyAnalyzer.CHAR); + charTrie.put('c', "c"); + charTrie.put('p', "p"); + charTrie.put('l', "l"); + charTrie.put('t', "t"); + charTrie.put('k', "k"); + charTrie.put('a', "a"); + charTrie.put('y', "y"); + charTrie.put('r', "r"); + charTrie.put('u', "u"); + charTrie.put('o', "o"); + charTrie.put('w', "w"); + charTrie.put('i', "i"); + charTrie.put('e', "e"); + charTrie.put('x', "x"); + charTrie.put('q', "q"); + charTrie.put('b', "b"); + charTrie.put('j', "j"); + charTrie.put('s', "s"); + charTrie.put('n', "n"); + charTrie.put('v', "v"); + charTrie.put('g', "g"); + charTrie.put('h', "h"); + charTrie.put('m', "m"); + charTrie.put('z', "z"); + charTrie.put('f', "f"); + charTrie.put('d', "d"); + cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", + 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", + 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", + 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", + 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", + 'z', "z"); + + cursor.starting(); + charTrie.traverse(cursor); + cursor.finished(); + + cursor.starting(); + for (Map.Entry entry : charTrie.entrySet()) + cursor.select(entry); + cursor.finished(); + + cursor.starting(); + for (Character character : charTrie.keySet()) + cursor.checkKey(character); + cursor.finished(); + + cursor.starting(); + for (String string : charTrie.values()) + cursor.checkValue(string); + cursor.finished(); + } + + @Test + public void testSelect() { + PatriciaTrie charTrie = new PatriciaTrie(CharacterKeyAnalyzer.CHAR); + charTrie.put('c', "c"); + charTrie.put('p', "p"); + charTrie.put('l', "l"); + charTrie.put('t', "t"); + charTrie.put('k', "k"); + charTrie.put('a', "a"); + charTrie.put('y', "y"); + charTrie.put('r', "r"); + charTrie.put('u', "u"); + charTrie.put('o', "o"); + charTrie.put('w', "w"); + charTrie.put('i', "i"); + charTrie.put('e', "e"); + charTrie.put('x', "x"); + charTrie.put('q', "q"); + charTrie.put('b', "b"); + charTrie.put('j', "j"); + charTrie.put('s', "s"); + charTrie.put('n', "n"); + charTrie.put('v', "v"); + charTrie.put('g', "g"); + charTrie.put('h', "h"); + charTrie.put('m', "m"); + charTrie.put('z', "z"); + charTrie.put('f', "f"); + charTrie.put('d', "d"); + TestCursor cursor = new TestCursor( + 'd', "d", 'e', "e", 'f', "f", 'g', "g", + 'a', "a", 'b', "b", 'c', "c", + 'l', "l", 'm', "m", 'n', "n", 'o', "o", + 'h', "h", 'i', "i", 'j', "j", 'k', "k", + 't', "t", 'u', "u", 'v', "v", 'w', "w", + 'p', "p", 'q', "q", 'r', "r", 's', "s", + 'x', "x", 'y', "y", 'z', "z"); + + assertEquals(26, charTrie.size()); + + cursor.starting(); + charTrie.select('d', cursor); + cursor.finished(); + } + + @Test + public void testTraverseCursorRemove() { + PatriciaTrie charTrie = new PatriciaTrie(CharacterKeyAnalyzer.CHAR); + charTrie.put('c', "c"); + charTrie.put('p', "p"); + charTrie.put('l', "l"); + charTrie.put('t', "t"); + charTrie.put('k', "k"); + charTrie.put('a', "a"); + charTrie.put('y', "y"); + charTrie.put('r', "r"); + charTrie.put('u', "u"); + charTrie.put('o', "o"); + charTrie.put('w', "w"); + charTrie.put('i', "i"); + charTrie.put('e', "e"); + charTrie.put('x', "x"); + charTrie.put('q', "q"); + charTrie.put('b', "b"); + charTrie.put('j', "j"); + charTrie.put('s', "s"); + charTrie.put('n', "n"); + charTrie.put('v', "v"); + charTrie.put('g', "g"); + charTrie.put('h', "h"); + charTrie.put('m', "m"); + charTrie.put('z', "z"); + charTrie.put('f', "f"); + charTrie.put('d', "d"); + TestCursor cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", + 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", + 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", + 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", + 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", + 'z', "z"); + + cursor.starting(); + charTrie.traverse(cursor); + cursor.finished(); + + // Test removing both an internal & external node. + // 'm' is an example External node in this Trie, and 'p' is an internal. + + assertEquals(26, charTrie.size()); + + Object[] toRemove = new Object[]{'g', 'd', 'e', 'm', 'p', 'q', 'r', 's'}; + cursor.addToRemove(toRemove); + + cursor.starting(); + charTrie.traverse(cursor); + cursor.finished(); + + assertEquals(26 - toRemove.length, charTrie.size()); + + cursor.starting(); + charTrie.traverse(cursor); + cursor.finished(); + + cursor.starting(); + for (Entry entry : charTrie.entrySet()) { + cursor.select(entry); + if (Arrays.asList(toRemove).contains(entry.getKey())) { + fail("got an: " + entry); + } + } + cursor.finished(); + } + + @Test + public void testIteratorRemove() { + PatriciaTrie charTrie = new PatriciaTrie(CharacterKeyAnalyzer.CHAR); + charTrie.put('c', "c"); + charTrie.put('p', "p"); + charTrie.put('l', "l"); + charTrie.put('t', "t"); + charTrie.put('k', "k"); + charTrie.put('a', "a"); + charTrie.put('y', "y"); + charTrie.put('r', "r"); + charTrie.put('u', "u"); + charTrie.put('o', "o"); + charTrie.put('w', "w"); + charTrie.put('i', "i"); + charTrie.put('e', "e"); + charTrie.put('x', "x"); + charTrie.put('q', "q"); + charTrie.put('b', "b"); + charTrie.put('j', "j"); + charTrie.put('s', "s"); + charTrie.put('n', "n"); + charTrie.put('v', "v"); + charTrie.put('g', "g"); + charTrie.put('h', "h"); + charTrie.put('m', "m"); + charTrie.put('z', "z"); + charTrie.put('f', "f"); + charTrie.put('d', "d"); + TestCursor cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", + 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", + 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", + 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", + 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", + 'z', "z"); + + // Test removing both an internal & external node. + // 'm' is an example External node in this Trie, and 'p' is an internal. + + assertEquals(26, charTrie.size()); + + Object[] toRemove = new Object[]{'e', 'm', 'p', 'q', 'r', 's'}; + + cursor.starting(); + for (Iterator> i = charTrie.entrySet().iterator(); i.hasNext(); ) { + Map.Entry entry = i.next(); + cursor.select(entry); + if (Arrays.asList(toRemove).contains(entry.getKey())) + i.remove(); + } + cursor.finished(); + + assertEquals(26 - toRemove.length, charTrie.size()); + + cursor.remove(toRemove); + + cursor.starting(); + for (Entry entry : charTrie.entrySet()) { + cursor.select(entry); + if (Arrays.asList(toRemove).contains(entry.getKey())) { + fail("got an: " + entry); + } + } + cursor.finished(); + } + + @Test + public void testHamlet() throws Exception { + // Make sure that Hamlet is read & stored in the same order as a SortedSet. + List original = new ArrayList(); + List control = new ArrayList(); + SortedMap sortedControl = new TreeMap(); + PatriciaTrie trie = new PatriciaTrie(StringKeyAnalyzer.CHAR); + + InputStream in = getClass().getResourceAsStream("hamlet.txt"); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + String read = null; + while ((read = reader.readLine()) != null) { + StringTokenizer st = new StringTokenizer(read); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + original.add(token); + sortedControl.put(token, token); + trie.put(token, token); + } + } + control.addAll(sortedControl.values()); + + assertEquals(control.size(), sortedControl.size()); + assertEquals(sortedControl.size(), trie.size()); + Iterator iter = trie.values().iterator(); + for (String aControl : control) { + assertEquals(aControl, iter.next()); + } + + Random rnd = new Random(); + int item = 0; + iter = trie.values().iterator(); + int removed = 0; + for (; item < control.size(); item++) { + assertEquals(control.get(item), iter.next()); + if (rnd.nextBoolean()) { + iter.remove(); + removed++; + } + } + + assertEquals(control.size(), item); + assertTrue(removed > 0); + assertEquals(control.size(), trie.size() + removed); + + // reset hamlet + trie.clear(); + for (String anOriginal : original) { + trie.put(anOriginal, anOriginal); + } + + assertEqualArrays(sortedControl.values().toArray(), trie.values().toArray()); + assertEqualArrays(sortedControl.keySet().toArray(), trie.keySet().toArray()); + assertEqualArrays(sortedControl.entrySet().toArray(), trie.entrySet().toArray()); + + assertEquals(sortedControl.firstKey(), trie.firstKey()); + assertEquals(sortedControl.lastKey(), trie.lastKey()); + + SortedMap sub = trie.headMap(control.get(523)); + assertEquals(523, sub.size()); + for (int i = 0; i < control.size(); i++) { + if (i < 523) + assertTrue(sub.containsKey(control.get(i))); + else + assertFalse(sub.containsKey(control.get(i))); + } + // Too slow to check values on all, so just do a few. + assertTrue(sub.containsValue(control.get(522))); + assertFalse(sub.containsValue(control.get(523))); + assertFalse(sub.containsValue(control.get(524))); + + try { + sub.headMap(control.get(524)); + fail("should have thrown IAE"); + } catch (IllegalArgumentException expected) { + } + + assertEquals(sub.lastKey(), control.get(522)); + assertEquals(sub.firstKey(), control.get(0)); + + sub = sub.tailMap(control.get(234)); + assertEquals(289, sub.size()); + assertEquals(control.get(234), sub.firstKey()); + assertEquals(control.get(522), sub.lastKey()); + for (int i = 0; i < control.size(); i++) { + if (i < 523 && i > 233) + assertTrue(sub.containsKey(control.get(i))); + else + assertFalse(sub.containsKey(control.get(i))); + } + + try { + sub.tailMap(control.get(232)); + fail("should have thrown IAE"); + } catch (IllegalArgumentException expected) { + } + + sub = sub.subMap(control.get(300), control.get(400)); + assertEquals(100, sub.size()); + assertEquals(control.get(300), sub.firstKey()); + assertEquals(control.get(399), sub.lastKey()); + + for (int i = 0; i < control.size(); i++) { + if (i < 400 && i > 299) + assertTrue(sub.containsKey(control.get(i))); + else + assertFalse(sub.containsKey(control.get(i))); + } + } + + @Test + public void testPrefixedBy() { + PatriciaTrie trie + = new PatriciaTrie(StringKeyAnalyzer.CHAR); + + final String[] keys = new String[]{ + "", + "Albert", "Xavier", "XyZ", "Anna", "Alien", "Alberto", + "Alberts", "Allie", "Alliese", "Alabama", "Banane", + "Blabla", "Amber", "Ammun", "Akka", "Akko", "Albertoo", + "Amma" + }; + + for (String key : keys) { + trie.put(key, key); + } + + SortedMap map; + Iterator iterator; + Iterator> entryIterator; + Map.Entry entry; + + map = trie.prefixMap("Al"); + assertEquals(8, map.size()); + assertEquals("Alabama", map.firstKey()); + assertEquals("Alliese", map.lastKey()); + assertEquals("Albertoo", map.get("Albertoo")); + assertNotNull(trie.get("Xavier")); + assertNull(map.get("Xavier")); + assertNull(trie.get("Alice")); + assertNull(map.get("Alice")); + iterator = map.values().iterator(); + assertEquals("Alabama", iterator.next()); + assertEquals("Albert", iterator.next()); + assertEquals("Alberto", iterator.next()); + assertEquals("Albertoo", iterator.next()); + assertEquals("Alberts", iterator.next()); + assertEquals("Alien", iterator.next()); + assertEquals("Allie", iterator.next()); + assertEquals("Alliese", iterator.next()); + assertFalse(iterator.hasNext()); + + map = trie.prefixMap("Albert"); + iterator = map.keySet().iterator(); + assertEquals("Albert", iterator.next()); + assertEquals("Alberto", iterator.next()); + assertEquals("Albertoo", iterator.next()); + assertEquals("Alberts", iterator.next()); + assertFalse(iterator.hasNext()); + assertEquals(4, map.size()); + assertEquals("Albert", map.firstKey()); + assertEquals("Alberts", map.lastKey()); + assertNull(trie.get("Albertz")); + map.put("Albertz", "Albertz"); + assertEquals("Albertz", trie.get("Albertz")); + assertEquals(5, map.size()); + assertEquals("Albertz", map.lastKey()); + iterator = map.keySet().iterator(); + assertEquals("Albert", iterator.next()); + assertEquals("Alberto", iterator.next()); + assertEquals("Albertoo", iterator.next()); + assertEquals("Alberts", iterator.next()); + assertEquals("Albertz", iterator.next()); + assertFalse(iterator.hasNext()); + assertEquals("Albertz", map.remove("Albertz")); + + map = trie.prefixMap("Alberto"); + assertEquals(2, map.size()); + assertEquals("Alberto", map.firstKey()); + assertEquals("Albertoo", map.lastKey()); + entryIterator = map.entrySet().iterator(); + entry = entryIterator.next(); + assertEquals("Alberto", entry.getKey()); + assertEquals("Alberto", entry.getValue()); + entry = entryIterator.next(); + assertEquals("Albertoo", entry.getKey()); + assertEquals("Albertoo", entry.getValue()); + assertFalse(entryIterator.hasNext()); + trie.put("Albertoad", "Albertoad"); + assertEquals(3, map.size()); + assertEquals("Alberto", map.firstKey()); + assertEquals("Albertoo", map.lastKey()); + entryIterator = map.entrySet().iterator(); + entry = entryIterator.next(); + assertEquals("Alberto", entry.getKey()); + assertEquals("Alberto", entry.getValue()); + entry = entryIterator.next(); + assertEquals("Albertoad", entry.getKey()); + assertEquals("Albertoad", entry.getValue()); + entry = entryIterator.next(); + assertEquals("Albertoo", entry.getKey()); + assertEquals("Albertoo", entry.getValue()); + assertFalse(entryIterator.hasNext()); + assertEquals("Albertoo", trie.remove("Albertoo")); + assertEquals("Alberto", map.firstKey()); + assertEquals("Albertoad", map.lastKey()); + assertEquals(2, map.size()); + entryIterator = map.entrySet().iterator(); + entry = entryIterator.next(); + assertEquals("Alberto", entry.getKey()); + assertEquals("Alberto", entry.getValue()); + entry = entryIterator.next(); + assertEquals("Albertoad", entry.getKey()); + assertEquals("Albertoad", entry.getValue()); + assertFalse(entryIterator.hasNext()); + assertEquals("Albertoad", trie.remove("Albertoad")); + trie.put("Albertoo", "Albertoo"); + + map = trie.prefixMap("X"); + assertEquals(2, map.size()); + assertFalse(map.containsKey("Albert")); + assertTrue(map.containsKey("Xavier")); + assertFalse(map.containsKey("Xalan")); + iterator = map.values().iterator(); + assertEquals("Xavier", iterator.next()); + assertEquals("XyZ", iterator.next()); + assertFalse(iterator.hasNext()); + + map = trie.prefixMap("An"); + assertEquals(1, map.size()); + assertEquals("Anna", map.firstKey()); + assertEquals("Anna", map.lastKey()); + iterator = map.keySet().iterator(); + assertEquals("Anna", iterator.next()); + assertFalse(iterator.hasNext()); + + map = trie.prefixMap("Ban"); + assertEquals(1, map.size()); + assertEquals("Banane", map.firstKey()); + assertEquals("Banane", map.lastKey()); + iterator = map.keySet().iterator(); + assertEquals("Banane", iterator.next()); + assertFalse(iterator.hasNext()); + + map = trie.prefixMap("Am"); + assertFalse(map.isEmpty()); + assertEquals(3, map.size()); + assertEquals("Amber", trie.remove("Amber")); + iterator = map.keySet().iterator(); + assertEquals("Amma", iterator.next()); + assertEquals("Ammun", iterator.next()); + assertFalse(iterator.hasNext()); + iterator = map.keySet().iterator(); + map.put("Amber", "Amber"); + assertEquals(3, map.size()); + try { + iterator.next(); + fail("CME expected"); + } catch (ConcurrentModificationException expected) { + } + assertEquals("Amber", map.firstKey()); + assertEquals("Ammun", map.lastKey()); + + map = trie.prefixMap("Ak\0"); + assertTrue(map.isEmpty()); + + map = trie.prefixMap("Ak"); + assertEquals(2, map.size()); + assertEquals("Akka", map.firstKey()); + assertEquals("Akko", map.lastKey()); + map.put("Ak", "Ak"); + assertEquals("Ak", map.firstKey()); + assertEquals("Akko", map.lastKey()); + assertEquals(3, map.size()); + trie.put("Al", "Al"); + assertEquals(3, map.size()); + assertEquals("Ak", map.remove("Ak")); + assertEquals("Akka", map.firstKey()); + assertEquals("Akko", map.lastKey()); + assertEquals(2, map.size()); + iterator = map.keySet().iterator(); + assertEquals("Akka", iterator.next()); + assertEquals("Akko", iterator.next()); + assertFalse(iterator.hasNext()); + assertEquals("Al", trie.remove("Al")); + + map = trie.prefixMap("Akka"); + assertEquals(1, map.size()); + assertEquals("Akka", map.firstKey()); + assertEquals("Akka", map.lastKey()); + iterator = map.keySet().iterator(); + assertEquals("Akka", iterator.next()); + assertFalse(iterator.hasNext()); + + map = trie.prefixMap("Ab"); + assertTrue(map.isEmpty()); + assertEquals(0, map.size()); + try { + Object o = map.firstKey(); + fail("got a first key: " + o); + } catch (NoSuchElementException nsee) { + } + try { + Object o = map.lastKey(); + fail("got a last key: " + o); + } catch (NoSuchElementException nsee) { + } + iterator = map.values().iterator(); + assertFalse(iterator.hasNext()); + + map = trie.prefixMap("Albertooo"); + assertTrue(map.isEmpty()); + assertEquals(0, map.size()); + try { + Object o = map.firstKey(); + fail("got a first key: " + o); + } catch (NoSuchElementException nsee) { + } + try { + Object o = map.lastKey(); + fail("got a last key: " + o); + } catch (NoSuchElementException nsee) { + } + iterator = map.values().iterator(); + assertFalse(iterator.hasNext()); + + map = trie.prefixMap(""); + assertSame(trie, map); // stricter than necessary, but a good check + + map = trie.prefixMap("\0"); + assertTrue(map.isEmpty()); + assertEquals(0, map.size()); + try { + Object o = map.firstKey(); + fail("got a first key: " + o); + } catch (NoSuchElementException nsee) { + } + try { + Object o = map.lastKey(); + fail("got a last key: " + o); + } catch (NoSuchElementException nsee) { + } + iterator = map.values().iterator(); + assertFalse(iterator.hasNext()); + } + + @Test + public void testPrefixedByRemoval() { + PatriciaTrie trie + = new PatriciaTrie(StringKeyAnalyzer.CHAR); + + final String[] keys = new String[]{ + "Albert", "Xavier", "XyZ", "Anna", "Alien", "Alberto", + "Alberts", "Allie", "Alliese", "Alabama", "Banane", + "Blabla", "Amber", "Ammun", "Akka", "Akko", "Albertoo", + "Amma" + }; + + for (String key : keys) { + trie.put(key, key); + } + + SortedMap map = trie.prefixMap("Al"); + assertEquals(8, map.size()); + Iterator iter = map.keySet().iterator(); + assertEquals("Alabama", iter.next()); + assertEquals("Albert", iter.next()); + assertEquals("Alberto", iter.next()); + assertEquals("Albertoo", iter.next()); + assertEquals("Alberts", iter.next()); + assertEquals("Alien", iter.next()); + iter.remove(); + assertEquals(7, map.size()); + assertEquals("Allie", iter.next()); + assertEquals("Alliese", iter.next()); + assertFalse(iter.hasNext()); + + map = trie.prefixMap("Ak"); + assertEquals(2, map.size()); + iter = map.keySet().iterator(); + assertEquals("Akka", iter.next()); + iter.remove(); + assertEquals(1, map.size()); + assertEquals("Akko", iter.next()); + if (iter.hasNext()) + fail("shouldn't have next (but was: " + iter.next() + ")"); + assertFalse(iter.hasNext()); + } + + @Test + public void testTraverseWithAllNullBitKey() { + PatriciaTrie trie + = new PatriciaTrie(StringKeyAnalyzer.CHAR); + + // + // One entry in the Trie + // Entry is stored at the root + // + + // trie.put("", "All Bits Are Zero"); + trie.put("\0", "All Bits Are Zero"); + + // + // / ("") <-- root + // \_/ \ + // null + // + + final List strings = new ArrayList(); + trie.traverse(new Cursor() { + public Decision select(Entry entry) { + strings.add(entry.getValue()); + return Decision.CONTINUE; + } + }); + + assertEquals(1, strings.size()); + + strings.clear(); + for (String s : trie.values()) { + strings.add(s); + } + assertEquals(1, strings.size()); + } + + @Test + public void testSelectWithAllNullBitKey() { + PatriciaTrie trie + = new PatriciaTrie(StringKeyAnalyzer.CHAR); + + // trie.put("", "All Bits Are Zero"); + trie.put("\0", "All Bits Are Zero"); + + final List strings = new ArrayList(); + trie.select("Hello", new Cursor() { + public Decision select(Entry entry) { + strings.add(entry.getValue()); + return Decision.CONTINUE; + } + }); + assertEquals(1, strings.size()); + } + + private static class TestCursor implements Cursor { + private final List keys; + private final List values; + private Object selectFor; + private List toRemove; + private int index = 0; + + TestCursor(Object... objects) { + if (objects.length % 2 != 0) + throw new IllegalArgumentException("must be * 2"); + + keys = new ArrayList(objects.length / 2); + values = new ArrayList(keys.size()); + toRemove = Collections.emptyList(); + for (int i = 0; i < objects.length; i++) { + keys.add(objects[i]); + values.add(objects[++i]); + } + } + + void addToRemove(Object... objects) { + toRemove = new ArrayList(Arrays.asList(objects)); + } + + void remove(Object... objects) { + for (Object object : objects) { + int idx = keys.indexOf(object); + keys.remove(idx); + values.remove(idx); + } + } + + void starting() { + index = 0; + } + + public void checkKey(Object k) { + assertEquals(keys.get(index++), k); + } + + public void checkValue(Object o) { + assertEquals(values.get(index++), o); + } + + public Decision select(Entry entry) { + // System.out.println("Scanning: " + entry.getKey()); + assertEquals(keys.get(index), entry.getKey()); + assertEquals(values.get(index), entry.getValue()); + index++; + + if (toRemove.contains(entry.getKey())) { + // System.out.println("Removing: " + entry.getKey()); + index--; + keys.remove(index); + values.remove(index); + toRemove.remove(entry.getKey()); + return Decision.REMOVE; + } + + if (selectFor != null && selectFor.equals(entry.getKey())) + return Decision.EXIT; + else + return Decision.CONTINUE; + } + + void finished() { + assertEquals(keys.size(), index); + } + } +} diff --git a/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/StringKeyAnalyzer.java b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/StringKeyAnalyzer.java new file mode 100644 index 0000000..da0e187 --- /dev/null +++ b/datastructures-trie/src/test/java/org/xbib/datastructures/trie/patricia/StringKeyAnalyzer.java @@ -0,0 +1,109 @@ +package org.xbib.datastructures.trie.patricia; + +/** + * A {@link KeyAnalyzer} for {@link String}s. + */ +public class StringKeyAnalyzer extends AbstractKeyAnalyzer { + + /** + * A {@link StringKeyAnalyzer} that uses all bits (16) of a {@code char}. + */ + public static final StringKeyAnalyzer CHAR = new StringKeyAnalyzer(Character.SIZE); + + /** + * A {@link StringKeyAnalyzer} that uses only the lower 8 bits of each {@code char}. + */ + public static final StringKeyAnalyzer BYTE = new StringKeyAnalyzer(Byte.SIZE); + + public static final StringKeyAnalyzer INSTANCE = CHAR; + + private final int size; + + private final int msb; + + private StringKeyAnalyzer(int size) { + this(size, 1 << size - 1); + } + + private StringKeyAnalyzer(int size, int msb) { + this.size = size; + this.msb = msb; + } + + /** + * Returns a bit mask where the given bit is set + */ + private int mask(int bit) { + return msb >>> bit; + } + + /** + * Returns the {@code char} at the given index. + */ + private char valueAt(String value, int index) { + if (index < value.length()) { + char ch = value.charAt(index); + if (size == Byte.SIZE) { + ch &= 0xFF; + } + return ch; + } + return 0; + } + + @Override + public int lengthInBits(String key) { + return key.length() * size; + } + + @Override + public boolean isBitSet(String key, int bitIndex) { + if (bitIndex >= lengthInBits(key)) { + return false; + } + + int index = bitIndex / size; + int bit = bitIndex % size; + + return (key.charAt(index) & mask(bit)) != 0; + } + + @Override + public boolean isPrefix(String key, String prefix) { + return key.startsWith(prefix); + } + + @Override + public int bitIndex(String key, String otherKey) { + + boolean allNull = true; + int length = Math.max(key.length(), otherKey.length()); + + for (int i = 0; i < length; i++) { + + char ch1 = valueAt(key, i); + char ch2 = valueAt(otherKey, i); + + if (ch1 != ch2) { + int xor = ch1 ^ ch2; + for (int j = 0; j < size; j++) { + if ((xor & mask(j)) != 0) { + return (i * size) + j; + } + } + } + + if (ch1 != 0) { + allNull = false; + } + } + + // All bits are 0 + if (allNull) { + return KeyAnalyzer.NULL_BIT_KEY; + } + + // Both keys are equal + return KeyAnalyzer.EQUAL_BIT_KEY; + } +} diff --git a/datastructures-trie/src/test/resources/org/xbib/datastructures/trie/patricia/hamlet.txt b/datastructures-trie/src/test/resources/org/xbib/datastructures/trie/patricia/hamlet.txt new file mode 100644 index 0000000..87c8188 --- /dev/null +++ b/datastructures-trie/src/test/resources/org/xbib/datastructures/trie/patricia/hamlet.txt @@ -0,0 +1,6047 @@ + HAMLET + + + DRAMATIS PERSONAE + + +CLAUDIUS king of Denmark. (KING CLAUDIUS:) + +HAMLET son to the late, and nephew to the present king. + +POLONIUS lord chamberlain. (LORD POLONIUS:) + +HORATIO friend to Hamlet. + +LAERTES son to Polonius. + +LUCIANUS nephew to the king. + + +VOLTIMAND | + | +CORNELIUS | + | +ROSENCRANTZ | courtiers. + | +GUILDENSTERN | + | +OSRIC | + + + A Gentleman, (Gentlemen:) + + A Priest. (First Priest:) + + +MARCELLUS | + | officers. +BERNARDO | + + +FRANCISCO a soldier. + +REYNALDO servant to Polonius. + Players. + (First Player:) + (Player King:) + (Player Queen:) + + Two Clowns, grave-diggers. + (First Clown:) + (Second Clown:) + +FORTINBRAS prince of Norway. (PRINCE FORTINBRAS:) + + A Captain. + + English Ambassadors. (First Ambassador:) + +GERTRUDE queen of Denmark, and mother to Hamlet. + (QUEEN GERTRUDE:) + +OPHELIA daughter to Polonius. + + Lords, Ladies, Officers, Soldiers, Sailors, Messengers, + and other Attendants. (Lord:) + (First Sailor:) + (Messenger:) + + Ghost of Hamlet's Father. (Ghost:) + + + +SCENE Denmark. + + + + + HAMLET + + +ACT I + + + +SCENE I Elsinore. A platform before the castle. + + + [FRANCISCO at his post. Enter to him BERNARDO] + +BERNARDO Who's there? + +FRANCISCO Nay, answer me: stand, and unfold yourself. + +BERNARDO Long live the king! + +FRANCISCO Bernardo? + +BERNARDO He. + +FRANCISCO You come most carefully upon your hour. + +BERNARDO 'Tis now struck twelve; get thee to bed, Francisco. + +FRANCISCO For this relief much thanks: 'tis bitter cold, + And I am sick at heart. + +BERNARDO Have you had quiet guard? + +FRANCISCO Not a mouse stirring. + +BERNARDO Well, good night. + If you do meet Horatio and Marcellus, + The rivals of my watch, bid them make haste. + +FRANCISCO I think I hear them. Stand, ho! Who's there? + + [Enter HORATIO and MARCELLUS] + +HORATIO Friends to this ground. + +MARCELLUS And liegemen to the Dane. + +FRANCISCO Give you good night. + +MARCELLUS O, farewell, honest soldier: + Who hath relieved you? + +FRANCISCO Bernardo has my place. + Give you good night. + + [Exit] + +MARCELLUS Holla! Bernardo! + +BERNARDO Say, + What, is Horatio there? + +HORATIO A piece of him. + +BERNARDO Welcome, Horatio: welcome, good Marcellus. + +MARCELLUS What, has this thing appear'd again to-night? + +BERNARDO I have seen nothing. + +MARCELLUS Horatio says 'tis but our fantasy, + And will not let belief take hold of him + Touching this dreaded sight, twice seen of us: + Therefore I have entreated him along + With us to watch the minutes of this night; + That if again this apparition come, + He may approve our eyes and speak to it. + +HORATIO Tush, tush, 'twill not appear. + +BERNARDO Sit down awhile; + And let us once again assail your ears, + That are so fortified against our story + What we have two nights seen. + +HORATIO Well, sit we down, + And let us hear Bernardo speak of this. + +BERNARDO Last night of all, + When yond same star that's westward from the pole + Had made his course to illume that part of heaven + Where now it burns, Marcellus and myself, + The bell then beating one,-- + + [Enter Ghost] + +MARCELLUS Peace, break thee off; look, where it comes again! + +BERNARDO In the same figure, like the king that's dead. + +MARCELLUS Thou art a scholar; speak to it, Horatio. + +BERNARDO Looks it not like the king? mark it, Horatio. + +HORATIO Most like: it harrows me with fear and wonder. + +BERNARDO It would be spoke to. + +MARCELLUS Question it, Horatio. + +HORATIO What art thou that usurp'st this time of night, + Together with that fair and warlike form + In which the majesty of buried Denmark + Did sometimes march? by heaven I charge thee, speak! + +MARCELLUS It is offended. + +BERNARDO See, it stalks away! + +HORATIO Stay! speak, speak! I charge thee, speak! + + [Exit Ghost] + +MARCELLUS 'Tis gone, and will not answer. + +BERNARDO How now, Horatio! you tremble and look pale: + Is not this something more than fantasy? + What think you on't? + +HORATIO Before my God, I might not this believe + Without the sensible and true avouch + Of mine own eyes. + +MARCELLUS Is it not like the king? + +HORATIO As thou art to thyself: + Such was the very armour he had on + When he the ambitious Norway combated; + So frown'd he once, when, in an angry parle, + He smote the sledded Polacks on the ice. + 'Tis strange. + +MARCELLUS Thus twice before, and jump at this dead hour, + With martial stalk hath he gone by our watch. + +HORATIO In what particular thought to work I know not; + But in the gross and scope of my opinion, + This bodes some strange eruption to our state. + +MARCELLUS Good now, sit down, and tell me, he that knows, + Why this same strict and most observant watch + So nightly toils the subject of the land, + And why such daily cast of brazen cannon, + And foreign mart for implements of war; + Why such impress of shipwrights, whose sore task + Does not divide the Sunday from the week; + What might be toward, that this sweaty haste + Doth make the night joint-labourer with the day: + Who is't that can inform me? + +HORATIO That can I; + At least, the whisper goes so. Our last king, + Whose image even but now appear'd to us, + Was, as you know, by Fortinbras of Norway, + Thereto prick'd on by a most emulate pride, + Dared to the combat; in which our valiant Hamlet-- + For so this side of our known world esteem'd him-- + Did slay this Fortinbras; who by a seal'd compact, + Well ratified by law and heraldry, + Did forfeit, with his life, all those his lands + Which he stood seized of, to the conqueror: + Against the which, a moiety competent + Was gaged by our king; which had return'd + To the inheritance of Fortinbras, + Had he been vanquisher; as, by the same covenant, + And carriage of the article design'd, + His fell to Hamlet. Now, sir, young Fortinbras, + Of unimproved mettle hot and full, + Hath in the skirts of Norway here and there + Shark'd up a list of lawless resolutes, + For food and diet, to some enterprise + That hath a stomach in't; which is no other-- + As it doth well appear unto our state-- + But to recover of us, by strong hand + And terms compulsatory, those foresaid lands + So by his father lost: and this, I take it, + Is the main motive of our preparations, + The source of this our watch and the chief head + Of this post-haste and romage in the land. + +BERNARDO I think it be no other but e'en so: + Well may it sort that this portentous figure + Comes armed through our watch; so like the king + That was and is the question of these wars. + +HORATIO A mote it is to trouble the mind's eye. + In the most high and palmy state of Rome, + A little ere the mightiest Julius fell, + The graves stood tenantless and the sheeted dead + Did squeak and gibber in the Roman streets: + As stars with trains of fire and dews of blood, + Disasters in the sun; and the moist star + Upon whose influence Neptune's empire stands + Was sick almost to doomsday with eclipse: + And even the like precurse of fierce events, + As harbingers preceding still the fates + And prologue to the omen coming on, + Have heaven and earth together demonstrated + Unto our climatures and countrymen.-- + But soft, behold! lo, where it comes again! + + [Re-enter Ghost] + + I'll cross it, though it blast me. Stay, illusion! + If thou hast any sound, or use of voice, + Speak to me: + If there be any good thing to be done, + That may to thee do ease and grace to me, + Speak to me: + + [Cock crows] + + If thou art privy to thy country's fate, + Which, happily, foreknowing may avoid, O, speak! + Or if thou hast uphoarded in thy life + Extorted treasure in the womb of earth, + For which, they say, you spirits oft walk in death, + Speak of it: stay, and speak! Stop it, Marcellus. + +MARCELLUS Shall I strike at it with my partisan? + +HORATIO Do, if it will not stand. + +BERNARDO 'Tis here! + +HORATIO 'Tis here! + +MARCELLUS 'Tis gone! + + [Exit Ghost] + + We do it wrong, being so majestical, + To offer it the show of violence; + For it is, as the air, invulnerable, + And our vain blows malicious mockery. + +BERNARDO It was about to speak, when the cock crew. + +HORATIO And then it started like a guilty thing + Upon a fearful summons. I have heard, + The cock, that is the trumpet to the morn, + Doth with his lofty and shrill-sounding throat + Awake the god of day; and, at his warning, + Whether in sea or fire, in earth or air, + The extravagant and erring spirit hies + To his confine: and of the truth herein + This present object made probation. + +MARCELLUS It faded on the crowing of the cock. + Some say that ever 'gainst that season comes + Wherein our Saviour's birth is celebrated, + The bird of dawning singeth all night long: + And then, they say, no spirit dares stir abroad; + The nights are wholesome; then no planets strike, + No fairy takes, nor witch hath power to charm, + So hallow'd and so gracious is the time. + +HORATIO So have I heard and do in part believe it. + But, look, the morn, in russet mantle clad, + Walks o'er the dew of yon high eastward hill: + Break we our watch up; and by my advice, + Let us impart what we have seen to-night + Unto young Hamlet; for, upon my life, + This spirit, dumb to us, will speak to him. + Do you consent we shall acquaint him with it, + As needful in our loves, fitting our duty? + +MARCELLUS Let's do't, I pray; and I this morning know + Where we shall find him most conveniently. + + [Exeunt] + + + + + HAMLET + + +ACT I + + + +SCENE II A room of state in the castle. + + + [Enter KING CLAUDIUS, QUEEN GERTRUDE, HAMLET, + POLONIUS, LAERTES, VOLTIMAND, CORNELIUS, Lords, + and Attendants] + +KING CLAUDIUS Though yet of Hamlet our dear brother's death + The memory be green, and that it us befitted + To bear our hearts in grief and our whole kingdom + To be contracted in one brow of woe, + Yet so far hath discretion fought with nature + That we with wisest sorrow think on him, + Together with remembrance of ourselves. + Therefore our sometime sister, now our queen, + The imperial jointress to this warlike state, + Have we, as 'twere with a defeated joy,-- + With an auspicious and a dropping eye, + With mirth in funeral and with dirge in marriage, + In equal scale weighing delight and dole,-- + Taken to wife: nor have we herein barr'd + Your better wisdoms, which have freely gone + With this affair along. For all, our thanks. + Now follows, that you know, young Fortinbras, + Holding a weak supposal of our worth, + Or thinking by our late dear brother's death + Our state to be disjoint and out of frame, + Colleagued with the dream of his advantage, + He hath not fail'd to pester us with message, + Importing the surrender of those lands + Lost by his father, with all bonds of law, + To our most valiant brother. So much for him. + Now for ourself and for this time of meeting: + Thus much the business is: we have here writ + To Norway, uncle of young Fortinbras,-- + Who, impotent and bed-rid, scarcely hears + Of this his nephew's purpose,--to suppress + His further gait herein; in that the levies, + The lists and full proportions, are all made + Out of his subject: and we here dispatch + You, good Cornelius, and you, Voltimand, + For bearers of this greeting to old Norway; + Giving to you no further personal power + To business with the king, more than the scope + Of these delated articles allow. + Farewell, and let your haste commend your duty. + + +CORNELIUS | + | In that and all things will we show our duty. +VOLTIMAND | + + +KING CLAUDIUS We doubt it nothing: heartily farewell. + + [Exeunt VOLTIMAND and CORNELIUS] + + And now, Laertes, what's the news with you? + You told us of some suit; what is't, Laertes? + You cannot speak of reason to the Dane, + And loose your voice: what wouldst thou beg, Laertes, + That shall not be my offer, not thy asking? + The head is not more native to the heart, + The hand more instrumental to the mouth, + Than is the throne of Denmark to thy father. + What wouldst thou have, Laertes? + +LAERTES My dread lord, + Your leave and favour to return to France; + From whence though willingly I came to Denmark, + To show my duty in your coronation, + Yet now, I must confess, that duty done, + My thoughts and wishes bend again toward France + And bow them to your gracious leave and pardon. + +KING CLAUDIUS Have you your father's leave? What says Polonius? + +LORD POLONIUS He hath, my lord, wrung from me my slow leave + By laboursome petition, and at last + Upon his will I seal'd my hard consent: + I do beseech you, give him leave to go. + +KING CLAUDIUS Take thy fair hour, Laertes; time be thine, + And thy best graces spend it at thy will! + But now, my cousin Hamlet, and my son,-- + +HAMLET [Aside] A little more than kin, and less than kind. + +KING CLAUDIUS How is it that the clouds still hang on you? + +HAMLET Not so, my lord; I am too much i' the sun. + +QUEEN GERTRUDE Good Hamlet, cast thy nighted colour off, + And let thine eye look like a friend on Denmark. + Do not for ever with thy vailed lids + Seek for thy noble father in the dust: + Thou know'st 'tis common; all that lives must die, + Passing through nature to eternity. + +HAMLET Ay, madam, it is common. + +QUEEN GERTRUDE If it be, + Why seems it so particular with thee? + +HAMLET Seems, madam! nay it is; I know not 'seems.' + 'Tis not alone my inky cloak, good mother, + Nor customary suits of solemn black, + Nor windy suspiration of forced breath, + No, nor the fruitful river in the eye, + Nor the dejected 'havior of the visage, + Together with all forms, moods, shapes of grief, + That can denote me truly: these indeed seem, + For they are actions that a man might play: + But I have that within which passeth show; + These but the trappings and the suits of woe. + +KING CLAUDIUS 'Tis sweet and commendable in your nature, Hamlet, + To give these mourning duties to your father: + But, you must know, your father lost a father; + That father lost, lost his, and the survivor bound + In filial obligation for some term + To do obsequious sorrow: but to persever + In obstinate condolement is a course + Of impious stubbornness; 'tis unmanly grief; + It shows a will most incorrect to heaven, + A heart unfortified, a mind impatient, + An understanding simple and unschool'd: + For what we know must be and is as common + As any the most vulgar thing to sense, + Why should we in our peevish opposition + Take it to heart? Fie! 'tis a fault to heaven, + A fault against the dead, a fault to nature, + To reason most absurd: whose common theme + Is death of fathers, and who still hath cried, + From the first corse till he that died to-day, + 'This must be so.' We pray you, throw to earth + This unprevailing woe, and think of us + As of a father: for let the world take note, + You are the most immediate to our throne; + And with no less nobility of love + Than that which dearest father bears his son, + Do I impart toward you. For your intent + In going back to school in Wittenberg, + It is most retrograde to our desire: + And we beseech you, bend you to remain + Here, in the cheer and comfort of our eye, + Our chiefest courtier, cousin, and our son. + +QUEEN GERTRUDE Let not thy mother lose her prayers, Hamlet: + I pray thee, stay with us; go not to Wittenberg. + +HAMLET I shall in all my best obey you, madam. + +KING CLAUDIUS Why, 'tis a loving and a fair reply: + Be as ourself in Denmark. Madam, come; + This gentle and unforced accord of Hamlet + Sits smiling to my heart: in grace whereof, + No jocund health that Denmark drinks to-day, + But the great cannon to the clouds shall tell, + And the king's rouse the heavens all bruit again, + Re-speaking earthly thunder. Come away. + + [Exeunt all but HAMLET] + +HAMLET O, that this too too solid flesh would melt + Thaw and resolve itself into a dew! + Or that the Everlasting had not fix'd + His canon 'gainst self-slaughter! O God! God! + How weary, stale, flat and unprofitable, + Seem to me all the uses of this world! + Fie on't! ah fie! 'tis an unweeded garden, + That grows to seed; things rank and gross in nature + Possess it merely. That it should come to this! + But two months dead: nay, not so much, not two: + So excellent a king; that was, to this, + Hyperion to a satyr; so loving to my mother + That he might not beteem the winds of heaven + Visit her face too roughly. Heaven and earth! + Must I remember? why, she would hang on him, + As if increase of appetite had grown + By what it fed on: and yet, within a month-- + Let me not think on't--Frailty, thy name is woman!-- + A little month, or ere those shoes were old + With which she follow'd my poor father's body, + Like Niobe, all tears:--why she, even she-- + O, God! a beast, that wants discourse of reason, + Would have mourn'd longer--married with my uncle, + My father's brother, but no more like my father + Than I to Hercules: within a month: + Ere yet the salt of most unrighteous tears + Had left the flushing in her galled eyes, + She married. O, most wicked speed, to post + With such dexterity to incestuous sheets! + It is not nor it cannot come to good: + But break, my heart; for I must hold my tongue. + + [Enter HORATIO, MARCELLUS, and BERNARDO] + +HORATIO Hail to your lordship! + +HAMLET I am glad to see you well: + Horatio,--or I do forget myself. + +HORATIO The same, my lord, and your poor servant ever. + +HAMLET Sir, my good friend; I'll change that name with you: + And what make you from Wittenberg, Horatio? Marcellus? + +MARCELLUS My good lord-- + +HAMLET I am very glad to see you. Good even, sir. + But what, in faith, make you from Wittenberg? + +HORATIO A truant disposition, good my lord. + +HAMLET I would not hear your enemy say so, + Nor shall you do mine ear that violence, + To make it truster of your own report + Against yourself: I know you are no truant. + But what is your affair in Elsinore? + We'll teach you to drink deep ere you depart. + +HORATIO My lord, I came to see your father's funeral. + +HAMLET I pray thee, do not mock me, fellow-student; + I think it was to see my mother's wedding. + +HORATIO Indeed, my lord, it follow'd hard upon. + +HAMLET Thrift, thrift, Horatio! the funeral baked meats + Did coldly furnish forth the marriage tables. + Would I had met my dearest foe in heaven + Or ever I had seen that day, Horatio! + My father!--methinks I see my father. + +HORATIO Where, my lord? + +HAMLET In my mind's eye, Horatio. + +HORATIO I saw him once; he was a goodly king. + +HAMLET He was a man, take him for all in all, + I shall not look upon his like again. + +HORATIO My lord, I think I saw him yesternight. + +HAMLET Saw? who? + +HORATIO My lord, the king your father. + +HAMLET The king my father! + +HORATIO Season your admiration for awhile + With an attent ear, till I may deliver, + Upon the witness of these gentlemen, + This marvel to you. + +HAMLET For God's love, let me hear. + +HORATIO Two nights together had these gentlemen, + Marcellus and Bernardo, on their watch, + In the dead vast and middle of the night, + Been thus encounter'd. A figure like your father, + Armed at point exactly, cap-a-pe, + Appears before them, and with solemn march + Goes slow and stately by them: thrice he walk'd + By their oppress'd and fear-surprised eyes, + Within his truncheon's length; whilst they, distilled + Almost to jelly with the act of fear, + Stand dumb and speak not to him. This to me + In dreadful secrecy impart they did; + And I with them the third night kept the watch; + Where, as they had deliver'd, both in time, + Form of the thing, each word made true and good, + The apparition comes: I knew your father; + These hands are not more like. + +HAMLET But where was this? + +MARCELLUS My lord, upon the platform where we watch'd. + +HAMLET Did you not speak to it? + +HORATIO My lord, I did; + But answer made it none: yet once methought + It lifted up its head and did address + Itself to motion, like as it would speak; + But even then the morning cock crew loud, + And at the sound it shrunk in haste away, + And vanish'd from our sight. + +HAMLET 'Tis very strange. + +HORATIO As I do live, my honour'd lord, 'tis true; + And we did think it writ down in our duty + To let you know of it. + +HAMLET Indeed, indeed, sirs, but this troubles me. + Hold you the watch to-night? + + +MARCELLUS | + | We do, my lord. +BERNARDO | + + +HAMLET Arm'd, say you? + + +MARCELLUS | + | Arm'd, my lord. +BERNARDO | + + +HAMLET From top to toe? + + +MARCELLUS | + | My lord, from head to foot. +BERNARDO | + + +HAMLET Then saw you not his face? + +HORATIO O, yes, my lord; he wore his beaver up. + +HAMLET What, look'd he frowningly? + +HORATIO A countenance more in sorrow than in anger. + +HAMLET Pale or red? + +HORATIO Nay, very pale. + +HAMLET And fix'd his eyes upon you? + +HORATIO Most constantly. + +HAMLET I would I had been there. + +HORATIO It would have much amazed you. + +HAMLET Very like, very like. Stay'd it long? + +HORATIO While one with moderate haste might tell a hundred. + + +MARCELLUS | + | Longer, longer. +BERNARDO | + + +HORATIO Not when I saw't. + +HAMLET His beard was grizzled--no? + +HORATIO It was, as I have seen it in his life, + A sable silver'd. + +HAMLET I will watch to-night; + Perchance 'twill walk again. + +HORATIO I warrant it will. + +HAMLET If it assume my noble father's person, + I'll speak to it, though hell itself should gape + And bid me hold my peace. I pray you all, + If you have hitherto conceal'd this sight, + Let it be tenable in your silence still; + And whatsoever else shall hap to-night, + Give it an understanding, but no tongue: + I will requite your loves. So, fare you well: + Upon the platform, 'twixt eleven and twelve, + I'll visit you. + +All Our duty to your honour. + +HAMLET Your loves, as mine to you: farewell. + + [Exeunt all but HAMLET] + + My father's spirit in arms! all is not well; + I doubt some foul play: would the night were come! + Till then sit still, my soul: foul deeds will rise, + Though all the earth o'erwhelm them, to men's eyes. + + [Exit] + + + + + HAMLET + + +ACT I + + + +SCENE III A room in Polonius' house. + + + [Enter LAERTES and OPHELIA] + +LAERTES My necessaries are embark'd: farewell: + And, sister, as the winds give benefit + And convoy is assistant, do not sleep, + But let me hear from you. + +OPHELIA Do you doubt that? + +LAERTES For Hamlet and the trifling of his favour, + Hold it a fashion and a toy in blood, + A violet in the youth of primy nature, + Forward, not permanent, sweet, not lasting, + The perfume and suppliance of a minute; No more. + +OPHELIA No more but so? + +LAERTES Think it no more; + For nature, crescent, does not grow alone + In thews and bulk, but, as this temple waxes, + The inward service of the mind and soul + Grows wide withal. Perhaps he loves you now, + And now no soil nor cautel doth besmirch + The virtue of his will: but you must fear, + His greatness weigh'd, his will is not his own; + For he himself is subject to his birth: + He may not, as unvalued persons do, + Carve for himself; for on his choice depends + The safety and health of this whole state; + And therefore must his choice be circumscribed + Unto the voice and yielding of that body + Whereof he is the head. Then if he says he loves you, + It fits your wisdom so far to believe it + As he in his particular act and place + May give his saying deed; which is no further + Than the main voice of Denmark goes withal. + Then weigh what loss your honour may sustain, + If with too credent ear you list his songs, + Or lose your heart, or your chaste treasure open + To his unmaster'd importunity. + Fear it, Ophelia, fear it, my dear sister, + And keep you in the rear of your affection, + Out of the shot and danger of desire. + The chariest maid is prodigal enough, + If she unmask her beauty to the moon: + Virtue itself 'scapes not calumnious strokes: + The canker galls the infants of the spring, + Too oft before their buttons be disclosed, + And in the morn and liquid dew of youth + Contagious blastments are most imminent. + Be wary then; best safety lies in fear: + Youth to itself rebels, though none else near. + +OPHELIA I shall the effect of this good lesson keep, + As watchman to my heart. But, good my brother, + Do not, as some ungracious pastors do, + Show me the steep and thorny way to heaven; + Whiles, like a puff'd and reckless libertine, + Himself the primrose path of dalliance treads, + And recks not his own rede. + +LAERTES O, fear me not. + I stay too long: but here my father comes. + + [Enter POLONIUS] + + A double blessing is a double grace, + Occasion smiles upon a second leave. + +LORD POLONIUS Yet here, Laertes! aboard, aboard, for shame! + The wind sits in the shoulder of your sail, + And you are stay'd for. There; my blessing with thee! + And these few precepts in thy memory + See thou character. Give thy thoughts no tongue, + Nor any unproportioned thought his act. + Be thou familiar, but by no means vulgar. + Those friends thou hast, and their adoption tried, + Grapple them to thy soul with hoops of steel; + But do not dull thy palm with entertainment + Of each new-hatch'd, unfledged comrade. Beware + Of entrance to a quarrel, but being in, + Bear't that the opposed may beware of thee. + Give every man thy ear, but few thy voice; + Take each man's censure, but reserve thy judgment. + Costly thy habit as thy purse can buy, + But not express'd in fancy; rich, not gaudy; + For the apparel oft proclaims the man, + And they in France of the best rank and station + Are of a most select and generous chief in that. + Neither a borrower nor a lender be; + For loan oft loses both itself and friend, + And borrowing dulls the edge of husbandry. + This above all: to thine ownself be true, + And it must follow, as the night the day, + Thou canst not then be false to any man. + Farewell: my blessing season this in thee! + +LAERTES Most humbly do I take my leave, my lord. + +LORD POLONIUS The time invites you; go; your servants tend. + +LAERTES Farewell, Ophelia; and remember well + What I have said to you. + +OPHELIA 'Tis in my memory lock'd, + And you yourself shall keep the key of it. + +LAERTES Farewell. + + [Exit] + +LORD POLONIUS What is't, Ophelia, be hath said to you? + +OPHELIA So please you, something touching the Lord Hamlet. + +LORD POLONIUS Marry, well bethought: + 'Tis told me, he hath very oft of late + Given private time to you; and you yourself + Have of your audience been most free and bounteous: + If it be so, as so 'tis put on me, + And that in way of caution, I must tell you, + You do not understand yourself so clearly + As it behoves my daughter and your honour. + What is between you? give me up the truth. + +OPHELIA He hath, my lord, of late made many tenders + Of his affection to me. + +LORD POLONIUS Affection! pooh! you speak like a green girl, + Unsifted in such perilous circumstance. + Do you believe his tenders, as you call them? + +OPHELIA I do not know, my lord, what I should think. + +LORD POLONIUS Marry, I'll teach you: think yourself a baby; + That you have ta'en these tenders for true pay, + Which are not sterling. Tender yourself more dearly; + Or--not to crack the wind of the poor phrase, + Running it thus--you'll tender me a fool. + +OPHELIA My lord, he hath importuned me with love + In honourable fashion. + +LORD POLONIUS Ay, fashion you may call it; go to, go to. + +OPHELIA And hath given countenance to his speech, my lord, + With almost all the holy vows of heaven. + +LORD POLONIUS Ay, springes to catch woodcocks. I do know, + When the blood burns, how prodigal the soul + Lends the tongue vows: these blazes, daughter, + Giving more light than heat, extinct in both, + Even in their promise, as it is a-making, + You must not take for fire. From this time + Be somewhat scanter of your maiden presence; + Set your entreatments at a higher rate + Than a command to parley. For Lord Hamlet, + Believe so much in him, that he is young + And with a larger tether may he walk + Than may be given you: in few, Ophelia, + Do not believe his vows; for they are brokers, + Not of that dye which their investments show, + But mere implorators of unholy suits, + Breathing like sanctified and pious bawds, + The better to beguile. This is for all: + I would not, in plain terms, from this time forth, + Have you so slander any moment leisure, + As to give words or talk with the Lord Hamlet. + Look to't, I charge you: come your ways. + +OPHELIA I shall obey, my lord. + + [Exeunt] + + + + + HAMLET + + +ACT I + + + +SCENE IV The platform. + + + [Enter HAMLET, HORATIO, and MARCELLUS] + +HAMLET The air bites shrewdly; it is very cold. + +HORATIO It is a nipping and an eager air. + +HAMLET What hour now? + +HORATIO I think it lacks of twelve. + +HAMLET No, it is struck. + +HORATIO Indeed? I heard it not: then it draws near the season + Wherein the spirit held his wont to walk. + + [A flourish of trumpets, and ordnance shot off, within] + + What does this mean, my lord? + +HAMLET The king doth wake to-night and takes his rouse, + Keeps wassail, and the swaggering up-spring reels; + And, as he drains his draughts of Rhenish down, + The kettle-drum and trumpet thus bray out + The triumph of his pledge. + +HORATIO Is it a custom? + +HAMLET Ay, marry, is't: + But to my mind, though I am native here + And to the manner born, it is a custom + More honour'd in the breach than the observance. + This heavy-headed revel east and west + Makes us traduced and tax'd of other nations: + They clepe us drunkards, and with swinish phrase + Soil our addition; and indeed it takes + From our achievements, though perform'd at height, + The pith and marrow of our attribute. + So, oft it chances in particular men, + That for some vicious mole of nature in them, + As, in their birth--wherein they are not guilty, + Since nature cannot choose his origin-- + By the o'ergrowth of some complexion, + Oft breaking down the pales and forts of reason, + Or by some habit that too much o'er-leavens + The form of plausive manners, that these men, + Carrying, I say, the stamp of one defect, + Being nature's livery, or fortune's star,-- + Their virtues else--be they as pure as grace, + As infinite as man may undergo-- + Shall in the general censure take corruption + From that particular fault: the dram of eale + Doth all the noble substance of a doubt + To his own scandal. + +HORATIO Look, my lord, it comes! + + [Enter Ghost] + +HAMLET Angels and ministers of grace defend us! + Be thou a spirit of health or goblin damn'd, + Bring with thee airs from heaven or blasts from hell, + Be thy intents wicked or charitable, + Thou comest in such a questionable shape + That I will speak to thee: I'll call thee Hamlet, + King, father, royal Dane: O, answer me! + Let me not burst in ignorance; but tell + Why thy canonized bones, hearsed in death, + Have burst their cerements; why the sepulchre, + Wherein we saw thee quietly inurn'd, + Hath oped his ponderous and marble jaws, + To cast thee up again. What may this mean, + That thou, dead corse, again in complete steel + Revisit'st thus the glimpses of the moon, + Making night hideous; and we fools of nature + So horridly to shake our disposition + With thoughts beyond the reaches of our souls? + Say, why is this? wherefore? what should we do? + + [Ghost beckons HAMLET] + +HORATIO It beckons you to go away with it, + As if it some impartment did desire + To you alone. + +MARCELLUS Look, with what courteous action + It waves you to a more removed ground: + But do not go with it. + +HORATIO No, by no means. + +HAMLET It will not speak; then I will follow it. + +HORATIO Do not, my lord. + +HAMLET Why, what should be the fear? + I do not set my life in a pin's fee; + And for my soul, what can it do to that, + Being a thing immortal as itself? + It waves me forth again: I'll follow it. + +HORATIO What if it tempt you toward the flood, my lord, + Or to the dreadful summit of the cliff + That beetles o'er his base into the sea, + And there assume some other horrible form, + Which might deprive your sovereignty of reason + And draw you into madness? think of it: + The very place puts toys of desperation, + Without more motive, into every brain + That looks so many fathoms to the sea + And hears it roar beneath. + +HAMLET It waves me still. + Go on; I'll follow thee. + +MARCELLUS You shall not go, my lord. + +HAMLET Hold off your hands. + +HORATIO Be ruled; you shall not go. + +HAMLET My fate cries out, + And makes each petty artery in this body + As hardy as the Nemean lion's nerve. + Still am I call'd. Unhand me, gentlemen. + By heaven, I'll make a ghost of him that lets me! + I say, away! Go on; I'll follow thee. + + [Exeunt Ghost and HAMLET] + +HORATIO He waxes desperate with imagination. + +MARCELLUS Let's follow; 'tis not fit thus to obey him. + +HORATIO Have after. To what issue will this come? + +MARCELLUS Something is rotten in the state of Denmark. + +HORATIO Heaven will direct it. + +MARCELLUS Nay, let's follow him. + + [Exeunt] + + + + + HAMLET + + +ACT I + + + +SCENE V Another part of the platform. + + + [Enter GHOST and HAMLET] + +HAMLET Where wilt thou lead me? speak; I'll go no further. + +Ghost Mark me. + +HAMLET I will. + +Ghost My hour is almost come, + When I to sulphurous and tormenting flames + Must render up myself. + +HAMLET Alas, poor ghost! + +Ghost Pity me not, but lend thy serious hearing + To what I shall unfold. + +HAMLET Speak; I am bound to hear. + +Ghost So art thou to revenge, when thou shalt hear. + +HAMLET What? + +Ghost I am thy father's spirit, + Doom'd for a certain term to walk the night, + And for the day confined to fast in fires, + Till the foul crimes done in my days of nature + Are burnt and purged away. But that I am forbid + To tell the secrets of my prison-house, + I could a tale unfold whose lightest word + Would harrow up thy soul, freeze thy young blood, + Make thy two eyes, like stars, start from their spheres, + Thy knotted and combined locks to part + And each particular hair to stand on end, + Like quills upon the fretful porpentine: + But this eternal blazon must not be + To ears of flesh and blood. List, list, O, list! + If thou didst ever thy dear father love-- + +HAMLET O God! + +Ghost Revenge his foul and most unnatural murder. + +HAMLET Murder! + +Ghost Murder most foul, as in the best it is; + But this most foul, strange and unnatural. + +HAMLET Haste me to know't, that I, with wings as swift + As meditation or the thoughts of love, + May sweep to my revenge. + +Ghost I find thee apt; + And duller shouldst thou be than the fat weed + That roots itself in ease on Lethe wharf, + Wouldst thou not stir in this. Now, Hamlet, hear: + 'Tis given out that, sleeping in my orchard, + A serpent stung me; so the whole ear of Denmark + Is by a forged process of my death + Rankly abused: but know, thou noble youth, + The serpent that did sting thy father's life + Now wears his crown. + +HAMLET O my prophetic soul! My uncle! + +Ghost Ay, that incestuous, that adulterate beast, + With witchcraft of his wit, with traitorous gifts,-- + O wicked wit and gifts, that have the power + So to seduce!--won to his shameful lust + The will of my most seeming-virtuous queen: + O Hamlet, what a falling-off was there! + From me, whose love was of that dignity + That it went hand in hand even with the vow + I made to her in marriage, and to decline + Upon a wretch whose natural gifts were poor + To those of mine! + But virtue, as it never will be moved, + Though lewdness court it in a shape of heaven, + So lust, though to a radiant angel link'd, + Will sate itself in a celestial bed, + And prey on garbage. + But, soft! methinks I scent the morning air; + Brief let me be. Sleeping within my orchard, + My custom always of the afternoon, + Upon my secure hour thy uncle stole, + With juice of cursed hebenon in a vial, + And in the porches of my ears did pour + The leperous distilment; whose effect + Holds such an enmity with blood of man + That swift as quicksilver it courses through + The natural gates and alleys of the body, + And with a sudden vigour doth posset + And curd, like eager droppings into milk, + The thin and wholesome blood: so did it mine; + And a most instant tetter bark'd about, + Most lazar-like, with vile and loathsome crust, + All my smooth body. + Thus was I, sleeping, by a brother's hand + Of life, of crown, of queen, at once dispatch'd: + Cut off even in the blossoms of my sin, + Unhousel'd, disappointed, unanel'd, + No reckoning made, but sent to my account + With all my imperfections on my head: + O, horrible! O, horrible! most horrible! + If thou hast nature in thee, bear it not; + Let not the royal bed of Denmark be + A couch for luxury and damned incest. + But, howsoever thou pursuest this act, + Taint not thy mind, nor let thy soul contrive + Against thy mother aught: leave her to heaven + And to those thorns that in her bosom lodge, + To prick and sting her. Fare thee well at once! + The glow-worm shows the matin to be near, + And 'gins to pale his uneffectual fire: + Adieu, adieu! Hamlet, remember me. + + [Exit] + +HAMLET O all you host of heaven! O earth! what else? + And shall I couple hell? O, fie! Hold, hold, my heart; + And you, my sinews, grow not instant old, + But bear me stiffly up. Remember thee! + Ay, thou poor ghost, while memory holds a seat + In this distracted globe. Remember thee! + Yea, from the table of my memory + I'll wipe away all trivial fond records, + All saws of books, all forms, all pressures past, + That youth and observation copied there; + And thy commandment all alone shall live + Within the book and volume of my brain, + Unmix'd with baser matter: yes, by heaven! + O most pernicious woman! + O villain, villain, smiling, damned villain! + My tables,--meet it is I set it down, + That one may smile, and smile, and be a villain; + At least I'm sure it may be so in Denmark: + + [Writing] + + So, uncle, there you are. Now to my word; + It is 'Adieu, adieu! remember me.' + I have sworn 't. + + +MARCELLUS | + | [Within] My lord, my lord,-- +HORATIO | + + +MARCELLUS [Within] Lord Hamlet,-- + +HORATIO [Within] Heaven secure him! + +HAMLET So be it! + +HORATIO [Within] Hillo, ho, ho, my lord! + +HAMLET Hillo, ho, ho, boy! come, bird, come. + + [Enter HORATIO and MARCELLUS] + +MARCELLUS How is't, my noble lord? + +HORATIO What news, my lord? + +HAMLET O, wonderful! + +HORATIO Good my lord, tell it. + +HAMLET No; you'll reveal it. + +HORATIO Not I, my lord, by heaven. + +MARCELLUS Nor I, my lord. + +HAMLET How say you, then; would heart of man once think it? + But you'll be secret? + + +HORATIO | + | Ay, by heaven, my lord. +MARCELLUS | + + +HAMLET There's ne'er a villain dwelling in all Denmark + But he's an arrant knave. + +HORATIO There needs no ghost, my lord, come from the grave + To tell us this. + +HAMLET Why, right; you are i' the right; + And so, without more circumstance at all, + I hold it fit that we shake hands and part: + You, as your business and desire shall point you; + For every man has business and desire, + Such as it is; and for mine own poor part, + Look you, I'll go pray. + +HORATIO These are but wild and whirling words, my lord. + +HAMLET I'm sorry they offend you, heartily; + Yes, 'faith heartily. + +HORATIO There's no offence, my lord. + +HAMLET Yes, by Saint Patrick, but there is, Horatio, + And much offence too. Touching this vision here, + It is an honest ghost, that let me tell you: + For your desire to know what is between us, + O'ermaster 't as you may. And now, good friends, + As you are friends, scholars and soldiers, + Give me one poor request. + +HORATIO What is't, my lord? we will. + +HAMLET Never make known what you have seen to-night. + + +HORATIO | + | My lord, we will not. +MARCELLUS | + + +HAMLET Nay, but swear't. + +HORATIO In faith, + My lord, not I. + +MARCELLUS Nor I, my lord, in faith. + +HAMLET Upon my sword. + +MARCELLUS We have sworn, my lord, already. + +HAMLET Indeed, upon my sword, indeed. + +Ghost [Beneath] Swear. + +HAMLET Ah, ha, boy! say'st thou so? art thou there, + truepenny? + Come on--you hear this fellow in the cellarage-- + Consent to swear. + +HORATIO Propose the oath, my lord. + +HAMLET Never to speak of this that you have seen, + Swear by my sword. + +Ghost [Beneath] Swear. + +HAMLET Hic et ubique? then we'll shift our ground. + Come hither, gentlemen, + And lay your hands again upon my sword: + Never to speak of this that you have heard, + Swear by my sword. + +Ghost [Beneath] Swear. + +HAMLET Well said, old mole! canst work i' the earth so fast? + A worthy pioner! Once more remove, good friends. + +HORATIO O day and night, but this is wondrous strange! + +HAMLET And therefore as a stranger give it welcome. + There are more things in heaven and earth, Horatio, + Than are dreamt of in your philosophy. But come; + Here, as before, never, so help you mercy, + How strange or odd soe'er I bear myself, + As I perchance hereafter shall think meet + To put an antic disposition on, + That you, at such times seeing me, never shall, + With arms encumber'd thus, or this headshake, + Or by pronouncing of some doubtful phrase, + As 'Well, well, we know,' or 'We could, an if we would,' + Or 'If we list to speak,' or 'There be, an if they might,' + Or such ambiguous giving out, to note + That you know aught of me: this not to do, + So grace and mercy at your most need help you, Swear. + +Ghost [Beneath] Swear. + +HAMLET Rest, rest, perturbed spirit! + + [They swear] + + So, gentlemen, + With all my love I do commend me to you: + And what so poor a man as Hamlet is + May do, to express his love and friending to you, + God willing, shall not lack. Let us go in together; + And still your fingers on your lips, I pray. + The time is out of joint: O cursed spite, + That ever I was born to set it right! + Nay, come, let's go together. + + [Exeunt] + + + + + HAMLET + + +ACT II + + + +SCENE I A room in POLONIUS' house. + + + [Enter POLONIUS and REYNALDO] + +LORD POLONIUS Give him this money and these notes, Reynaldo. + +REYNALDO I will, my lord. + +LORD POLONIUS You shall do marvellous wisely, good Reynaldo, + Before you visit him, to make inquire + Of his behavior. + +REYNALDO My lord, I did intend it. + +LORD POLONIUS Marry, well said; very well said. Look you, sir, + Inquire me first what Danskers are in Paris; + And how, and who, what means, and where they keep, + What company, at what expense; and finding + By this encompassment and drift of question + That they do know my son, come you more nearer + Than your particular demands will touch it: + Take you, as 'twere, some distant knowledge of him; + As thus, 'I know his father and his friends, + And in part him: ' do you mark this, Reynaldo? + +REYNALDO Ay, very well, my lord. + +LORD POLONIUS 'And in part him; but' you may say 'not well: + But, if't be he I mean, he's very wild; + Addicted so and so:' and there put on him + What forgeries you please; marry, none so rank + As may dishonour him; take heed of that; + But, sir, such wanton, wild and usual slips + As are companions noted and most known + To youth and liberty. + +REYNALDO As gaming, my lord. + +LORD POLONIUS Ay, or drinking, fencing, swearing, quarrelling, + Drabbing: you may go so far. + +REYNALDO My lord, that would dishonour him. + +LORD POLONIUS 'Faith, no; as you may season it in the charge + You must not put another scandal on him, + That he is open to incontinency; + That's not my meaning: but breathe his faults so quaintly + That they may seem the taints of liberty, + The flash and outbreak of a fiery mind, + A savageness in unreclaimed blood, + Of general assault. + +REYNALDO But, my good lord,-- + +LORD POLONIUS Wherefore should you do this? + +REYNALDO Ay, my lord, + I would know that. + +LORD POLONIUS Marry, sir, here's my drift; + And I believe, it is a fetch of wit: + You laying these slight sullies on my son, + As 'twere a thing a little soil'd i' the working, Mark you, + Your party in converse, him you would sound, + Having ever seen in the prenominate crimes + The youth you breathe of guilty, be assured + He closes with you in this consequence; + 'Good sir,' or so, or 'friend,' or 'gentleman,' + According to the phrase or the addition + Of man and country. + +REYNALDO Very good, my lord. + +LORD POLONIUS And then, sir, does he this--he does--what was I + about to say? By the mass, I was about to say + something: where did I leave? + +REYNALDO At 'closes in the consequence,' at 'friend or so,' + and 'gentleman.' + +LORD POLONIUS At 'closes in the consequence,' ay, marry; + He closes thus: 'I know the gentleman; + I saw him yesterday, or t' other day, + Or then, or then; with such, or such; and, as you say, + There was a' gaming; there o'ertook in's rouse; + There falling out at tennis:' or perchance, + 'I saw him enter such a house of sale,' + Videlicet, a brothel, or so forth. + See you now; + Your bait of falsehood takes this carp of truth: + And thus do we of wisdom and of reach, + With windlasses and with assays of bias, + By indirections find directions out: + So by my former lecture and advice, + Shall you my son. You have me, have you not? + +REYNALDO My lord, I have. + +LORD POLONIUS God be wi' you; fare you well. + +REYNALDO Good my lord! + +LORD POLONIUS Observe his inclination in yourself. + +REYNALDO I shall, my lord. + +LORD POLONIUS And let him ply his music. + +REYNALDO Well, my lord. + +LORD POLONIUS Farewell! + + [Exit REYNALDO] + + [Enter OPHELIA] + + How now, Ophelia! what's the matter? + +OPHELIA O, my lord, my lord, I have been so affrighted! + +LORD POLONIUS With what, i' the name of God? + +OPHELIA My lord, as I was sewing in my closet, + Lord Hamlet, with his doublet all unbraced; + No hat upon his head; his stockings foul'd, + Ungarter'd, and down-gyved to his ancle; + Pale as his shirt; his knees knocking each other; + And with a look so piteous in purport + As if he had been loosed out of hell + To speak of horrors,--he comes before me. + +LORD POLONIUS Mad for thy love? + +OPHELIA My lord, I do not know; + But truly, I do fear it. + +LORD POLONIUS What said he? + +OPHELIA He took me by the wrist and held me hard; + Then goes he to the length of all his arm; + And, with his other hand thus o'er his brow, + He falls to such perusal of my face + As he would draw it. Long stay'd he so; + At last, a little shaking of mine arm + And thrice his head thus waving up and down, + He raised a sigh so piteous and profound + As it did seem to shatter all his bulk + And end his being: that done, he lets me go: + And, with his head over his shoulder turn'd, + He seem'd to find his way without his eyes; + For out o' doors he went without their helps, + And, to the last, bended their light on me. + +LORD POLONIUS Come, go with me: I will go seek the king. + This is the very ecstasy of love, + Whose violent property fordoes itself + And leads the will to desperate undertakings + As oft as any passion under heaven + That does afflict our natures. I am sorry. + What, have you given him any hard words of late? + +OPHELIA No, my good lord, but, as you did command, + I did repel his fetters and denied + His access to me. + +LORD POLONIUS That hath made him mad. + I am sorry that with better heed and judgment + I had not quoted him: I fear'd he did but trifle, + And meant to wreck thee; but, beshrew my jealousy! + By heaven, it is as proper to our age + To cast beyond ourselves in our opinions + As it is common for the younger sort + To lack discretion. Come, go we to the king: + This must be known; which, being kept close, might + move + More grief to hide than hate to utter love. + + [Exeunt] + + + + + HAMLET + + +ACT II + + + +SCENE II A room in the castle. + + + [Enter KING CLAUDIUS, QUEEN GERTRUDE, ROSENCRANTZ, + GUILDENSTERN, and Attendants] + +KING CLAUDIUS Welcome, dear Rosencrantz and Guildenstern! + Moreover that we much did long to see you, + The need we have to use you did provoke + Our hasty sending. Something have you heard + Of Hamlet's transformation; so call it, + Sith nor the exterior nor the inward man + Resembles that it was. What it should be, + More than his father's death, that thus hath put him + So much from the understanding of himself, + I cannot dream of: I entreat you both, + That, being of so young days brought up with him, + And sith so neighbour'd to his youth and havior, + That you vouchsafe your rest here in our court + Some little time: so by your companies + To draw him on to pleasures, and to gather, + So much as from occasion you may glean, + Whether aught, to us unknown, afflicts him thus, + That, open'd, lies within our remedy. + +QUEEN GERTRUDE Good gentlemen, he hath much talk'd of you; + And sure I am two men there are not living + To whom he more adheres. If it will please you + To show us so much gentry and good will + As to expend your time with us awhile, + For the supply and profit of our hope, + Your visitation shall receive such thanks + As fits a king's remembrance. + +ROSENCRANTZ Both your majesties + Might, by the sovereign power you have of us, + Put your dread pleasures more into command + Than to entreaty. + +GUILDENSTERN But we both obey, + And here give up ourselves, in the full bent + To lay our service freely at your feet, + To be commanded. + +KING CLAUDIUS Thanks, Rosencrantz and gentle Guildenstern. + +QUEEN GERTRUDE Thanks, Guildenstern and gentle Rosencrantz: + And I beseech you instantly to visit + My too much changed son. Go, some of you, + And bring these gentlemen where Hamlet is. + +GUILDENSTERN Heavens make our presence and our practises + Pleasant and helpful to him! + +QUEEN GERTRUDE Ay, amen! + + [Exeunt ROSENCRANTZ, GUILDENSTERN, and some + Attendants] + + [Enter POLONIUS] + +LORD POLONIUS The ambassadors from Norway, my good lord, + Are joyfully return'd. + +KING CLAUDIUS Thou still hast been the father of good news. + +LORD POLONIUS Have I, my lord? I assure my good liege, + I hold my duty, as I hold my soul, + Both to my God and to my gracious king: + And I do think, or else this brain of mine + Hunts not the trail of policy so sure + As it hath used to do, that I have found + The very cause of Hamlet's lunacy. + +KING CLAUDIUS O, speak of that; that do I long to hear. + +LORD POLONIUS Give first admittance to the ambassadors; + My news shall be the fruit to that great feast. + +KING CLAUDIUS Thyself do grace to them, and bring them in. + + [Exit POLONIUS] + + He tells me, my dear Gertrude, he hath found + The head and source of all your son's distemper. + +QUEEN GERTRUDE I doubt it is no other but the main; + His father's death, and our o'erhasty marriage. + +KING CLAUDIUS Well, we shall sift him. + + [Re-enter POLONIUS, with VOLTIMAND and CORNELIUS] + + Welcome, my good friends! + Say, Voltimand, what from our brother Norway? + +VOLTIMAND Most fair return of greetings and desires. + Upon our first, he sent out to suppress + His nephew's levies; which to him appear'd + To be a preparation 'gainst the Polack; + But, better look'd into, he truly found + It was against your highness: whereat grieved, + That so his sickness, age and impotence + Was falsely borne in hand, sends out arrests + On Fortinbras; which he, in brief, obeys; + Receives rebuke from Norway, and in fine + Makes vow before his uncle never more + To give the assay of arms against your majesty. + Whereon old Norway, overcome with joy, + Gives him three thousand crowns in annual fee, + And his commission to employ those soldiers, + So levied as before, against the Polack: + With an entreaty, herein further shown, + + [Giving a paper] + + That it might please you to give quiet pass + Through your dominions for this enterprise, + On such regards of safety and allowance + As therein are set down. + +KING CLAUDIUS It likes us well; + And at our more consider'd time well read, + Answer, and think upon this business. + Meantime we thank you for your well-took labour: + Go to your rest; at night we'll feast together: + Most welcome home! + + [Exeunt VOLTIMAND and CORNELIUS] + +LORD POLONIUS This business is well ended. + My liege, and madam, to expostulate + What majesty should be, what duty is, + Why day is day, night night, and time is time, + Were nothing but to waste night, day and time. + Therefore, since brevity is the soul of wit, + And tediousness the limbs and outward flourishes, + I will be brief: your noble son is mad: + Mad call I it; for, to define true madness, + What is't but to be nothing else but mad? + But let that go. + +QUEEN GERTRUDE More matter, with less art. + +LORD POLONIUS Madam, I swear I use no art at all. + That he is mad, 'tis true: 'tis true 'tis pity; + And pity 'tis 'tis true: a foolish figure; + But farewell it, for I will use no art. + Mad let us grant him, then: and now remains + That we find out the cause of this effect, + Or rather say, the cause of this defect, + For this effect defective comes by cause: + Thus it remains, and the remainder thus. Perpend. + I have a daughter--have while she is mine-- + Who, in her duty and obedience, mark, + Hath given me this: now gather, and surmise. + + [Reads] + + 'To the celestial and my soul's idol, the most + beautified Ophelia,'-- + That's an ill phrase, a vile phrase; 'beautified' is + a vile phrase: but you shall hear. Thus: + + [Reads] + + 'In her excellent white bosom, these, &c.' + +QUEEN GERTRUDE Came this from Hamlet to her? + +LORD POLONIUS Good madam, stay awhile; I will be faithful. + + [Reads] + + 'Doubt thou the stars are fire; + Doubt that the sun doth move; + Doubt truth to be a liar; + But never doubt I love. + 'O dear Ophelia, I am ill at these numbers; + I have not art to reckon my groans: but that + I love thee best, O most best, believe it. Adieu. + 'Thine evermore most dear lady, whilst + this machine is to him, HAMLET.' + This, in obedience, hath my daughter shown me, + And more above, hath his solicitings, + As they fell out by time, by means and place, + All given to mine ear. + +KING CLAUDIUS But how hath she + Received his love? + +LORD POLONIUS What do you think of me? + +KING CLAUDIUS As of a man faithful and honourable. + +LORD POLONIUS I would fain prove so. But what might you think, + When I had seen this hot love on the wing-- + As I perceived it, I must tell you that, + Before my daughter told me--what might you, + Or my dear majesty your queen here, think, + If I had play'd the desk or table-book, + Or given my heart a winking, mute and dumb, + Or look'd upon this love with idle sight; + What might you think? No, I went round to work, + And my young mistress thus I did bespeak: + 'Lord Hamlet is a prince, out of thy star; + This must not be:' and then I precepts gave her, + That she should lock herself from his resort, + Admit no messengers, receive no tokens. + Which done, she took the fruits of my advice; + And he, repulsed--a short tale to make-- + Fell into a sadness, then into a fast, + Thence to a watch, thence into a weakness, + Thence to a lightness, and, by this declension, + Into the madness wherein now he raves, + And all we mourn for. + +KING CLAUDIUS Do you think 'tis this? + +QUEEN GERTRUDE It may be, very likely. + +LORD POLONIUS Hath there been such a time--I'd fain know that-- + That I have positively said 'Tis so,' + When it proved otherwise? + +KING CLAUDIUS Not that I know. + +LORD POLONIUS [Pointing to his head and shoulder] + + Take this from this, if this be otherwise: + If circumstances lead me, I will find + Where truth is hid, though it were hid indeed + Within the centre. + +KING CLAUDIUS How may we try it further? + +LORD POLONIUS You know, sometimes he walks four hours together + Here in the lobby. + +QUEEN GERTRUDE So he does indeed. + +LORD POLONIUS At such a time I'll loose my daughter to him: + Be you and I behind an arras then; + Mark the encounter: if he love her not + And be not from his reason fall'n thereon, + Let me be no assistant for a state, + But keep a farm and carters. + +KING CLAUDIUS We will try it. + +QUEEN GERTRUDE But, look, where sadly the poor wretch comes reading. + +LORD POLONIUS Away, I do beseech you, both away: + I'll board him presently. + + [Exeunt KING CLAUDIUS, QUEEN GERTRUDE, and + Attendants] + + [Enter HAMLET, reading] + + O, give me leave: + How does my good Lord Hamlet? + +HAMLET Well, God-a-mercy. + +LORD POLONIUS Do you know me, my lord? + +HAMLET Excellent well; you are a fishmonger. + +LORD POLONIUS Not I, my lord. + +HAMLET Then I would you were so honest a man. + +LORD POLONIUS Honest, my lord! + +HAMLET Ay, sir; to be honest, as this world goes, is to be + one man picked out of ten thousand. + +LORD POLONIUS That's very true, my lord. + +HAMLET For if the sun breed maggots in a dead dog, being a + god kissing carrion,--Have you a daughter? + +LORD POLONIUS I have, my lord. + +HAMLET Let her not walk i' the sun: conception is a + blessing: but not as your daughter may conceive. + Friend, look to 't. + +LORD POLONIUS [Aside] How say you by that? Still harping on my + daughter: yet he knew me not at first; he said I + was a fishmonger: he is far gone, far gone: and + truly in my youth I suffered much extremity for + love; very near this. I'll speak to him again. + What do you read, my lord? + +HAMLET Words, words, words. + +LORD POLONIUS What is the matter, my lord? + +HAMLET Between who? + +LORD POLONIUS I mean, the matter that you read, my lord. + +HAMLET Slanders, sir: for the satirical rogue says here + that old men have grey beards, that their faces are + wrinkled, their eyes purging thick amber and + plum-tree gum and that they have a plentiful lack of + wit, together with most weak hams: all which, sir, + though I most powerfully and potently believe, yet + I hold it not honesty to have it thus set down, for + yourself, sir, should be old as I am, if like a crab + you could go backward. + +LORD POLONIUS [Aside] Though this be madness, yet there is method + in 't. Will you walk out of the air, my lord? + +HAMLET Into my grave. + +LORD POLONIUS Indeed, that is out o' the air. + + [Aside] + + How pregnant sometimes his replies are! a happiness + that often madness hits on, which reason and sanity + could not so prosperously be delivered of. I will + leave him, and suddenly contrive the means of + meeting between him and my daughter.--My honourable + lord, I will most humbly take my leave of you. + +HAMLET You cannot, sir, take from me any thing that I will + more willingly part withal: except my life, except + my life, except my life. + +LORD POLONIUS Fare you well, my lord. + +HAMLET These tedious old fools! + + [Enter ROSENCRANTZ and GUILDENSTERN] + +LORD POLONIUS You go to seek the Lord Hamlet; there he is. + +ROSENCRANTZ [To POLONIUS] God save you, sir! + + [Exit POLONIUS] + +GUILDENSTERN My honoured lord! + +ROSENCRANTZ My most dear lord! + +HAMLET My excellent good friends! How dost thou, + Guildenstern? Ah, Rosencrantz! Good lads, how do ye both? + +ROSENCRANTZ As the indifferent children of the earth. + +GUILDENSTERN Happy, in that we are not over-happy; + On fortune's cap we are not the very button. + +HAMLET Nor the soles of her shoe? + +ROSENCRANTZ Neither, my lord. + +HAMLET Then you live about her waist, or in the middle of + her favours? + +GUILDENSTERN 'Faith, her privates we. + +HAMLET In the secret parts of fortune? O, most true; she + is a strumpet. What's the news? + +ROSENCRANTZ None, my lord, but that the world's grown honest. + +HAMLET Then is doomsday near: but your news is not true. + Let me question more in particular: what have you, + my good friends, deserved at the hands of fortune, + that she sends you to prison hither? + +GUILDENSTERN Prison, my lord! + +HAMLET Denmark's a prison. + +ROSENCRANTZ Then is the world one. + +HAMLET A goodly one; in which there are many confines, + wards and dungeons, Denmark being one o' the worst. + +ROSENCRANTZ We think not so, my lord. + +HAMLET Why, then, 'tis none to you; for there is nothing + either good or bad, but thinking makes it so: to me + it is a prison. + +ROSENCRANTZ Why then, your ambition makes it one; 'tis too + narrow for your mind. + +HAMLET O God, I could be bounded in a nut shell and count + myself a king of infinite space, were it not that I + have bad dreams. + +GUILDENSTERN Which dreams indeed are ambition, for the very + substance of the ambitious is merely the shadow of a dream. + +HAMLET A dream itself is but a shadow. + +ROSENCRANTZ Truly, and I hold ambition of so airy and light a + quality that it is but a shadow's shadow. + +HAMLET Then are our beggars bodies, and our monarchs and + outstretched heroes the beggars' shadows. Shall we + to the court? for, by my fay, I cannot reason. + + +ROSENCRANTZ | + | We'll wait upon you. +GUILDENSTERN | + + +HAMLET No such matter: I will not sort you with the rest + of my servants, for, to speak to you like an honest + man, I am most dreadfully attended. But, in the + beaten way of friendship, what make you at Elsinore? + +ROSENCRANTZ To visit you, my lord; no other occasion. + +HAMLET Beggar that I am, I am even poor in thanks; but I + thank you: and sure, dear friends, my thanks are + too dear a halfpenny. Were you not sent for? Is it + your own inclining? Is it a free visitation? Come, + deal justly with me: come, come; nay, speak. + +GUILDENSTERN What should we say, my lord? + +HAMLET Why, any thing, but to the purpose. You were sent + for; and there is a kind of confession in your looks + which your modesties have not craft enough to colour: + I know the good king and queen have sent for you. + +ROSENCRANTZ To what end, my lord? + +HAMLET That you must teach me. But let me conjure you, by + the rights of our fellowship, by the consonancy of + our youth, by the obligation of our ever-preserved + love, and by what more dear a better proposer could + charge you withal, be even and direct with me, + whether you were sent for, or no? + +ROSENCRANTZ [Aside to GUILDENSTERN] What say you? + +HAMLET [Aside] Nay, then, I have an eye of you.--If you + love me, hold not off. + +GUILDENSTERN My lord, we were sent for. + +HAMLET I will tell you why; so shall my anticipation + prevent your discovery, and your secrecy to the king + and queen moult no feather. I have of late--but + wherefore I know not--lost all my mirth, forgone all + custom of exercises; and indeed it goes so heavily + with my disposition that this goodly frame, the + earth, seems to me a sterile promontory, this most + excellent canopy, the air, look you, this brave + o'erhanging firmament, this majestical roof fretted + with golden fire, why, it appears no other thing to + me than a foul and pestilent congregation of vapours. + What a piece of work is a man! how noble in reason! + how infinite in faculty! in form and moving how + express and admirable! in action how like an angel! + in apprehension how like a god! the beauty of the + world! the paragon of animals! And yet, to me, + what is this quintessence of dust? man delights not + me: no, nor woman neither, though by your smiling + you seem to say so. + +ROSENCRANTZ My lord, there was no such stuff in my thoughts. + +HAMLET Why did you laugh then, when I said 'man delights not me'? + +ROSENCRANTZ To think, my lord, if you delight not in man, what + lenten entertainment the players shall receive from + you: we coted them on the way; and hither are they + coming, to offer you service. + +HAMLET He that plays the king shall be welcome; his majesty + shall have tribute of me; the adventurous knight + shall use his foil and target; the lover shall not + sigh gratis; the humourous man shall end his part + in peace; the clown shall make those laugh whose + lungs are tickled o' the sere; and the lady shall + say her mind freely, or the blank verse shall halt + for't. What players are they? + +ROSENCRANTZ Even those you were wont to take delight in, the + tragedians of the city. + +HAMLET How chances it they travel? their residence, both + in reputation and profit, was better both ways. + +ROSENCRANTZ I think their inhibition comes by the means of the + late innovation. + +HAMLET Do they hold the same estimation they did when I was + in the city? are they so followed? + +ROSENCRANTZ No, indeed, are they not. + +HAMLET How comes it? do they grow rusty? + +ROSENCRANTZ Nay, their endeavour keeps in the wonted pace: but + there is, sir, an aery of children, little eyases, + that cry out on the top of question, and are most + tyrannically clapped for't: these are now the + fashion, and so berattle the common stages--so they + call them--that many wearing rapiers are afraid of + goose-quills and dare scarce come thither. + +HAMLET What, are they children? who maintains 'em? how are + they escoted? Will they pursue the quality no + longer than they can sing? will they not say + afterwards, if they should grow themselves to common + players--as it is most like, if their means are no + better--their writers do them wrong, to make them + exclaim against their own succession? + +ROSENCRANTZ 'Faith, there has been much to do on both sides; and + the nation holds it no sin to tarre them to + controversy: there was, for a while, no money bid + for argument, unless the poet and the player went to + cuffs in the question. + +HAMLET Is't possible? + +GUILDENSTERN O, there has been much throwing about of brains. + +HAMLET Do the boys carry it away? + +ROSENCRANTZ Ay, that they do, my lord; Hercules and his load too. + +HAMLET It is not very strange; for mine uncle is king of + Denmark, and those that would make mows at him while + my father lived, give twenty, forty, fifty, an + hundred ducats a-piece for his picture in little. + 'Sblood, there is something in this more than + natural, if philosophy could find it out. + + [Flourish of trumpets within] + +GUILDENSTERN There are the players. + +HAMLET Gentlemen, you are welcome to Elsinore. Your hands, + come then: the appurtenance of welcome is fashion + and ceremony: let me comply with you in this garb, + lest my extent to the players, which, I tell you, + must show fairly outward, should more appear like + entertainment than yours. You are welcome: but my + uncle-father and aunt-mother are deceived. + +GUILDENSTERN In what, my dear lord? + +HAMLET I am but mad north-north-west: when the wind is + southerly I know a hawk from a handsaw. + + [Enter POLONIUS] + +LORD POLONIUS Well be with you, gentlemen! + +HAMLET Hark you, Guildenstern; and you too: at each ear a + hearer: that great baby you see there is not yet + out of his swaddling-clouts. + +ROSENCRANTZ Happily he's the second time come to them; for they + say an old man is twice a child. + +HAMLET I will prophesy he comes to tell me of the players; + mark it. You say right, sir: o' Monday morning; + 'twas so indeed. + +LORD POLONIUS My lord, I have news to tell you. + +HAMLET My lord, I have news to tell you. + When Roscius was an actor in Rome,-- + +LORD POLONIUS The actors are come hither, my lord. + +HAMLET Buz, buz! + +LORD POLONIUS Upon mine honour,-- + +HAMLET Then came each actor on his ass,-- + +LORD POLONIUS The best actors in the world, either for tragedy, + comedy, history, pastoral, pastoral-comical, + historical-pastoral, tragical-historical, tragical- + comical-historical-pastoral, scene individable, or + poem unlimited: Seneca cannot be too heavy, nor + Plautus too light. For the law of writ and the + liberty, these are the only men. + +HAMLET O Jephthah, judge of Israel, what a treasure hadst thou! + +LORD POLONIUS What a treasure had he, my lord? + +HAMLET Why, + 'One fair daughter and no more, + The which he loved passing well.' + +LORD POLONIUS [Aside] Still on my daughter. + +HAMLET Am I not i' the right, old Jephthah? + +LORD POLONIUS If you call me Jephthah, my lord, I have a daughter + that I love passing well. + +HAMLET Nay, that follows not. + +LORD POLONIUS What follows, then, my lord? + +HAMLET Why, + 'As by lot, God wot,' + and then, you know, + 'It came to pass, as most like it was,'-- + the first row of the pious chanson will show you + more; for look, where my abridgement comes. + + [Enter four or five Players] + + You are welcome, masters; welcome, all. I am glad + to see thee well. Welcome, good friends. O, my old + friend! thy face is valenced since I saw thee last: + comest thou to beard me in Denmark? What, my young + lady and mistress! By'r lady, your ladyship is + nearer to heaven than when I saw you last, by the + altitude of a chopine. Pray God, your voice, like + apiece of uncurrent gold, be not cracked within the + ring. Masters, you are all welcome. We'll e'en + to't like French falconers, fly at any thing we see: + we'll have a speech straight: come, give us a taste + of your quality; come, a passionate speech. + +First Player What speech, my lord? + +HAMLET I heard thee speak me a speech once, but it was + never acted; or, if it was, not above once; for the + play, I remember, pleased not the million; 'twas + caviare to the general: but it was--as I received + it, and others, whose judgments in such matters + cried in the top of mine--an excellent play, well + digested in the scenes, set down with as much + modesty as cunning. I remember, one said there + were no sallets in the lines to make the matter + savoury, nor no matter in the phrase that might + indict the author of affectation; but called it an + honest method, as wholesome as sweet, and by very + much more handsome than fine. One speech in it I + chiefly loved: 'twas Aeneas' tale to Dido; and + thereabout of it especially, where he speaks of + Priam's slaughter: if it live in your memory, begin + at this line: let me see, let me see-- + 'The rugged Pyrrhus, like the Hyrcanian beast,'-- + it is not so:--it begins with Pyrrhus:-- + 'The rugged Pyrrhus, he whose sable arms, + Black as his purpose, did the night resemble + When he lay couched in the ominous horse, + Hath now this dread and black complexion smear'd + With heraldry more dismal; head to foot + Now is he total gules; horridly trick'd + With blood of fathers, mothers, daughters, sons, + Baked and impasted with the parching streets, + That lend a tyrannous and damned light + To their lord's murder: roasted in wrath and fire, + And thus o'er-sized with coagulate gore, + With eyes like carbuncles, the hellish Pyrrhus + Old grandsire Priam seeks.' + So, proceed you. + +LORD POLONIUS 'Fore God, my lord, well spoken, with good accent and + good discretion. + +First Player 'Anon he finds him + Striking too short at Greeks; his antique sword, + Rebellious to his arm, lies where it falls, + Repugnant to command: unequal match'd, + Pyrrhus at Priam drives; in rage strikes wide; + But with the whiff and wind of his fell sword + The unnerved father falls. Then senseless Ilium, + Seeming to feel this blow, with flaming top + Stoops to his base, and with a hideous crash + Takes prisoner Pyrrhus' ear: for, lo! his sword, + Which was declining on the milky head + Of reverend Priam, seem'd i' the air to stick: + So, as a painted tyrant, Pyrrhus stood, + And like a neutral to his will and matter, + Did nothing. + But, as we often see, against some storm, + A silence in the heavens, the rack stand still, + The bold winds speechless and the orb below + As hush as death, anon the dreadful thunder + Doth rend the region, so, after Pyrrhus' pause, + Aroused vengeance sets him new a-work; + And never did the Cyclops' hammers fall + On Mars's armour forged for proof eterne + With less remorse than Pyrrhus' bleeding sword + Now falls on Priam. + Out, out, thou strumpet, Fortune! All you gods, + In general synod 'take away her power; + Break all the spokes and fellies from her wheel, + And bowl the round nave down the hill of heaven, + As low as to the fiends!' + +LORD POLONIUS This is too long. + +HAMLET It shall to the barber's, with your beard. Prithee, + say on: he's for a jig or a tale of bawdry, or he + sleeps: say on: come to Hecuba. + +First Player 'But who, O, who had seen the mobled queen--' + +HAMLET 'The mobled queen?' + +LORD POLONIUS That's good; 'mobled queen' is good. + +First Player 'Run barefoot up and down, threatening the flames + With bisson rheum; a clout upon that head + Where late the diadem stood, and for a robe, + About her lank and all o'er-teemed loins, + A blanket, in the alarm of fear caught up; + Who this had seen, with tongue in venom steep'd, + 'Gainst Fortune's state would treason have + pronounced: + But if the gods themselves did see her then + When she saw Pyrrhus make malicious sport + In mincing with his sword her husband's limbs, + The instant burst of clamour that she made, + Unless things mortal move them not at all, + Would have made milch the burning eyes of heaven, + And passion in the gods.' + +LORD POLONIUS Look, whether he has not turned his colour and has + tears in's eyes. Pray you, no more. + +HAMLET 'Tis well: I'll have thee speak out the rest soon. + Good my lord, will you see the players well + bestowed? Do you hear, let them be well used; for + they are the abstract and brief chronicles of the + time: after your death you were better have a bad + epitaph than their ill report while you live. + +LORD POLONIUS My lord, I will use them according to their desert. + +HAMLET God's bodykins, man, much better: use every man + after his desert, and who should 'scape whipping? + Use them after your own honour and dignity: the less + they deserve, the more merit is in your bounty. + Take them in. + +LORD POLONIUS Come, sirs. + +HAMLET Follow him, friends: we'll hear a play to-morrow. + + [Exit POLONIUS with all the Players but the First] + + Dost thou hear me, old friend; can you play the + Murder of Gonzago? + +First Player Ay, my lord. + +HAMLET We'll ha't to-morrow night. You could, for a need, + study a speech of some dozen or sixteen lines, which + I would set down and insert in't, could you not? + +First Player Ay, my lord. + +HAMLET Very well. Follow that lord; and look you mock him + not. + + [Exit First Player] + + My good friends, I'll leave you till night: you are + welcome to Elsinore. + +ROSENCRANTZ Good my lord! + +HAMLET Ay, so, God be wi' ye; + + [Exeunt ROSENCRANTZ and GUILDENSTERN] + + Now I am alone. + O, what a rogue and peasant slave am I! + Is it not monstrous that this player here, + But in a fiction, in a dream of passion, + Could force his soul so to his own conceit + That from her working all his visage wann'd, + Tears in his eyes, distraction in's aspect, + A broken voice, and his whole function suiting + With forms to his conceit? and all for nothing! + For Hecuba! + What's Hecuba to him, or he to Hecuba, + That he should weep for her? What would he do, + Had he the motive and the cue for passion + That I have? He would drown the stage with tears + And cleave the general ear with horrid speech, + Make mad the guilty and appal the free, + Confound the ignorant, and amaze indeed + The very faculties of eyes and ears. Yet I, + A dull and muddy-mettled rascal, peak, + Like John-a-dreams, unpregnant of my cause, + And can say nothing; no, not for a king, + Upon whose property and most dear life + A damn'd defeat was made. Am I a coward? + Who calls me villain? breaks my pate across? + Plucks off my beard, and blows it in my face? + Tweaks me by the nose? gives me the lie i' the throat, + As deep as to the lungs? who does me this? + Ha! + 'Swounds, I should take it: for it cannot be + But I am pigeon-liver'd and lack gall + To make oppression bitter, or ere this + I should have fatted all the region kites + With this slave's offal: bloody, bawdy villain! + Remorseless, treacherous, lecherous, kindless villain! + O, vengeance! + Why, what an ass am I! This is most brave, + That I, the son of a dear father murder'd, + Prompted to my revenge by heaven and hell, + Must, like a whore, unpack my heart with words, + And fall a-cursing, like a very drab, + A scullion! + Fie upon't! foh! About, my brain! I have heard + That guilty creatures sitting at a play + Have by the very cunning of the scene + Been struck so to the soul that presently + They have proclaim'd their malefactions; + For murder, though it have no tongue, will speak + With most miraculous organ. I'll have these players + Play something like the murder of my father + Before mine uncle: I'll observe his looks; + I'll tent him to the quick: if he but blench, + I know my course. The spirit that I have seen + May be the devil: and the devil hath power + To assume a pleasing shape; yea, and perhaps + Out of my weakness and my melancholy, + As he is very potent with such spirits, + Abuses me to damn me: I'll have grounds + More relative than this: the play 's the thing + Wherein I'll catch the conscience of the king. + + [Exit] + + + + + HAMLET + + +ACT III + + + +SCENE I A room in the castle. + + + [Enter KING CLAUDIUS, QUEEN GERTRUDE, POLONIUS, + OPHELIA, ROSENCRANTZ, and GUILDENSTERN] + +KING CLAUDIUS And can you, by no drift of circumstance, + Get from him why he puts on this confusion, + Grating so harshly all his days of quiet + With turbulent and dangerous lunacy? + +ROSENCRANTZ He does confess he feels himself distracted; + But from what cause he will by no means speak. + +GUILDENSTERN Nor do we find him forward to be sounded, + But, with a crafty madness, keeps aloof, + When we would bring him on to some confession + Of his true state. + +QUEEN GERTRUDE Did he receive you well? + +ROSENCRANTZ Most like a gentleman. + +GUILDENSTERN But with much forcing of his disposition. + +ROSENCRANTZ Niggard of question; but, of our demands, + Most free in his reply. + +QUEEN GERTRUDE Did you assay him? + To any pastime? + +ROSENCRANTZ Madam, it so fell out, that certain players + We o'er-raught on the way: of these we told him; + And there did seem in him a kind of joy + To hear of it: they are about the court, + And, as I think, they have already order + This night to play before him. + +LORD POLONIUS 'Tis most true: + And he beseech'd me to entreat your majesties + To hear and see the matter. + +KING CLAUDIUS With all my heart; and it doth much content me + To hear him so inclined. + Good gentlemen, give him a further edge, + And drive his purpose on to these delights. + +ROSENCRANTZ We shall, my lord. + + [Exeunt ROSENCRANTZ and GUILDENSTERN] + +KING CLAUDIUS Sweet Gertrude, leave us too; + For we have closely sent for Hamlet hither, + That he, as 'twere by accident, may here + Affront Ophelia: + Her father and myself, lawful espials, + Will so bestow ourselves that, seeing, unseen, + We may of their encounter frankly judge, + And gather by him, as he is behaved, + If 't be the affliction of his love or no + That thus he suffers for. + +QUEEN GERTRUDE I shall obey you. + And for your part, Ophelia, I do wish + That your good beauties be the happy cause + Of Hamlet's wildness: so shall I hope your virtues + Will bring him to his wonted way again, + To both your honours. + +OPHELIA Madam, I wish it may. + + [Exit QUEEN GERTRUDE] + +LORD POLONIUS Ophelia, walk you here. Gracious, so please you, + We will bestow ourselves. + + [To OPHELIA] + + Read on this book; + That show of such an exercise may colour + Your loneliness. We are oft to blame in this,-- + 'Tis too much proved--that with devotion's visage + And pious action we do sugar o'er + The devil himself. + +KING CLAUDIUS [Aside] O, 'tis too true! + How smart a lash that speech doth give my conscience! + The harlot's cheek, beautied with plastering art, + Is not more ugly to the thing that helps it + Than is my deed to my most painted word: + O heavy burthen! + +LORD POLONIUS I hear him coming: let's withdraw, my lord. + + [Exeunt KING CLAUDIUS and POLONIUS] + + [Enter HAMLET] + +HAMLET To be, or not to be: that is the question: + Whether 'tis nobler in the mind to suffer + The slings and arrows of outrageous fortune, + Or to take arms against a sea of troubles, + And by opposing end them? To die: to sleep; + No more; and by a sleep to say we end + The heart-ache and the thousand natural shocks + That flesh is heir to, 'tis a consummation + Devoutly to be wish'd. To die, to sleep; + To sleep: perchance to dream: ay, there's the rub; + For in that sleep of death what dreams may come + When we have shuffled off this mortal coil, + Must give us pause: there's the respect + That makes calamity of so long life; + For who would bear the whips and scorns of time, + The oppressor's wrong, the proud man's contumely, + The pangs of despised love, the law's delay, + The insolence of office and the spurns + That patient merit of the unworthy takes, + When he himself might his quietus make + With a bare bodkin? who would fardels bear, + To grunt and sweat under a weary life, + But that the dread of something after death, + The undiscover'd country from whose bourn + No traveller returns, puzzles the will + And makes us rather bear those ills we have + Than fly to others that we know not of? + Thus conscience does make cowards of us all; + And thus the native hue of resolution + Is sicklied o'er with the pale cast of thought, + And enterprises of great pith and moment + With this regard their currents turn awry, + And lose the name of action.--Soft you now! + The fair Ophelia! Nymph, in thy orisons + Be all my sins remember'd. + +OPHELIA Good my lord, + How does your honour for this many a day? + +HAMLET I humbly thank you; well, well, well. + +OPHELIA My lord, I have remembrances of yours, + That I have longed long to re-deliver; + I pray you, now receive them. + +HAMLET No, not I; + I never gave you aught. + +OPHELIA My honour'd lord, you know right well you did; + And, with them, words of so sweet breath composed + As made the things more rich: their perfume lost, + Take these again; for to the noble mind + Rich gifts wax poor when givers prove unkind. + There, my lord. + +HAMLET Ha, ha! are you honest? + +OPHELIA My lord? + +HAMLET Are you fair? + +OPHELIA What means your lordship? + +HAMLET That if you be honest and fair, your honesty should + admit no discourse to your beauty. + +OPHELIA Could beauty, my lord, have better commerce than + with honesty? + +HAMLET Ay, truly; for the power of beauty will sooner + transform honesty from what it is to a bawd than the + force of honesty can translate beauty into his + likeness: this was sometime a paradox, but now the + time gives it proof. I did love you once. + +OPHELIA Indeed, my lord, you made me believe so. + +HAMLET You should not have believed me; for virtue cannot + so inoculate our old stock but we shall relish of + it: I loved you not. + +OPHELIA I was the more deceived. + +HAMLET Get thee to a nunnery: why wouldst thou be a + breeder of sinners? I am myself indifferent honest; + but yet I could accuse me of such things that it + were better my mother had not borne me: I am very + proud, revengeful, ambitious, with more offences at + my beck than I have thoughts to put them in, + imagination to give them shape, or time to act them + in. What should such fellows as I do crawling + between earth and heaven? We are arrant knaves, + all; believe none of us. Go thy ways to a nunnery. + Where's your father? + +OPHELIA At home, my lord. + +HAMLET Let the doors be shut upon him, that he may play the + fool no where but in's own house. Farewell. + +OPHELIA O, help him, you sweet heavens! + +HAMLET If thou dost marry, I'll give thee this plague for + thy dowry: be thou as chaste as ice, as pure as + snow, thou shalt not escape calumny. Get thee to a + nunnery, go: farewell. Or, if thou wilt needs + marry, marry a fool; for wise men know well enough + what monsters you make of them. To a nunnery, go, + and quickly too. Farewell. + +OPHELIA O heavenly powers, restore him! + +HAMLET I have heard of your paintings too, well enough; God + has given you one face, and you make yourselves + another: you jig, you amble, and you lisp, and + nick-name God's creatures, and make your wantonness + your ignorance. Go to, I'll no more on't; it hath + made me mad. I say, we will have no more marriages: + those that are married already, all but one, shall + live; the rest shall keep as they are. To a + nunnery, go. + + [Exit] + +OPHELIA O, what a noble mind is here o'erthrown! + The courtier's, soldier's, scholar's, eye, tongue, sword; + The expectancy and rose of the fair state, + The glass of fashion and the mould of form, + The observed of all observers, quite, quite down! + And I, of ladies most deject and wretched, + That suck'd the honey of his music vows, + Now see that noble and most sovereign reason, + Like sweet bells jangled, out of tune and harsh; + That unmatch'd form and feature of blown youth + Blasted with ecstasy: O, woe is me, + To have seen what I have seen, see what I see! + + [Re-enter KING CLAUDIUS and POLONIUS] + +KING CLAUDIUS Love! his affections do not that way tend; + Nor what he spake, though it lack'd form a little, + Was not like madness. There's something in his soul, + O'er which his melancholy sits on brood; + And I do doubt the hatch and the disclose + Will be some danger: which for to prevent, + I have in quick determination + Thus set it down: he shall with speed to England, + For the demand of our neglected tribute + Haply the seas and countries different + With variable objects shall expel + This something-settled matter in his heart, + Whereon his brains still beating puts him thus + From fashion of himself. What think you on't? + +LORD POLONIUS It shall do well: but yet do I believe + The origin and commencement of his grief + Sprung from neglected love. How now, Ophelia! + You need not tell us what Lord Hamlet said; + We heard it all. My lord, do as you please; + But, if you hold it fit, after the play + Let his queen mother all alone entreat him + To show his grief: let her be round with him; + And I'll be placed, so please you, in the ear + Of all their conference. If she find him not, + To England send him, or confine him where + Your wisdom best shall think. + +KING CLAUDIUS It shall be so: + Madness in great ones must not unwatch'd go. + + [Exeunt] + + + + + HAMLET + + +ACT III + + + +SCENE II A hall in the castle. + + + [Enter HAMLET and Players] + +HAMLET Speak the speech, I pray you, as I pronounced it to + you, trippingly on the tongue: but if you mouth it, + as many of your players do, I had as lief the + town-crier spoke my lines. Nor do not saw the air + too much with your hand, thus, but use all gently; + for in the very torrent, tempest, and, as I may say, + the whirlwind of passion, you must acquire and beget + a temperance that may give it smoothness. O, it + offends me to the soul to hear a robustious + periwig-pated fellow tear a passion to tatters, to + very rags, to split the ears of the groundlings, who + for the most part are capable of nothing but + inexplicable dumbshows and noise: I would have such + a fellow whipped for o'erdoing Termagant; it + out-herods Herod: pray you, avoid it. + +First Player I warrant your honour. + +HAMLET Be not too tame neither, but let your own discretion + be your tutor: suit the action to the word, the + word to the action; with this special observance, + that you o'erstep not the modesty of nature: for any + thing so overdone is from the purpose of playing, whose + end, both at the first and now, was and is, to hold, as + 'twere, the mirror up to nature; to show virtue her own + feature, scorn her own image, and the very age and body + of the time his form and pressure. Now this overdone, + or come tardy off, though it make the unskilful + laugh, cannot but make the judicious grieve; the + censure of the which one must in your allowance + o'erweigh a whole theatre of others. O, there be + players that I have seen play, and heard others + praise, and that highly, not to speak it profanely, + that, neither having the accent of Christians nor + the gait of Christian, pagan, nor man, have so + strutted and bellowed that I have thought some of + nature's journeymen had made men and not made them + well, they imitated humanity so abominably. + +First Player I hope we have reformed that indifferently with us, + sir. + +HAMLET O, reform it altogether. And let those that play + your clowns speak no more than is set down for them; + for there be of them that will themselves laugh, to + set on some quantity of barren spectators to laugh + too; though, in the mean time, some necessary + question of the play be then to be considered: + that's villanous, and shows a most pitiful ambition + in the fool that uses it. Go, make you ready. + + [Exeunt Players] + + [Enter POLONIUS, ROSENCRANTZ, and GUILDENSTERN] + + How now, my lord! I will the king hear this piece of work? + +LORD POLONIUS And the queen too, and that presently. + +HAMLET Bid the players make haste. + + [Exit POLONIUS] + + Will you two help to hasten them? + + +ROSENCRANTZ | + | We will, my lord. +GUILDENSTERN | + + + [Exeunt ROSENCRANTZ and GUILDENSTERN] + +HAMLET What ho! Horatio! + + [Enter HORATIO] + +HORATIO Here, sweet lord, at your service. + +HAMLET Horatio, thou art e'en as just a man + As e'er my conversation coped withal. + +HORATIO O, my dear lord,-- + +HAMLET Nay, do not think I flatter; + For what advancement may I hope from thee + That no revenue hast but thy good spirits, + To feed and clothe thee? Why should the poor be flatter'd? + No, let the candied tongue lick absurd pomp, + And crook the pregnant hinges of the knee + Where thrift may follow fawning. Dost thou hear? + Since my dear soul was mistress of her choice + And could of men distinguish, her election + Hath seal'd thee for herself; for thou hast been + As one, in suffering all, that suffers nothing, + A man that fortune's buffets and rewards + Hast ta'en with equal thanks: and blest are those + Whose blood and judgment are so well commingled, + That they are not a pipe for fortune's finger + To sound what stop she please. Give me that man + That is not passion's slave, and I will wear him + In my heart's core, ay, in my heart of heart, + As I do thee.--Something too much of this.-- + There is a play to-night before the king; + One scene of it comes near the circumstance + Which I have told thee of my father's death: + I prithee, when thou seest that act afoot, + Even with the very comment of thy soul + Observe mine uncle: if his occulted guilt + Do not itself unkennel in one speech, + It is a damned ghost that we have seen, + And my imaginations are as foul + As Vulcan's stithy. Give him heedful note; + For I mine eyes will rivet to his face, + And after we will both our judgments join + In censure of his seeming. + +HORATIO Well, my lord: + If he steal aught the whilst this play is playing, + And 'scape detecting, I will pay the theft. + +HAMLET They are coming to the play; I must be idle: + Get you a place. + + [Danish march. A flourish. Enter KING CLAUDIUS, + QUEEN GERTRUDE, POLONIUS, OPHELIA, ROSENCRANTZ, + GUILDENSTERN, and others] + +KING CLAUDIUS How fares our cousin Hamlet? + +HAMLET Excellent, i' faith; of the chameleon's dish: I eat + the air, promise-crammed: you cannot feed capons so. + +KING CLAUDIUS I have nothing with this answer, Hamlet; these words + are not mine. + +HAMLET No, nor mine now. + + [To POLONIUS] + + My lord, you played once i' the university, you say? + +LORD POLONIUS That did I, my lord; and was accounted a good actor. + +HAMLET What did you enact? + +LORD POLONIUS I did enact Julius Caesar: I was killed i' the + Capitol; Brutus killed me. + +HAMLET It was a brute part of him to kill so capital a calf + there. Be the players ready? + +ROSENCRANTZ Ay, my lord; they stay upon your patience. + +QUEEN GERTRUDE Come hither, my dear Hamlet, sit by me. + +HAMLET No, good mother, here's metal more attractive. + +LORD POLONIUS [To KING CLAUDIUS] O, ho! do you mark that? + +HAMLET Lady, shall I lie in your lap? + + [Lying down at OPHELIA's feet] + +OPHELIA No, my lord. + +HAMLET I mean, my head upon your lap? + +OPHELIA Ay, my lord. + +HAMLET Do you think I meant country matters? + +OPHELIA I think nothing, my lord. + +HAMLET That's a fair thought to lie between maids' legs. + +OPHELIA What is, my lord? + +HAMLET Nothing. + +OPHELIA You are merry, my lord. + +HAMLET Who, I? + +OPHELIA Ay, my lord. + +HAMLET O God, your only jig-maker. What should a man do + but be merry? for, look you, how cheerfully my + mother looks, and my father died within these two hours. + +OPHELIA Nay, 'tis twice two months, my lord. + +HAMLET So long? Nay then, let the devil wear black, for + I'll have a suit of sables. O heavens! die two + months ago, and not forgotten yet? Then there's + hope a great man's memory may outlive his life half + a year: but, by'r lady, he must build churches, + then; or else shall he suffer not thinking on, with + the hobby-horse, whose epitaph is 'For, O, for, O, + the hobby-horse is forgot.' + + [Hautboys play. The dumb-show enters] + + [Enter a King and a Queen very lovingly; the Queen + embracing him, and he her. She kneels, and makes + show of protestation unto him. He takes her up, + and declines his head upon her neck: lays him down + upon a bank of flowers: she, seeing him asleep, + leaves him. Anon comes in a fellow, takes off his + crown, kisses it, and pours poison in the King's + ears, and exit. The Queen returns; finds the King + dead, and makes passionate action. The Poisoner, + with some two or three Mutes, comes in again, + seeming to lament with her. The dead body is + carried away. The Poisoner wooes the Queen with + gifts: she seems loath and unwilling awhile, but + in the end accepts his love] + + [Exeunt] + +OPHELIA What means this, my lord? + +HAMLET Marry, this is miching mallecho; it means mischief. + +OPHELIA Belike this show imports the argument of the play. + + [Enter Prologue] + +HAMLET We shall know by this fellow: the players cannot + keep counsel; they'll tell all. + +OPHELIA Will he tell us what this show meant? + +HAMLET Ay, or any show that you'll show him: be not you + ashamed to show, he'll not shame to tell you what it means. + +OPHELIA You are naught, you are naught: I'll mark the play. + +Prologue For us, and for our tragedy, + Here stooping to your clemency, + We beg your hearing patiently. + + [Exit] + +HAMLET Is this a prologue, or the posy of a ring? + +OPHELIA 'Tis brief, my lord. + +HAMLET As woman's love. + + [Enter two Players, King and Queen] + +Player King Full thirty times hath Phoebus' cart gone round + Neptune's salt wash and Tellus' orbed ground, + And thirty dozen moons with borrow'd sheen + About the world have times twelve thirties been, + Since love our hearts and Hymen did our hands + Unite commutual in most sacred bands. + +Player Queen So many journeys may the sun and moon + Make us again count o'er ere love be done! + But, woe is me, you are so sick of late, + So far from cheer and from your former state, + That I distrust you. Yet, though I distrust, + Discomfort you, my lord, it nothing must: + For women's fear and love holds quantity; + In neither aught, or in extremity. + Now, what my love is, proof hath made you know; + And as my love is sized, my fear is so: + Where love is great, the littlest doubts are fear; + Where little fears grow great, great love grows there. + +Player King 'Faith, I must leave thee, love, and shortly too; + My operant powers their functions leave to do: + And thou shalt live in this fair world behind, + Honour'd, beloved; and haply one as kind + For husband shalt thou-- + +Player Queen O, confound the rest! + Such love must needs be treason in my breast: + In second husband let me be accurst! + None wed the second but who kill'd the first. + +HAMLET [Aside] Wormwood, wormwood. + +Player Queen The instances that second marriage move + Are base respects of thrift, but none of love: + A second time I kill my husband dead, + When second husband kisses me in bed. + +Player King I do believe you think what now you speak; + But what we do determine oft we break. + Purpose is but the slave to memory, + Of violent birth, but poor validity; + Which now, like fruit unripe, sticks on the tree; + But fall, unshaken, when they mellow be. + Most necessary 'tis that we forget + To pay ourselves what to ourselves is debt: + What to ourselves in passion we propose, + The passion ending, doth the purpose lose. + The violence of either grief or joy + Their own enactures with themselves destroy: + Where joy most revels, grief doth most lament; + Grief joys, joy grieves, on slender accident. + This world is not for aye, nor 'tis not strange + That even our loves should with our fortunes change; + For 'tis a question left us yet to prove, + Whether love lead fortune, or else fortune love. + The great man down, you mark his favourite flies; + The poor advanced makes friends of enemies. + And hitherto doth love on fortune tend; + For who not needs shall never lack a friend, + And who in want a hollow friend doth try, + Directly seasons him his enemy. + But, orderly to end where I begun, + Our wills and fates do so contrary run + That our devices still are overthrown; + Our thoughts are ours, their ends none of our own: + So think thou wilt no second husband wed; + But die thy thoughts when thy first lord is dead. + +Player Queen Nor earth to me give food, nor heaven light! + Sport and repose lock from me day and night! + To desperation turn my trust and hope! + An anchor's cheer in prison be my scope! + Each opposite that blanks the face of joy + Meet what I would have well and it destroy! + Both here and hence pursue me lasting strife, + If, once a widow, ever I be wife! + +HAMLET If she should break it now! + +Player King 'Tis deeply sworn. Sweet, leave me here awhile; + My spirits grow dull, and fain I would beguile + The tedious day with sleep. + + [Sleeps] + +Player Queen Sleep rock thy brain, + And never come mischance between us twain! + + [Exit] + +HAMLET Madam, how like you this play? + +QUEEN GERTRUDE The lady protests too much, methinks. + +HAMLET O, but she'll keep her word. + +KING CLAUDIUS Have you heard the argument? Is there no offence in 't? + +HAMLET No, no, they do but jest, poison in jest; no offence + i' the world. + +KING CLAUDIUS What do you call the play? + +HAMLET The Mouse-trap. Marry, how? Tropically. This play + is the image of a murder done in Vienna: Gonzago is + the duke's name; his wife, Baptista: you shall see + anon; 'tis a knavish piece of work: but what o' + that? your majesty and we that have free souls, it + touches us not: let the galled jade wince, our + withers are unwrung. + + [Enter LUCIANUS] + + This is one Lucianus, nephew to the king. + +OPHELIA You are as good as a chorus, my lord. + +HAMLET I could interpret between you and your love, if I + could see the puppets dallying. + +OPHELIA You are keen, my lord, you are keen. + +HAMLET It would cost you a groaning to take off my edge. + +OPHELIA Still better, and worse. + +HAMLET So you must take your husbands. Begin, murderer; + pox, leave thy damnable faces, and begin. Come: + 'the croaking raven doth bellow for revenge.' + +LUCIANUS Thoughts black, hands apt, drugs fit, and time agreeing; + Confederate season, else no creature seeing; + Thou mixture rank, of midnight weeds collected, + With Hecate's ban thrice blasted, thrice infected, + Thy natural magic and dire property, + On wholesome life usurp immediately. + + [Pours the poison into the sleeper's ears] + +HAMLET He poisons him i' the garden for's estate. His + name's Gonzago: the story is extant, and writ in + choice Italian: you shall see anon how the murderer + gets the love of Gonzago's wife. + +OPHELIA The king rises. + +HAMLET What, frighted with false fire! + +QUEEN GERTRUDE How fares my lord? + +LORD POLONIUS Give o'er the play. + +KING CLAUDIUS Give me some light: away! + +All Lights, lights, lights! + + [Exeunt all but HAMLET and HORATIO] + +HAMLET Why, let the stricken deer go weep, + The hart ungalled play; + For some must watch, while some must sleep: + So runs the world away. + Would not this, sir, and a forest of feathers-- if + the rest of my fortunes turn Turk with me--with two + Provincial roses on my razed shoes, get me a + fellowship in a cry of players, sir? + +HORATIO Half a share. + +HAMLET A whole one, I. + For thou dost know, O Damon dear, + This realm dismantled was + Of Jove himself; and now reigns here + A very, very--pajock. + +HORATIO You might have rhymed. + +HAMLET O good Horatio, I'll take the ghost's word for a + thousand pound. Didst perceive? + +HORATIO Very well, my lord. + +HAMLET Upon the talk of the poisoning? + +HORATIO I did very well note him. + +HAMLET Ah, ha! Come, some music! come, the recorders! + For if the king like not the comedy, + Why then, belike, he likes it not, perdy. + Come, some music! + + [Re-enter ROSENCRANTZ and GUILDENSTERN] + +GUILDENSTERN Good my lord, vouchsafe me a word with you. + +HAMLET Sir, a whole history. + +GUILDENSTERN The king, sir,-- + +HAMLET Ay, sir, what of him? + +GUILDENSTERN Is in his retirement marvellous distempered. + +HAMLET With drink, sir? + +GUILDENSTERN No, my lord, rather with choler. + +HAMLET Your wisdom should show itself more richer to + signify this to his doctor; for, for me to put him + to his purgation would perhaps plunge him into far + more choler. + +GUILDENSTERN Good my lord, put your discourse into some frame and + start not so wildly from my affair. + +HAMLET I am tame, sir: pronounce. + +GUILDENSTERN The queen, your mother, in most great affliction of + spirit, hath sent me to you. + +HAMLET You are welcome. + +GUILDENSTERN Nay, good my lord, this courtesy is not of the right + breed. If it shall please you to make me a + wholesome answer, I will do your mother's + commandment: if not, your pardon and my return + shall be the end of my business. + +HAMLET Sir, I cannot. + +GUILDENSTERN What, my lord? + +HAMLET Make you a wholesome answer; my wit's diseased: but, + sir, such answer as I can make, you shall command; + or, rather, as you say, my mother: therefore no + more, but to the matter: my mother, you say,-- + +ROSENCRANTZ Then thus she says; your behavior hath struck her + into amazement and admiration. + +HAMLET O wonderful son, that can so astonish a mother! But + is there no sequel at the heels of this mother's + admiration? Impart. + +ROSENCRANTZ She desires to speak with you in her closet, ere you + go to bed. + +HAMLET We shall obey, were she ten times our mother. Have + you any further trade with us? + +ROSENCRANTZ My lord, you once did love me. + +HAMLET So I do still, by these pickers and stealers. + +ROSENCRANTZ Good my lord, what is your cause of distemper? you + do, surely, bar the door upon your own liberty, if + you deny your griefs to your friend. + +HAMLET Sir, I lack advancement. + +ROSENCRANTZ How can that be, when you have the voice of the king + himself for your succession in Denmark? + +HAMLET Ay, but sir, 'While the grass grows,'--the proverb + is something musty. + + [Re-enter Players with recorders] + + O, the recorders! let me see one. To withdraw with + you:--why do you go about to recover the wind of me, + as if you would drive me into a toil? + +GUILDENSTERN O, my lord, if my duty be too bold, my love is too + unmannerly. + +HAMLET I do not well understand that. Will you play upon + this pipe? + +GUILDENSTERN My lord, I cannot. + +HAMLET I pray you. + +GUILDENSTERN Believe me, I cannot. + +HAMLET I do beseech you. + +GUILDENSTERN I know no touch of it, my lord. + +HAMLET 'Tis as easy as lying: govern these ventages with + your lingers and thumb, give it breath with your + mouth, and it will discourse most eloquent music. + Look you, these are the stops. + +GUILDENSTERN But these cannot I command to any utterance of + harmony; I have not the skill. + +HAMLET Why, look you now, how unworthy a thing you make of + me! You would play upon me; you would seem to know + my stops; you would pluck out the heart of my + mystery; you would sound me from my lowest note to + the top of my compass: and there is much music, + excellent voice, in this little organ; yet cannot + you make it speak. 'Sblood, do you think I am + easier to be played on than a pipe? Call me what + instrument you will, though you can fret me, yet you + cannot play upon me. + + [Enter POLONIUS] + + God bless you, sir! + +LORD POLONIUS My lord, the queen would speak with you, and + presently. + +HAMLET Do you see yonder cloud that's almost in shape of a camel? + +LORD POLONIUS By the mass, and 'tis like a camel, indeed. + +HAMLET Methinks it is like a weasel. + +LORD POLONIUS It is backed like a weasel. + +HAMLET Or like a whale? + +LORD POLONIUS Very like a whale. + +HAMLET Then I will come to my mother by and by. They fool + me to the top of my bent. I will come by and by. + +LORD POLONIUS I will say so. + +HAMLET By and by is easily said. + + [Exit POLONIUS] + + Leave me, friends. + + [Exeunt all but HAMLET] + + Tis now the very witching time of night, + When churchyards yawn and hell itself breathes out + Contagion to this world: now could I drink hot blood, + And do such bitter business as the day + Would quake to look on. Soft! now to my mother. + O heart, lose not thy nature; let not ever + The soul of Nero enter this firm bosom: + Let me be cruel, not unnatural: + I will speak daggers to her, but use none; + My tongue and soul in this be hypocrites; + How in my words soever she be shent, + To give them seals never, my soul, consent! + + [Exit] + + + + HAMLET + + +ACT III + + + +SCENE III A room in the castle. + + + [Enter KING CLAUDIUS, ROSENCRANTZ, and GUILDENSTERN] + +KING CLAUDIUS I like him not, nor stands it safe with us + To let his madness range. Therefore prepare you; + I your commission will forthwith dispatch, + And he to England shall along with you: + The terms of our estate may not endure + Hazard so dangerous as doth hourly grow + Out of his lunacies. + +GUILDENSTERN We will ourselves provide: + Most holy and religious fear it is + To keep those many many bodies safe + That live and feed upon your majesty. + +ROSENCRANTZ The single and peculiar life is bound, + With all the strength and armour of the mind, + To keep itself from noyance; but much more + That spirit upon whose weal depend and rest + The lives of many. The cease of majesty + Dies not alone; but, like a gulf, doth draw + What's near it with it: it is a massy wheel, + Fix'd on the summit of the highest mount, + To whose huge spokes ten thousand lesser things + Are mortised and adjoin'd; which, when it falls, + Each small annexment, petty consequence, + Attends the boisterous ruin. Never alone + Did the king sigh, but with a general groan. + +KING CLAUDIUS Arm you, I pray you, to this speedy voyage; + For we will fetters put upon this fear, + Which now goes too free-footed. + + +ROSENCRANTZ | + | We will haste us. +GUILDENSTERN | + + + [Exeunt ROSENCRANTZ and GUILDENSTERN] + + [Enter POLONIUS] + +LORD POLONIUS My lord, he's going to his mother's closet: + Behind the arras I'll convey myself, + To hear the process; and warrant she'll tax him home: + And, as you said, and wisely was it said, + 'Tis meet that some more audience than a mother, + Since nature makes them partial, should o'erhear + The speech, of vantage. Fare you well, my liege: + I'll call upon you ere you go to bed, + And tell you what I know. + +KING CLAUDIUS Thanks, dear my lord. + + [Exit POLONIUS] + + O, my offence is rank it smells to heaven; + It hath the primal eldest curse upon't, + A brother's murder. Pray can I not, + Though inclination be as sharp as will: + My stronger guilt defeats my strong intent; + And, like a man to double business bound, + I stand in pause where I shall first begin, + And both neglect. What if this cursed hand + Were thicker than itself with brother's blood, + Is there not rain enough in the sweet heavens + To wash it white as snow? Whereto serves mercy + But to confront the visage of offence? + And what's in prayer but this two-fold force, + To be forestalled ere we come to fall, + Or pardon'd being down? Then I'll look up; + My fault is past. But, O, what form of prayer + Can serve my turn? 'Forgive me my foul murder'? + That cannot be; since I am still possess'd + Of those effects for which I did the murder, + My crown, mine own ambition and my queen. + May one be pardon'd and retain the offence? + In the corrupted currents of this world + Offence's gilded hand may shove by justice, + And oft 'tis seen the wicked prize itself + Buys out the law: but 'tis not so above; + There is no shuffling, there the action lies + In his true nature; and we ourselves compell'd, + Even to the teeth and forehead of our faults, + To give in evidence. What then? what rests? + Try what repentance can: what can it not? + Yet what can it when one can not repent? + O wretched state! O bosom black as death! + O limed soul, that, struggling to be free, + Art more engaged! Help, angels! Make assay! + Bow, stubborn knees; and, heart with strings of steel, + Be soft as sinews of the newborn babe! + All may be well. + + [Retires and kneels] + + [Enter HAMLET] + +HAMLET Now might I do it pat, now he is praying; + And now I'll do't. And so he goes to heaven; + And so am I revenged. That would be scann'd: + A villain kills my father; and for that, + I, his sole son, do this same villain send + To heaven. + O, this is hire and salary, not revenge. + He took my father grossly, full of bread; + With all his crimes broad blown, as flush as May; + And how his audit stands who knows save heaven? + But in our circumstance and course of thought, + 'Tis heavy with him: and am I then revenged, + To take him in the purging of his soul, + When he is fit and season'd for his passage? + No! + Up, sword; and know thou a more horrid hent: + When he is drunk asleep, or in his rage, + Or in the incestuous pleasure of his bed; + At gaming, swearing, or about some act + That has no relish of salvation in't; + Then trip him, that his heels may kick at heaven, + And that his soul may be as damn'd and black + As hell, whereto it goes. My mother stays: + This physic but prolongs thy sickly days. + + [Exit] + +KING CLAUDIUS [Rising] My words fly up, my thoughts remain below: + Words without thoughts never to heaven go. + + [Exit] + + + + + HAMLET + + +ACT III + + + +SCENE IV The Queen's closet. + + + [Enter QUEEN GERTRUDE and POLONIUS] + +LORD POLONIUS He will come straight. Look you lay home to him: + Tell him his pranks have been too broad to bear with, + And that your grace hath screen'd and stood between + Much heat and him. I'll sconce me even here. + Pray you, be round with him. + +HAMLET [Within] Mother, mother, mother! + +QUEEN GERTRUDE I'll warrant you, + Fear me not: withdraw, I hear him coming. + + [POLONIUS hides behind the arras] + + [Enter HAMLET] + +HAMLET Now, mother, what's the matter? + +QUEEN GERTRUDE Hamlet, thou hast thy father much offended. + +HAMLET Mother, you have my father much offended. + +QUEEN GERTRUDE Come, come, you answer with an idle tongue. + +HAMLET Go, go, you question with a wicked tongue. + +QUEEN GERTRUDE Why, how now, Hamlet! + +HAMLET What's the matter now? + +QUEEN GERTRUDE Have you forgot me? + +HAMLET No, by the rood, not so: + You are the queen, your husband's brother's wife; + And--would it were not so!--you are my mother. + +QUEEN GERTRUDE Nay, then, I'll set those to you that can speak. + +HAMLET Come, come, and sit you down; you shall not budge; + You go not till I set you up a glass + Where you may see the inmost part of you. + +QUEEN GERTRUDE What wilt thou do? thou wilt not murder me? + Help, help, ho! + +LORD POLONIUS [Behind] What, ho! help, help, help! + +HAMLET [Drawing] How now! a rat? Dead, for a ducat, dead! + + [Makes a pass through the arras] + +LORD POLONIUS [Behind] O, I am slain! + + [Falls and dies] + +QUEEN GERTRUDE O me, what hast thou done? + +HAMLET Nay, I know not: + Is it the king? + +QUEEN GERTRUDE O, what a rash and bloody deed is this! + +HAMLET A bloody deed! almost as bad, good mother, + As kill a king, and marry with his brother. + +QUEEN GERTRUDE As kill a king! + +HAMLET Ay, lady, 'twas my word. + + [Lifts up the array and discovers POLONIUS] + + Thou wretched, rash, intruding fool, farewell! + I took thee for thy better: take thy fortune; + Thou find'st to be too busy is some danger. + Leave wringing of your hands: peace! sit you down, + And let me wring your heart; for so I shall, + If it be made of penetrable stuff, + If damned custom have not brass'd it so + That it is proof and bulwark against sense. + +QUEEN GERTRUDE What have I done, that thou darest wag thy tongue + In noise so rude against me? + +HAMLET Such an act + That blurs the grace and blush of modesty, + Calls virtue hypocrite, takes off the rose + From the fair forehead of an innocent love + And sets a blister there, makes marriage-vows + As false as dicers' oaths: O, such a deed + As from the body of contraction plucks + The very soul, and sweet religion makes + A rhapsody of words: heaven's face doth glow: + Yea, this solidity and compound mass, + With tristful visage, as against the doom, + Is thought-sick at the act. + +QUEEN GERTRUDE Ay me, what act, + That roars so loud, and thunders in the index? + +HAMLET Look here, upon this picture, and on this, + The counterfeit presentment of two brothers. + See, what a grace was seated on this brow; + Hyperion's curls; the front of Jove himself; + An eye like Mars, to threaten and command; + A station like the herald Mercury + New-lighted on a heaven-kissing hill; + A combination and a form indeed, + Where every god did seem to set his seal, + To give the world assurance of a man: + This was your husband. Look you now, what follows: + Here is your husband; like a mildew'd ear, + Blasting his wholesome brother. Have you eyes? + Could you on this fair mountain leave to feed, + And batten on this moor? Ha! have you eyes? + You cannot call it love; for at your age + The hey-day in the blood is tame, it's humble, + And waits upon the judgment: and what judgment + Would step from this to this? Sense, sure, you have, + Else could you not have motion; but sure, that sense + Is apoplex'd; for madness would not err, + Nor sense to ecstasy was ne'er so thrall'd + But it reserved some quantity of choice, + To serve in such a difference. What devil was't + That thus hath cozen'd you at hoodman-blind? + Eyes without feeling, feeling without sight, + Ears without hands or eyes, smelling sans all, + Or but a sickly part of one true sense + Could not so mope. + O shame! where is thy blush? Rebellious hell, + If thou canst mutine in a matron's bones, + To flaming youth let virtue be as wax, + And melt in her own fire: proclaim no shame + When the compulsive ardour gives the charge, + Since frost itself as actively doth burn + And reason panders will. + +QUEEN GERTRUDE O Hamlet, speak no more: + Thou turn'st mine eyes into my very soul; + And there I see such black and grained spots + As will not leave their tinct. + +HAMLET Nay, but to live + In the rank sweat of an enseamed bed, + Stew'd in corruption, honeying and making love + Over the nasty sty,-- + +QUEEN GERTRUDE O, speak to me no more; + These words, like daggers, enter in mine ears; + No more, sweet Hamlet! + +HAMLET A murderer and a villain; + A slave that is not twentieth part the tithe + Of your precedent lord; a vice of kings; + A cutpurse of the empire and the rule, + That from a shelf the precious diadem stole, + And put it in his pocket! + +QUEEN GERTRUDE No more! + +HAMLET A king of shreds and patches,-- + + [Enter Ghost] + + Save me, and hover o'er me with your wings, + You heavenly guards! What would your gracious figure? + +QUEEN GERTRUDE Alas, he's mad! + +HAMLET Do you not come your tardy son to chide, + That, lapsed in time and passion, lets go by + The important acting of your dread command? O, say! + +Ghost Do not forget: this visitation + Is but to whet thy almost blunted purpose. + But, look, amazement on thy mother sits: + O, step between her and her fighting soul: + Conceit in weakest bodies strongest works: + Speak to her, Hamlet. + +HAMLET How is it with you, lady? + +QUEEN GERTRUDE Alas, how is't with you, + That you do bend your eye on vacancy + And with the incorporal air do hold discourse? + Forth at your eyes your spirits wildly peep; + And, as the sleeping soldiers in the alarm, + Your bedded hair, like life in excrements, + Starts up, and stands on end. O gentle son, + Upon the heat and flame of thy distemper + Sprinkle cool patience. Whereon do you look? + +HAMLET On him, on him! Look you, how pale he glares! + His form and cause conjoin'd, preaching to stones, + Would make them capable. Do not look upon me; + Lest with this piteous action you convert + My stern effects: then what I have to do + Will want true colour; tears perchance for blood. + +QUEEN GERTRUDE To whom do you speak this? + +HAMLET Do you see nothing there? + +QUEEN GERTRUDE Nothing at all; yet all that is I see. + +HAMLET Nor did you nothing hear? + +QUEEN GERTRUDE No, nothing but ourselves. + +HAMLET Why, look you there! look, how it steals away! + My father, in his habit as he lived! + Look, where he goes, even now, out at the portal! + + [Exit Ghost] + +QUEEN GERTRUDE This the very coinage of your brain: + This bodiless creation ecstasy + Is very cunning in. + +HAMLET Ecstasy! + My pulse, as yours, doth temperately keep time, + And makes as healthful music: it is not madness + That I have utter'd: bring me to the test, + And I the matter will re-word; which madness + Would gambol from. Mother, for love of grace, + Lay not that mattering unction to your soul, + That not your trespass, but my madness speaks: + It will but skin and film the ulcerous place, + Whilst rank corruption, mining all within, + Infects unseen. Confess yourself to heaven; + Repent what's past; avoid what is to come; + And do not spread the compost on the weeds, + To make them ranker. Forgive me this my virtue; + For in the fatness of these pursy times + Virtue itself of vice must pardon beg, + Yea, curb and woo for leave to do him good. + +QUEEN GERTRUDE O Hamlet, thou hast cleft my heart in twain. + +HAMLET O, throw away the worser part of it, + And live the purer with the other half. + Good night: but go not to mine uncle's bed; + Assume a virtue, if you have it not. + That monster, custom, who all sense doth eat, + Of habits devil, is angel yet in this, + That to the use of actions fair and good + He likewise gives a frock or livery, + That aptly is put on. Refrain to-night, + And that shall lend a kind of easiness + To the next abstinence: the next more easy; + For use almost can change the stamp of nature, + And either curb the devil, or throw him out + With wondrous potency. Once more, good night: + And when you are desirous to be bless'd, + I'll blessing beg of you. For this same lord, + + [Pointing to POLONIUS] + + I do repent: but heaven hath pleased it so, + To punish me with this and this with me, + That I must be their scourge and minister. + I will bestow him, and will answer well + The death I gave him. So, again, good night. + I must be cruel, only to be kind: + Thus bad begins and worse remains behind. + One word more, good lady. + +QUEEN GERTRUDE What shall I do? + +HAMLET Not this, by no means, that I bid you do: + Let the bloat king tempt you again to bed; + Pinch wanton on your cheek; call you his mouse; + And let him, for a pair of reechy kisses, + Or paddling in your neck with his damn'd fingers, + Make you to ravel all this matter out, + That I essentially am not in madness, + But mad in craft. 'Twere good you let him know; + For who, that's but a queen, fair, sober, wise, + Would from a paddock, from a bat, a gib, + Such dear concernings hide? who would do so? + No, in despite of sense and secrecy, + Unpeg the basket on the house's top. + Let the birds fly, and, like the famous ape, + To try conclusions, in the basket creep, + And break your own neck down. + +QUEEN GERTRUDE Be thou assured, if words be made of breath, + And breath of life, I have no life to breathe + What thou hast said to me. + +HAMLET I must to England; you know that? + +QUEEN GERTRUDE Alack, + I had forgot: 'tis so concluded on. + +HAMLET There's letters seal'd: and my two schoolfellows, + Whom I will trust as I will adders fang'd, + They bear the mandate; they must sweep my way, + And marshal me to knavery. Let it work; + For 'tis the sport to have the engineer + Hoist with his own petard: and 't shall go hard + But I will delve one yard below their mines, + And blow them at the moon: O, 'tis most sweet, + When in one line two crafts directly meet. + This man shall set me packing: + I'll lug the guts into the neighbour room. + Mother, good night. Indeed this counsellor + Is now most still, most secret and most grave, + Who was in life a foolish prating knave. + Come, sir, to draw toward an end with you. + Good night, mother. + + [Exeunt severally; HAMLET dragging in POLONIUS] + + + + + HAMLET + + +ACT IV + + + +SCENE I A room in the castle. + + + [Enter KING CLAUDIUS, QUEEN GERTRUDE, ROSENCRANTZ, + and GUILDENSTERN] + +KING CLAUDIUS There's matter in these sighs, these profound heaves: + You must translate: 'tis fit we understand them. + Where is your son? + +QUEEN GERTRUDE Bestow this place on us a little while. + + [Exeunt ROSENCRANTZ and GUILDENSTERN] + + Ah, my good lord, what have I seen to-night! + +KING CLAUDIUS What, Gertrude? How does Hamlet? + +QUEEN GERTRUDE Mad as the sea and wind, when both contend + Which is the mightier: in his lawless fit, + Behind the arras hearing something stir, + Whips out his rapier, cries, 'A rat, a rat!' + And, in this brainish apprehension, kills + The unseen good old man. + +KING CLAUDIUS O heavy deed! + It had been so with us, had we been there: + His liberty is full of threats to all; + To you yourself, to us, to every one. + Alas, how shall this bloody deed be answer'd? + It will be laid to us, whose providence + Should have kept short, restrain'd and out of haunt, + This mad young man: but so much was our love, + We would not understand what was most fit; + But, like the owner of a foul disease, + To keep it from divulging, let it feed + Even on the pith of Life. Where is he gone? + +QUEEN GERTRUDE To draw apart the body he hath kill'd: + O'er whom his very madness, like some ore + Among a mineral of metals base, + Shows itself pure; he weeps for what is done. + +KING CLAUDIUS O Gertrude, come away! + The sun no sooner shall the mountains touch, + But we will ship him hence: and this vile deed + We must, with all our majesty and skill, + Both countenance and excuse. Ho, Guildenstern! + + [Re-enter ROSENCRANTZ and GUILDENSTERN] + + Friends both, go join you with some further aid: + Hamlet in madness hath Polonius slain, + And from his mother's closet hath he dragg'd him: + Go seek him out; speak fair, and bring the body + Into the chapel. I pray you, haste in this. + + [Exeunt ROSENCRANTZ and GUILDENSTERN] + + Come, Gertrude, we'll call up our wisest friends; + And let them know, both what we mean to do, + And what's untimely done; so haply slander, + Whose whisper o'er the world's diameter, + As level as the cannon to his blank, + Transports his poison'd shot, may miss our name, + And hit the woundless air. O, come away! + My soul is full of discord and dismay. + + [Exeunt] + + + + + HAMLET + + +ACT IV + + + +SCENE II Another room in the castle. + + + [Enter HAMLET] + +HAMLET Safely stowed. + + +ROSENCRANTZ: | + | [Within] Hamlet! Lord Hamlet! +GUILDENSTERN: | + + +HAMLET What noise? who calls on Hamlet? + O, here they come. + + [Enter ROSENCRANTZ and GUILDENSTERN] + +ROSENCRANTZ What have you done, my lord, with the dead body? + +HAMLET Compounded it with dust, whereto 'tis kin. + +ROSENCRANTZ Tell us where 'tis, that we may take it thence + And bear it to the chapel. + +HAMLET Do not believe it. + +ROSENCRANTZ Believe what? + +HAMLET That I can keep your counsel and not mine own. + Besides, to be demanded of a sponge! what + replication should be made by the son of a king? + +ROSENCRANTZ Take you me for a sponge, my lord? + +HAMLET Ay, sir, that soaks up the king's countenance, his + rewards, his authorities. But such officers do the + king best service in the end: he keeps them, like + an ape, in the corner of his jaw; first mouthed, to + be last swallowed: when he needs what you have + gleaned, it is but squeezing you, and, sponge, you + shall be dry again. + +ROSENCRANTZ I understand you not, my lord. + +HAMLET I am glad of it: a knavish speech sleeps in a + foolish ear. + +ROSENCRANTZ My lord, you must tell us where the body is, and go + with us to the king. + +HAMLET The body is with the king, but the king is not with + the body. The king is a thing-- + +GUILDENSTERN A thing, my lord! + +HAMLET Of nothing: bring me to him. Hide fox, and all after. + + [Exeunt] + + + + + HAMLET + + +ACT IV + + + +SCENE III Another room in the castle. + + + [Enter KING CLAUDIUS, attended] + +KING CLAUDIUS I have sent to seek him, and to find the body. + How dangerous is it that this man goes loose! + Yet must not we put the strong law on him: + He's loved of the distracted multitude, + Who like not in their judgment, but their eyes; + And where tis so, the offender's scourge is weigh'd, + But never the offence. To bear all smooth and even, + This sudden sending him away must seem + Deliberate pause: diseases desperate grown + By desperate appliance are relieved, + Or not at all. + + [Enter ROSENCRANTZ] + + How now! what hath befall'n? + +ROSENCRANTZ Where the dead body is bestow'd, my lord, + We cannot get from him. + +KING CLAUDIUS But where is he? + +ROSENCRANTZ Without, my lord; guarded, to know your pleasure. + +KING CLAUDIUS Bring him before us. + +ROSENCRANTZ Ho, Guildenstern! bring in my lord. + + [Enter HAMLET and GUILDENSTERN] + +KING CLAUDIUS Now, Hamlet, where's Polonius? + +HAMLET At supper. + +KING CLAUDIUS At supper! where? + +HAMLET Not where he eats, but where he is eaten: a certain + convocation of politic worms are e'en at him. Your + worm is your only emperor for diet: we fat all + creatures else to fat us, and we fat ourselves for + maggots: your fat king and your lean beggar is but + variable service, two dishes, but to one table: + that's the end. + +KING CLAUDIUS Alas, alas! + +HAMLET A man may fish with the worm that hath eat of a + king, and cat of the fish that hath fed of that worm. + +KING CLAUDIUS What dost you mean by this? + +HAMLET Nothing but to show you how a king may go a + progress through the guts of a beggar. + +KING CLAUDIUS Where is Polonius? + +HAMLET In heaven; send hither to see: if your messenger + find him not there, seek him i' the other place + yourself. But indeed, if you find him not within + this month, you shall nose him as you go up the + stairs into the lobby. + +KING CLAUDIUS Go seek him there. + + [To some Attendants] + +HAMLET He will stay till ye come. + + [Exeunt Attendants] + +KING CLAUDIUS Hamlet, this deed, for thine especial safety,-- + Which we do tender, as we dearly grieve + For that which thou hast done,--must send thee hence + With fiery quickness: therefore prepare thyself; + The bark is ready, and the wind at help, + The associates tend, and every thing is bent + For England. + +HAMLET For England! + +KING CLAUDIUS Ay, Hamlet. + +HAMLET Good. + +KING CLAUDIUS So is it, if thou knew'st our purposes. + +HAMLET I see a cherub that sees them. But, come; for + England! Farewell, dear mother. + +KING CLAUDIUS Thy loving father, Hamlet. + +HAMLET My mother: father and mother is man and wife; man + and wife is one flesh; and so, my mother. Come, for England! + + [Exit] + +KING CLAUDIUS Follow him at foot; tempt him with speed aboard; + Delay it not; I'll have him hence to-night: + Away! for every thing is seal'd and done + That else leans on the affair: pray you, make haste. + + [Exeunt ROSENCRANTZ and GUILDENSTERN] + + And, England, if my love thou hold'st at aught-- + As my great power thereof may give thee sense, + Since yet thy cicatrice looks raw and red + After the Danish sword, and thy free awe + Pays homage to us--thou mayst not coldly set + Our sovereign process; which imports at full, + By letters congruing to that effect, + The present death of Hamlet. Do it, England; + For like the hectic in my blood he rages, + And thou must cure me: till I know 'tis done, + Howe'er my haps, my joys were ne'er begun. + + [Exit] + + + + + HAMLET + + +ACT IV + + + +SCENE IV A plain in Denmark. + + + [Enter FORTINBRAS, a Captain, and Soldiers, marching] + +PRINCE FORTINBRAS Go, captain, from me greet the Danish king; + Tell him that, by his licence, Fortinbras + Craves the conveyance of a promised march + Over his kingdom. You know the rendezvous. + If that his majesty would aught with us, + We shall express our duty in his eye; + And let him know so. + +Captain I will do't, my lord. + +PRINCE FORTINBRAS Go softly on. + + [Exeunt FORTINBRAS and Soldiers] + + [Enter HAMLET, ROSENCRANTZ, GUILDENSTERN, and others] + +HAMLET Good sir, whose powers are these? + +Captain They are of Norway, sir. + +HAMLET How purposed, sir, I pray you? + +Captain Against some part of Poland. + +HAMLET Who commands them, sir? + +Captain The nephews to old Norway, Fortinbras. + +HAMLET Goes it against the main of Poland, sir, + Or for some frontier? + +Captain Truly to speak, and with no addition, + We go to gain a little patch of ground + That hath in it no profit but the name. + To pay five ducats, five, I would not farm it; + Nor will it yield to Norway or the Pole + A ranker rate, should it be sold in fee. + +HAMLET Why, then the Polack never will defend it. + +Captain Yes, it is already garrison'd. + +HAMLET Two thousand souls and twenty thousand ducats + Will not debate the question of this straw: + This is the imposthume of much wealth and peace, + That inward breaks, and shows no cause without + Why the man dies. I humbly thank you, sir. + +Captain God be wi' you, sir. + + [Exit] + +ROSENCRANTZ Wilt please you go, my lord? + +HAMLET I'll be with you straight go a little before. + + [Exeunt all except HAMLET] + + How all occasions do inform against me, + And spur my dull revenge! What is a man, + If his chief good and market of his time + Be but to sleep and feed? a beast, no more. + Sure, he that made us with such large discourse, + Looking before and after, gave us not + That capability and god-like reason + To fust in us unused. Now, whether it be + Bestial oblivion, or some craven scruple + Of thinking too precisely on the event, + A thought which, quarter'd, hath but one part wisdom + And ever three parts coward, I do not know + Why yet I live to say 'This thing's to do;' + Sith I have cause and will and strength and means + To do't. Examples gross as earth exhort me: + Witness this army of such mass and charge + Led by a delicate and tender prince, + Whose spirit with divine ambition puff'd + Makes mouths at the invisible event, + Exposing what is mortal and unsure + To all that fortune, death and danger dare, + Even for an egg-shell. Rightly to be great + Is not to stir without great argument, + But greatly to find quarrel in a straw + When honour's at the stake. How stand I then, + That have a father kill'd, a mother stain'd, + Excitements of my reason and my blood, + And let all sleep? while, to my shame, I see + The imminent death of twenty thousand men, + That, for a fantasy and trick of fame, + Go to their graves like beds, fight for a plot + Whereon the numbers cannot try the cause, + Which is not tomb enough and continent + To hide the slain? O, from this time forth, + My thoughts be bloody, or be nothing worth! + + [Exit] + + + + + HAMLET + + +ACT IV + + +SCENE V Elsinore. A room in the castle. + + + [Enter QUEEN GERTRUDE, HORATIO, and a Gentleman] + +QUEEN GERTRUDE I will not speak with her. + +Gentleman She is importunate, indeed distract: + Her mood will needs be pitied. + +QUEEN GERTRUDE What would she have? + +Gentleman She speaks much of her father; says she hears + There's tricks i' the world; and hems, and beats her heart; + Spurns enviously at straws; speaks things in doubt, + That carry but half sense: her speech is nothing, + Yet the unshaped use of it doth move + The hearers to collection; they aim at it, + And botch the words up fit to their own thoughts; + Which, as her winks, and nods, and gestures + yield them, + Indeed would make one think there might be thought, + Though nothing sure, yet much unhappily. + +HORATIO 'Twere good she were spoken with; for she may strew + Dangerous conjectures in ill-breeding minds. + +QUEEN GERTRUDE Let her come in. + + [Exit HORATIO] + + To my sick soul, as sin's true nature is, + Each toy seems prologue to some great amiss: + So full of artless jealousy is guilt, + It spills itself in fearing to be spilt. + + [Re-enter HORATIO, with OPHELIA] + +OPHELIA Where is the beauteous majesty of Denmark? + +QUEEN GERTRUDE How now, Ophelia! + +OPHELIA [Sings] + + How should I your true love know + From another one? + By his cockle hat and staff, + And his sandal shoon. + +QUEEN GERTRUDE Alas, sweet lady, what imports this song? + +OPHELIA Say you? nay, pray you, mark. + + [Sings] + + He is dead and gone, lady, + He is dead and gone; + At his head a grass-green turf, + At his heels a stone. + +QUEEN GERTRUDE Nay, but, Ophelia,-- + +OPHELIA Pray you, mark. + + [Sings] + + White his shroud as the mountain snow,-- + + [Enter KING CLAUDIUS] + +QUEEN GERTRUDE Alas, look here, my lord. + +OPHELIA [Sings] + + Larded with sweet flowers + Which bewept to the grave did go + With true-love showers. + +KING CLAUDIUS How do you, pretty lady? + +OPHELIA Well, God 'ild you! They say the owl was a baker's + daughter. Lord, we know what we are, but know not + what we may be. God be at your table! + +KING CLAUDIUS Conceit upon her father. + +OPHELIA Pray you, let's have no words of this; but when they + ask you what it means, say you this: + + [Sings] + + To-morrow is Saint Valentine's day, + All in the morning betime, + And I a maid at your window, + To be your Valentine. + Then up he rose, and donn'd his clothes, + And dupp'd the chamber-door; + Let in the maid, that out a maid + Never departed more. + +KING CLAUDIUS Pretty Ophelia! + +OPHELIA Indeed, la, without an oath, I'll make an end on't: + + [Sings] + + By Gis and by Saint Charity, + Alack, and fie for shame! + Young men will do't, if they come to't; + By cock, they are to blame. + Quoth she, before you tumbled me, + You promised me to wed. + So would I ha' done, by yonder sun, + An thou hadst not come to my bed. + +KING CLAUDIUS How long hath she been thus? + +OPHELIA I hope all will be well. We must be patient: but I + cannot choose but weep, to think they should lay him + i' the cold ground. My brother shall know of it: + and so I thank you for your good counsel. Come, my + coach! Good night, ladies; good night, sweet ladies; + good night, good night. + + [Exit] + +KING CLAUDIUS Follow her close; give her good watch, + I pray you. + + [Exit HORATIO] + + O, this is the poison of deep grief; it springs + All from her father's death. O Gertrude, Gertrude, + When sorrows come, they come not single spies + But in battalions. First, her father slain: + Next, your son gone; and he most violent author + Of his own just remove: the people muddied, + Thick and unwholesome in their thoughts and whispers, + For good Polonius' death; and we have done but greenly, + In hugger-mugger to inter him: poor Ophelia + Divided from herself and her fair judgment, + Without the which we are pictures, or mere beasts: + Last, and as much containing as all these, + Her brother is in secret come from France; + Feeds on his wonder, keeps himself in clouds, + And wants not buzzers to infect his ear + With pestilent speeches of his father's death; + Wherein necessity, of matter beggar'd, + Will nothing stick our person to arraign + In ear and ear. O my dear Gertrude, this, + Like to a murdering-piece, in many places + Gives me superfluous death. + + [A noise within] + +QUEEN GERTRUDE Alack, what noise is this? + +KING CLAUDIUS Where are my Switzers? Let them guard the door. + + [Enter another Gentleman] + + What is the matter? + +Gentleman Save yourself, my lord: + The ocean, overpeering of his list, + Eats not the flats with more impetuous haste + Than young Laertes, in a riotous head, + O'erbears your officers. The rabble call him lord; + And, as the world were now but to begin, + Antiquity forgot, custom not known, + The ratifiers and props of every word, + They cry 'Choose we: Laertes shall be king:' + Caps, hands, and tongues, applaud it to the clouds: + 'Laertes shall be king, Laertes king!' + +QUEEN GERTRUDE How cheerfully on the false trail they cry! + O, this is counter, you false Danish dogs! + +KING CLAUDIUS The doors are broke. + + [Noise within] + + [Enter LAERTES, armed; Danes following] + +LAERTES Where is this king? Sirs, stand you all without. + +Danes No, let's come in. + +LAERTES I pray you, give me leave. + +Danes We will, we will. + + [They retire without the door] + +LAERTES I thank you: keep the door. O thou vile king, + Give me my father! + +QUEEN GERTRUDE Calmly, good Laertes. + +LAERTES That drop of blood that's calm proclaims me bastard, + Cries cuckold to my father, brands the harlot + Even here, between the chaste unsmirched brow + Of my true mother. + +KING CLAUDIUS What is the cause, Laertes, + That thy rebellion looks so giant-like? + Let him go, Gertrude; do not fear our person: + There's such divinity doth hedge a king, + That treason can but peep to what it would, + Acts little of his will. Tell me, Laertes, + Why thou art thus incensed. Let him go, Gertrude. + Speak, man. + +LAERTES Where is my father? + +KING CLAUDIUS Dead. + +QUEEN GERTRUDE But not by him. + +KING CLAUDIUS Let him demand his fill. + +LAERTES How came he dead? I'll not be juggled with: + To hell, allegiance! vows, to the blackest devil! + Conscience and grace, to the profoundest pit! + I dare damnation. To this point I stand, + That both the worlds I give to negligence, + Let come what comes; only I'll be revenged + Most thoroughly for my father. + +KING CLAUDIUS Who shall stay you? + +LAERTES My will, not all the world: + And for my means, I'll husband them so well, + They shall go far with little. + +KING CLAUDIUS Good Laertes, + If you desire to know the certainty + Of your dear father's death, is't writ in your revenge, + That, swoopstake, you will draw both friend and foe, + Winner and loser? + +LAERTES None but his enemies. + +KING CLAUDIUS Will you know them then? + +LAERTES To his good friends thus wide I'll ope my arms; + And like the kind life-rendering pelican, + Repast them with my blood. + +KING CLAUDIUS Why, now you speak + Like a good child and a true gentleman. + That I am guiltless of your father's death, + And am most sensible in grief for it, + It shall as level to your judgment pierce + As day does to your eye. + +Danes [Within] Let her come in. + +LAERTES How now! what noise is that? + + [Re-enter OPHELIA] + + O heat, dry up my brains! tears seven times salt, + Burn out the sense and virtue of mine eye! + By heaven, thy madness shall be paid by weight, + Till our scale turn the beam. O rose of May! + Dear maid, kind sister, sweet Ophelia! + O heavens! is't possible, a young maid's wits + Should be as moral as an old man's life? + Nature is fine in love, and where 'tis fine, + It sends some precious instance of itself + After the thing it loves. + +OPHELIA [Sings] + + They bore him barefaced on the bier; + Hey non nonny, nonny, hey nonny; + And in his grave rain'd many a tear:-- + Fare you well, my dove! + +LAERTES Hadst thou thy wits, and didst persuade revenge, + It could not move thus. + +OPHELIA [Sings] + + You must sing a-down a-down, + An you call him a-down-a. + O, how the wheel becomes it! It is the false + steward, that stole his master's daughter. + +LAERTES This nothing's more than matter. + +OPHELIA There's rosemary, that's for remembrance; pray, + love, remember: and there is pansies. that's for thoughts. + +LAERTES A document in madness, thoughts and remembrance fitted. + +OPHELIA There's fennel for you, and columbines: there's rue + for you; and here's some for me: we may call it + herb-grace o' Sundays: O you must wear your rue with + a difference. There's a daisy: I would give you + some violets, but they withered all when my father + died: they say he made a good end,-- + + [Sings] + + For bonny sweet Robin is all my joy. + +LAERTES Thought and affliction, passion, hell itself, + She turns to favour and to prettiness. + +OPHELIA [Sings] + + And will he not come again? + And will he not come again? + No, no, he is dead: + Go to thy death-bed: + He never will come again. + + His beard was as white as snow, + All flaxen was his poll: + He is gone, he is gone, + And we cast away moan: + God ha' mercy on his soul! + + And of all Christian souls, I pray God. God be wi' ye. + + [Exit] + +LAERTES Do you see this, O God? + +KING CLAUDIUS Laertes, I must commune with your grief, + Or you deny me right. Go but apart, + Make choice of whom your wisest friends you will. + And they shall hear and judge 'twixt you and me: + If by direct or by collateral hand + They find us touch'd, we will our kingdom give, + Our crown, our life, and all that we can ours, + To you in satisfaction; but if not, + Be you content to lend your patience to us, + And we shall jointly labour with your soul + To give it due content. + +LAERTES Let this be so; + His means of death, his obscure funeral-- + No trophy, sword, nor hatchment o'er his bones, + No noble rite nor formal ostentation-- + Cry to be heard, as 'twere from heaven to earth, + That I must call't in question. + +KING CLAUDIUS So you shall; + And where the offence is let the great axe fall. + I pray you, go with me. + + [Exeunt] + + + + + HAMLET + + +ACT IV + + + +SCENE VI Another room in the castle. + + + [Enter HORATIO and a Servant] + +HORATIO What are they that would speak with me? + +Servant Sailors, sir: they say they have letters for you. + +HORATIO Let them come in. + + [Exit Servant] + + I do not know from what part of the world + I should be greeted, if not from Lord Hamlet. + + [Enter Sailors] + +First Sailor God bless you, sir. + +HORATIO Let him bless thee too. + +First Sailor He shall, sir, an't please him. There's a letter for + you, sir; it comes from the ambassador that was + bound for England; if your name be Horatio, as I am + let to know it is. + +HORATIO [Reads] 'Horatio, when thou shalt have overlooked + this, give these fellows some means to the king: + they have letters for him. Ere we were two days old + at sea, a pirate of very warlike appointment gave us + chase. Finding ourselves too slow of sail, we put on + a compelled valour, and in the grapple I boarded + them: on the instant they got clear of our ship; so + I alone became their prisoner. They have dealt with + me like thieves of mercy: but they knew what they + did; I am to do a good turn for them. Let the king + have the letters I have sent; and repair thou to me + with as much speed as thou wouldst fly death. I + have words to speak in thine ear will make thee + dumb; yet are they much too light for the bore of + the matter. These good fellows will bring thee + where I am. Rosencrantz and Guildenstern hold their + course for England: of them I have much to tell + thee. Farewell. + 'He that thou knowest thine, HAMLET.' + Come, I will make you way for these your letters; + And do't the speedier, that you may direct me + To him from whom you brought them. + + [Exeunt] + + + + + HAMLET + + +ACT IV + + +SCENE VII Another room in the castle. + + + [Enter KING CLAUDIUS and LAERTES] + +KING CLAUDIUS Now must your conscience my acquaintance seal, + And you must put me in your heart for friend, + Sith you have heard, and with a knowing ear, + That he which hath your noble father slain + Pursued my life. + +LAERTES It well appears: but tell me + Why you proceeded not against these feats, + So crimeful and so capital in nature, + As by your safety, wisdom, all things else, + You mainly were stirr'd up. + +KING CLAUDIUS O, for two special reasons; + Which may to you, perhaps, seem much unsinew'd, + But yet to me they are strong. The queen his mother + Lives almost by his looks; and for myself-- + My virtue or my plague, be it either which-- + She's so conjunctive to my life and soul, + That, as the star moves not but in his sphere, + I could not but by her. The other motive, + Why to a public count I might not go, + Is the great love the general gender bear him; + Who, dipping all his faults in their affection, + Would, like the spring that turneth wood to stone, + Convert his gyves to graces; so that my arrows, + Too slightly timber'd for so loud a wind, + Would have reverted to my bow again, + And not where I had aim'd them. + +LAERTES And so have I a noble father lost; + A sister driven into desperate terms, + Whose worth, if praises may go back again, + Stood challenger on mount of all the age + For her perfections: but my revenge will come. + +KING CLAUDIUS Break not your sleeps for that: you must not think + That we are made of stuff so flat and dull + That we can let our beard be shook with danger + And think it pastime. You shortly shall hear more: + I loved your father, and we love ourself; + And that, I hope, will teach you to imagine-- + + [Enter a Messenger] + + How now! what news? + +Messenger Letters, my lord, from Hamlet: + This to your majesty; this to the queen. + +KING CLAUDIUS From Hamlet! who brought them? + +Messenger Sailors, my lord, they say; I saw them not: + They were given me by Claudio; he received them + Of him that brought them. + +KING CLAUDIUS Laertes, you shall hear them. Leave us. + + [Exit Messenger] + + [Reads] + + 'High and mighty, You shall know I am set naked on + your kingdom. To-morrow shall I beg leave to see + your kingly eyes: when I shall, first asking your + pardon thereunto, recount the occasion of my sudden + and more strange return. 'HAMLET.' + What should this mean? Are all the rest come back? + Or is it some abuse, and no such thing? + +LAERTES Know you the hand? + +KING CLAUDIUS 'Tis Hamlets character. 'Naked! + And in a postscript here, he says 'alone.' + Can you advise me? + +LAERTES I'm lost in it, my lord. But let him come; + It warms the very sickness in my heart, + That I shall live and tell him to his teeth, + 'Thus didest thou.' + +KING CLAUDIUS If it be so, Laertes-- + As how should it be so? how otherwise?-- + Will you be ruled by me? + +LAERTES Ay, my lord; + So you will not o'errule me to a peace. + +KING CLAUDIUS To thine own peace. If he be now return'd, + As checking at his voyage, and that he means + No more to undertake it, I will work him + To an exploit, now ripe in my device, + Under the which he shall not choose but fall: + And for his death no wind of blame shall breathe, + But even his mother shall uncharge the practise + And call it accident. + +LAERTES My lord, I will be ruled; + The rather, if you could devise it so + That I might be the organ. + +KING CLAUDIUS It falls right. + You have been talk'd of since your travel much, + And that in Hamlet's hearing, for a quality + Wherein, they say, you shine: your sum of parts + Did not together pluck such envy from him + As did that one, and that, in my regard, + Of the unworthiest siege. + +LAERTES What part is that, my lord? + +KING CLAUDIUS A very riband in the cap of youth, + Yet needful too; for youth no less becomes + The light and careless livery that it wears + Than settled age his sables and his weeds, + Importing health and graveness. Two months since, + Here was a gentleman of Normandy:-- + I've seen myself, and served against, the French, + And they can well on horseback: but this gallant + Had witchcraft in't; he grew unto his seat; + And to such wondrous doing brought his horse, + As he had been incorpsed and demi-natured + With the brave beast: so far he topp'd my thought, + That I, in forgery of shapes and tricks, + Come short of what he did. + +LAERTES A Norman was't? + +KING CLAUDIUS A Norman. + +LAERTES Upon my life, Lamond. + +KING CLAUDIUS The very same. + +LAERTES I know him well: he is the brooch indeed + And gem of all the nation. + +KING CLAUDIUS He made confession of you, + And gave you such a masterly report + For art and exercise in your defence + And for your rapier most especially, + That he cried out, 'twould be a sight indeed, + If one could match you: the scrimers of their nation, + He swore, had had neither motion, guard, nor eye, + If you opposed them. Sir, this report of his + Did Hamlet so envenom with his envy + That he could nothing do but wish and beg + Your sudden coming o'er, to play with him. + Now, out of this,-- + +LAERTES What out of this, my lord? + +KING CLAUDIUS Laertes, was your father dear to you? + Or are you like the painting of a sorrow, + A face without a heart? + +LAERTES Why ask you this? + +KING CLAUDIUS Not that I think you did not love your father; + But that I know love is begun by time; + And that I see, in passages of proof, + Time qualifies the spark and fire of it. + There lives within the very flame of love + A kind of wick or snuff that will abate it; + And nothing is at a like goodness still; + For goodness, growing to a plurisy, + Dies in his own too much: that we would do + We should do when we would; for this 'would' changes + And hath abatements and delays as many + As there are tongues, are hands, are accidents; + And then this 'should' is like a spendthrift sigh, + That hurts by easing. But, to the quick o' the ulcer:-- + Hamlet comes back: what would you undertake, + To show yourself your father's son in deed + More than in words? + +LAERTES To cut his throat i' the church. + +KING CLAUDIUS No place, indeed, should murder sanctuarize; + Revenge should have no bounds. But, good Laertes, + Will you do this, keep close within your chamber. + Hamlet return'd shall know you are come home: + We'll put on those shall praise your excellence + And set a double varnish on the fame + The Frenchman gave you, bring you in fine together + And wager on your heads: he, being remiss, + Most generous and free from all contriving, + Will not peruse the foils; so that, with ease, + Or with a little shuffling, you may choose + A sword unbated, and in a pass of practise + Requite him for your father. + +LAERTES I will do't: + And, for that purpose, I'll anoint my sword. + I bought an unction of a mountebank, + So mortal that, but dip a knife in it, + Where it draws blood no cataplasm so rare, + Collected from all simples that have virtue + Under the moon, can save the thing from death + That is but scratch'd withal: I'll touch my point + With this contagion, that, if I gall him slightly, + It may be death. + +KING CLAUDIUS Let's further think of this; + Weigh what convenience both of time and means + May fit us to our shape: if this should fail, + And that our drift look through our bad performance, + 'Twere better not assay'd: therefore this project + Should have a back or second, that might hold, + If this should blast in proof. Soft! let me see: + We'll make a solemn wager on your cunnings: I ha't. + When in your motion you are hot and dry-- + As make your bouts more violent to that end-- + And that he calls for drink, I'll have prepared him + A chalice for the nonce, whereon but sipping, + If he by chance escape your venom'd stuck, + Our purpose may hold there. + + [Enter QUEEN GERTRUDE] + + How now, sweet queen! + +QUEEN GERTRUDE One woe doth tread upon another's heel, + So fast they follow; your sister's drown'd, Laertes. + +LAERTES Drown'd! O, where? + +QUEEN GERTRUDE There is a willow grows aslant a brook, + That shows his hoar leaves in the glassy stream; + There with fantastic garlands did she come + Of crow-flowers, nettles, daisies, and long purples + That liberal shepherds give a grosser name, + But our cold maids do dead men's fingers call them: + There, on the pendent boughs her coronet weeds + Clambering to hang, an envious sliver broke; + When down her weedy trophies and herself + Fell in the weeping brook. Her clothes spread wide; + And, mermaid-like, awhile they bore her up: + Which time she chanted snatches of old tunes; + As one incapable of her own distress, + Or like a creature native and indued + Unto that element: but long it could not be + Till that her garments, heavy with their drink, + Pull'd the poor wretch from her melodious lay + To muddy death. + +LAERTES Alas, then, she is drown'd? + +QUEEN GERTRUDE Drown'd, drown'd. + +LAERTES Too much of water hast thou, poor Ophelia, + And therefore I forbid my tears: but yet + It is our trick; nature her custom holds, + Let shame say what it will: when these are gone, + The woman will be out. Adieu, my lord: + I have a speech of fire, that fain would blaze, + But that this folly douts it. + + [Exit] + +KING CLAUDIUS Let's follow, Gertrude: + How much I had to do to calm his rage! + Now fear I this will give it start again; + Therefore let's follow. + + [Exeunt] + + + + + HAMLET + + +ACT V + + + +SCENE I A churchyard. + + + [Enter two Clowns, with spades, &c] + +First Clown Is she to be buried in Christian burial that + wilfully seeks her own salvation? + +Second Clown I tell thee she is: and therefore make her grave + straight: the crowner hath sat on her, and finds it + Christian burial. + +First Clown How can that be, unless she drowned herself in her + own defence? + +Second Clown Why, 'tis found so. + +First Clown It must be 'se offendendo;' it cannot be else. For + here lies the point: if I drown myself wittingly, + it argues an act: and an act hath three branches: it + is, to act, to do, to perform: argal, she drowned + herself wittingly. + +Second Clown Nay, but hear you, goodman delver,-- + +First Clown Give me leave. Here lies the water; good: here + stands the man; good; if the man go to this water, + and drown himself, it is, will he, nill he, he + goes,--mark you that; but if the water come to him + and drown him, he drowns not himself: argal, he + that is not guilty of his own death shortens not his own life. + +Second Clown But is this law? + +First Clown Ay, marry, is't; crowner's quest law. + +Second Clown Will you ha' the truth on't? If this had not been + a gentlewoman, she should have been buried out o' + Christian burial. + +First Clown Why, there thou say'st: and the more pity that + great folk should have countenance in this world to + drown or hang themselves, more than their even + Christian. Come, my spade. There is no ancient + gentleman but gardeners, ditchers, and grave-makers: + they hold up Adam's profession. + +Second Clown Was he a gentleman? + +First Clown He was the first that ever bore arms. + +Second Clown Why, he had none. + +First Clown What, art a heathen? How dost thou understand the + Scripture? The Scripture says 'Adam digged:' + could he dig without arms? I'll put another + question to thee: if thou answerest me not to the + purpose, confess thyself-- + +Second Clown Go to. + +First Clown What is he that builds stronger than either the + mason, the shipwright, or the carpenter? + +Second Clown The gallows-maker; for that frame outlives a + thousand tenants. + +First Clown I like thy wit well, in good faith: the gallows + does well; but how does it well? it does well to + those that do in: now thou dost ill to say the + gallows is built stronger than the church: argal, + the gallows may do well to thee. To't again, come. + +Second Clown 'Who builds stronger than a mason, a shipwright, or + a carpenter?' + +First Clown Ay, tell me that, and unyoke. + +Second Clown Marry, now I can tell. + +First Clown To't. + +Second Clown Mass, I cannot tell. + + [Enter HAMLET and HORATIO, at a distance] + +First Clown Cudgel thy brains no more about it, for your dull + ass will not mend his pace with beating; and, when + you are asked this question next, say 'a + grave-maker: 'the houses that he makes last till + doomsday. Go, get thee to Yaughan: fetch me a + stoup of liquor. + + [Exit Second Clown] + + [He digs and sings] + + In youth, when I did love, did love, + Methought it was very sweet, + To contract, O, the time, for, ah, my behove, + O, methought, there was nothing meet. + +HAMLET Has this fellow no feeling of his business, that he + sings at grave-making? + +HORATIO Custom hath made it in him a property of easiness. + +HAMLET 'Tis e'en so: the hand of little employment hath + the daintier sense. + +First Clown [Sings] + + But age, with his stealing steps, + Hath claw'd me in his clutch, + And hath shipped me intil the land, + As if I had never been such. + + [Throws up a skull] + +HAMLET That skull had a tongue in it, and could sing once: + how the knave jowls it to the ground, as if it were + Cain's jaw-bone, that did the first murder! It + might be the pate of a politician, which this ass + now o'er-reaches; one that would circumvent God, + might it not? + +HORATIO It might, my lord. + +HAMLET Or of a courtier; which could say 'Good morrow, + sweet lord! How dost thou, good lord?' This might + be my lord such-a-one, that praised my lord + such-a-one's horse, when he meant to beg it; might it not? + +HORATIO Ay, my lord. + +HAMLET Why, e'en so: and now my Lady Worm's; chapless, and + knocked about the mazzard with a sexton's spade: + here's fine revolution, an we had the trick to + see't. Did these bones cost no more the breeding, + but to play at loggats with 'em? mine ache to think on't. + +First Clown: [Sings] + + A pick-axe, and a spade, a spade, + For and a shrouding sheet: + O, a pit of clay for to be made + For such a guest is meet. + + [Throws up another skull] + +HAMLET There's another: why may not that be the skull of a + lawyer? Where be his quiddities now, his quillets, + his cases, his tenures, and his tricks? why does he + suffer this rude knave now to knock him about the + sconce with a dirty shovel, and will not tell him of + his action of battery? Hum! This fellow might be + in's time a great buyer of land, with his statutes, + his recognizances, his fines, his double vouchers, + his recoveries: is this the fine of his fines, and + the recovery of his recoveries, to have his fine + pate full of fine dirt? will his vouchers vouch him + no more of his purchases, and double ones too, than + the length and breadth of a pair of indentures? The + very conveyances of his lands will hardly lie in + this box; and must the inheritor himself have no more, ha? + +HORATIO Not a jot more, my lord. + +HAMLET Is not parchment made of sheepskins? + +HORATIO Ay, my lord, and of calf-skins too. + +HAMLET They are sheep and calves which seek out assurance + in that. I will speak to this fellow. Whose + grave's this, sirrah? + +First Clown Mine, sir. + + [Sings] + + O, a pit of clay for to be made + For such a guest is meet. + +HAMLET I think it be thine, indeed; for thou liest in't. + +First Clown You lie out on't, sir, and therefore it is not + yours: for my part, I do not lie in't, and yet it is mine. + +HAMLET 'Thou dost lie in't, to be in't and say it is thine: + 'tis for the dead, not for the quick; therefore thou liest. + +First Clown 'Tis a quick lie, sir; 'twill away gain, from me to + you. + +HAMLET What man dost thou dig it for? + +First Clown For no man, sir. + +HAMLET What woman, then? + +First Clown For none, neither. + +HAMLET Who is to be buried in't? + +First Clown One that was a woman, sir; but, rest her soul, she's dead. + +HAMLET How absolute the knave is! we must speak by the + card, or equivocation will undo us. By the Lord, + Horatio, these three years I have taken a note of + it; the age is grown so picked that the toe of the + peasant comes so near the heel of the courtier, he + gaffs his kibe. How long hast thou been a + grave-maker? + +First Clown Of all the days i' the year, I came to't that day + that our last king Hamlet overcame Fortinbras. + +HAMLET How long is that since? + +First Clown Cannot you tell that? every fool can tell that: it + was the very day that young Hamlet was born; he that + is mad, and sent into England. + +HAMLET Ay, marry, why was he sent into England? + +First Clown Why, because he was mad: he shall recover his wits + there; or, if he do not, it's no great matter there. + +HAMLET Why? + +First Clown 'Twill, a not be seen in him there; there the men + are as mad as he. + +HAMLET How came he mad? + +First Clown Very strangely, they say. + +HAMLET How strangely? + +First Clown Faith, e'en with losing his wits. + +HAMLET Upon what ground? + +First Clown Why, here in Denmark: I have been sexton here, man + and boy, thirty years. + +HAMLET How long will a man lie i' the earth ere he rot? + +First Clown I' faith, if he be not rotten before he die--as we + have many pocky corses now-a-days, that will scarce + hold the laying in--he will last you some eight year + or nine year: a tanner will last you nine year. + +HAMLET Why he more than another? + +First Clown Why, sir, his hide is so tanned with his trade, that + he will keep out water a great while; and your water + is a sore decayer of your whoreson dead body. + Here's a skull now; this skull has lain in the earth + three and twenty years. + +HAMLET Whose was it? + +First Clown A whoreson mad fellow's it was: whose do you think it was? + +HAMLET Nay, I know not. + +First Clown A pestilence on him for a mad rogue! a' poured a + flagon of Rhenish on my head once. This same skull, + sir, was Yorick's skull, the king's jester. + +HAMLET This? + +First Clown E'en that. + +HAMLET Let me see. + + [Takes the skull] + + Alas, poor Yorick! I knew him, Horatio: a fellow + of infinite jest, of most excellent fancy: he hath + borne me on his back a thousand times; and now, how + abhorred in my imagination it is! my gorge rims at + it. Here hung those lips that I have kissed I know + not how oft. Where be your gibes now? your + gambols? your songs? your flashes of merriment, + that were wont to set the table on a roar? Not one + now, to mock your own grinning? quite chap-fallen? + Now get you to my lady's chamber, and tell her, let + her paint an inch thick, to this favour she must + come; make her laugh at that. Prithee, Horatio, tell + me one thing. + +HORATIO What's that, my lord? + +HAMLET Dost thou think Alexander looked o' this fashion i' + the earth? + +HORATIO E'en so. + +HAMLET And smelt so? pah! + + [Puts down the skull] + +HORATIO E'en so, my lord. + +HAMLET To what base uses we may return, Horatio! Why may + not imagination trace the noble dust of Alexander, + till he find it stopping a bung-hole? + +HORATIO 'Twere to consider too curiously, to consider so. + +HAMLET No, faith, not a jot; but to follow him thither with + modesty enough, and likelihood to lead it: as + thus: Alexander died, Alexander was buried, + Alexander returneth into dust; the dust is earth; of + earth we make loam; and why of that loam, whereto he + was converted, might they not stop a beer-barrel? + Imperious Caesar, dead and turn'd to clay, + Might stop a hole to keep the wind away: + O, that that earth, which kept the world in awe, + Should patch a wall to expel the winter flaw! + But soft! but soft! aside: here comes the king. + + [Enter Priest, &c. in procession; the Corpse of + OPHELIA, LAERTES and Mourners following; KING + CLAUDIUS, QUEEN GERTRUDE, their trains, &c] + + The queen, the courtiers: who is this they follow? + And with such maimed rites? This doth betoken + The corse they follow did with desperate hand + Fordo its own life: 'twas of some estate. + Couch we awhile, and mark. + + [Retiring with HORATIO] + +LAERTES What ceremony else? + +HAMLET That is Laertes, + A very noble youth: mark. + +LAERTES What ceremony else? + +First Priest Her obsequies have been as far enlarged + As we have warrantise: her death was doubtful; + And, but that great command o'ersways the order, + She should in ground unsanctified have lodged + Till the last trumpet: for charitable prayers, + Shards, flints and pebbles should be thrown on her; + Yet here she is allow'd her virgin crants, + Her maiden strewments and the bringing home + Of bell and burial. + +LAERTES Must there no more be done? + +First Priest No more be done: + We should profane the service of the dead + To sing a requiem and such rest to her + As to peace-parted souls. + +LAERTES Lay her i' the earth: + And from her fair and unpolluted flesh + May violets spring! I tell thee, churlish priest, + A ministering angel shall my sister be, + When thou liest howling. + +HAMLET What, the fair Ophelia! + +QUEEN GERTRUDE Sweets to the sweet: farewell! + + [Scattering flowers] + + I hoped thou shouldst have been my Hamlet's wife; + I thought thy bride-bed to have deck'd, sweet maid, + And not have strew'd thy grave. + +LAERTES O, treble woe + Fall ten times treble on that cursed head, + Whose wicked deed thy most ingenious sense + Deprived thee of! Hold off the earth awhile, + Till I have caught her once more in mine arms: + + [Leaps into the grave] + + Now pile your dust upon the quick and dead, + Till of this flat a mountain you have made, + To o'ertop old Pelion, or the skyish head + Of blue Olympus. + +HAMLET [Advancing] What is he whose grief + Bears such an emphasis? whose phrase of sorrow + Conjures the wandering stars, and makes them stand + Like wonder-wounded hearers? This is I, + Hamlet the Dane. + + [Leaps into the grave] + +LAERTES The devil take thy soul! + + [Grappling with him] + +HAMLET Thou pray'st not well. + I prithee, take thy fingers from my throat; + For, though I am not splenitive and rash, + Yet have I something in me dangerous, + Which let thy wiseness fear: hold off thy hand. + +KING CLAUDIUS Pluck them asunder. + +QUEEN GERTRUDE Hamlet, Hamlet! + +All Gentlemen,-- + +HORATIO Good my lord, be quiet. + + [The Attendants part them, and they come out of the grave] + +HAMLET Why I will fight with him upon this theme + Until my eyelids will no longer wag. + +QUEEN GERTRUDE O my son, what theme? + +HAMLET I loved Ophelia: forty thousand brothers + Could not, with all their quantity of love, + Make up my sum. What wilt thou do for her? + +KING CLAUDIUS O, he is mad, Laertes. + +QUEEN GERTRUDE For love of God, forbear him. + +HAMLET 'Swounds, show me what thou'lt do: + Woo't weep? woo't fight? woo't fast? woo't tear thyself? + Woo't drink up eisel? eat a crocodile? + I'll do't. Dost thou come here to whine? + To outface me with leaping in her grave? + Be buried quick with her, and so will I: + And, if thou prate of mountains, let them throw + Millions of acres on us, till our ground, + Singeing his pate against the burning zone, + Make Ossa like a wart! Nay, an thou'lt mouth, + I'll rant as well as thou. + +QUEEN GERTRUDE This is mere madness: + And thus awhile the fit will work on him; + Anon, as patient as the female dove, + When that her golden couplets are disclosed, + His silence will sit drooping. + +HAMLET Hear you, sir; + What is the reason that you use me thus? + I loved you ever: but it is no matter; + Let Hercules himself do what he may, + The cat will mew and dog will have his day. + + [Exit] + +KING CLAUDIUS I pray you, good Horatio, wait upon him. + + [Exit HORATIO] + + [To LAERTES] + + Strengthen your patience in our last night's speech; + We'll put the matter to the present push. + Good Gertrude, set some watch over your son. + This grave shall have a living monument: + An hour of quiet shortly shall we see; + Till then, in patience our proceeding be. + + [Exeunt] + + + + HAMLET + + +ACT V + + + +SCENE II A hall in the castle. + + + [Enter HAMLET and HORATIO] + +HAMLET So much for this, sir: now shall you see the other; + You do remember all the circumstance? + +HORATIO Remember it, my lord? + +HAMLET Sir, in my heart there was a kind of fighting, + That would not let me sleep: methought I lay + Worse than the mutines in the bilboes. Rashly, + And praised be rashness for it, let us know, + Our indiscretion sometimes serves us well, + When our deep plots do pall: and that should teach us + There's a divinity that shapes our ends, + Rough-hew them how we will,-- + +HORATIO That is most certain. + +HAMLET Up from my cabin, + My sea-gown scarf'd about me, in the dark + Groped I to find out them; had my desire. + Finger'd their packet, and in fine withdrew + To mine own room again; making so bold, + My fears forgetting manners, to unseal + Their grand commission; where I found, Horatio,-- + O royal knavery!--an exact command, + Larded with many several sorts of reasons + Importing Denmark's health and England's too, + With, ho! such bugs and goblins in my life, + That, on the supervise, no leisure bated, + No, not to stay the grinding of the axe, + My head should be struck off. + +HORATIO Is't possible? + +HAMLET Here's the commission: read it at more leisure. + But wilt thou hear me how I did proceed? + +HORATIO I beseech you. + +HAMLET Being thus be-netted round with villanies,-- + Ere I could make a prologue to my brains, + They had begun the play--I sat me down, + Devised a new commission, wrote it fair: + I once did hold it, as our statists do, + A baseness to write fair and labour'd much + How to forget that learning, but, sir, now + It did me yeoman's service: wilt thou know + The effect of what I wrote? + +HORATIO Ay, good my lord. + +HAMLET An earnest conjuration from the king, + As England was his faithful tributary, + As love between them like the palm might flourish, + As peace should stiff her wheaten garland wear + And stand a comma 'tween their amities, + And many such-like 'As'es of great charge, + That, on the view and knowing of these contents, + Without debatement further, more or less, + He should the bearers put to sudden death, + Not shriving-time allow'd. + +HORATIO How was this seal'd? + +HAMLET Why, even in that was heaven ordinant. + I had my father's signet in my purse, + Which was the model of that Danish seal; + Folded the writ up in form of the other, + Subscribed it, gave't the impression, placed it safely, + The changeling never known. Now, the next day + Was our sea-fight; and what to this was sequent + Thou know'st already. + +HORATIO So Guildenstern and Rosencrantz go to't. + +HAMLET Why, man, they did make love to this employment; + They are not near my conscience; their defeat + Does by their own insinuation grow: + 'Tis dangerous when the baser nature comes + Between the pass and fell incensed points + Of mighty opposites. + +HORATIO Why, what a king is this! + +HAMLET Does it not, think'st thee, stand me now upon-- + He that hath kill'd my king and whored my mother, + Popp'd in between the election and my hopes, + Thrown out his angle for my proper life, + And with such cozenage--is't not perfect conscience, + To quit him with this arm? and is't not to be damn'd, + To let this canker of our nature come + In further evil? + +HORATIO It must be shortly known to him from England + What is the issue of the business there. + +HAMLET It will be short: the interim is mine; + And a man's life's no more than to say 'One.' + But I am very sorry, good Horatio, + That to Laertes I forgot myself; + For, by the image of my cause, I see + The portraiture of his: I'll court his favours. + But, sure, the bravery of his grief did put me + Into a towering passion. + +HORATIO Peace! who comes here? + + [Enter OSRIC] + +OSRIC Your lordship is right welcome back to Denmark. + +HAMLET I humbly thank you, sir. Dost know this water-fly? + +HORATIO No, my good lord. + +HAMLET Thy state is the more gracious; for 'tis a vice to + know him. He hath much land, and fertile: let a + beast be lord of beasts, and his crib shall stand at + the king's mess: 'tis a chough; but, as I say, + spacious in the possession of dirt. + +OSRIC Sweet lord, if your lordship were at leisure, I + should impart a thing to you from his majesty. + +HAMLET I will receive it, sir, with all diligence of + spirit. Put your bonnet to his right use; 'tis for the head. + +OSRIC I thank your lordship, it is very hot. + +HAMLET No, believe me, 'tis very cold; the wind is + northerly. + +OSRIC It is indifferent cold, my lord, indeed. + +HAMLET But yet methinks it is very sultry and hot for my + complexion. + +OSRIC Exceedingly, my lord; it is very sultry,--as + 'twere,--I cannot tell how. But, my lord, his + majesty bade me signify to you that he has laid a + great wager on your head: sir, this is the matter,-- + +HAMLET I beseech you, remember-- + + [HAMLET moves him to put on his hat] + +OSRIC Nay, good my lord; for mine ease, in good faith. + Sir, here is newly come to court Laertes; believe + me, an absolute gentleman, full of most excellent + differences, of very soft society and great showing: + indeed, to speak feelingly of him, he is the card or + calendar of gentry, for you shall find in him the + continent of what part a gentleman would see. + +HAMLET Sir, his definement suffers no perdition in you; + though, I know, to divide him inventorially would + dizzy the arithmetic of memory, and yet but yaw + neither, in respect of his quick sail. But, in the + verity of extolment, I take him to be a soul of + great article; and his infusion of such dearth and + rareness, as, to make true diction of him, his + semblable is his mirror; and who else would trace + him, his umbrage, nothing more. + +OSRIC Your lordship speaks most infallibly of him. + +HAMLET The concernancy, sir? why do we wrap the gentleman + in our more rawer breath? + +OSRIC Sir? + +HORATIO Is't not possible to understand in another tongue? + You will do't, sir, really. + +HAMLET What imports the nomination of this gentleman? + +OSRIC Of Laertes? + +HORATIO His purse is empty already; all's golden words are spent. + +HAMLET Of him, sir. + +OSRIC I know you are not ignorant-- + +HAMLET I would you did, sir; yet, in faith, if you did, + it would not much approve me. Well, sir? + +OSRIC You are not ignorant of what excellence Laertes is-- + +HAMLET I dare not confess that, lest I should compare with + him in excellence; but, to know a man well, were to + know himself. + +OSRIC I mean, sir, for his weapon; but in the imputation + laid on him by them, in his meed he's unfellowed. + +HAMLET What's his weapon? + +OSRIC Rapier and dagger. + +HAMLET That's two of his weapons: but, well. + +OSRIC The king, sir, hath wagered with him six Barbary + horses: against the which he has imponed, as I take + it, six French rapiers and poniards, with their + assigns, as girdle, hangers, and so: three of the + carriages, in faith, are very dear to fancy, very + responsive to the hilts, most delicate carriages, + and of very liberal conceit. + +HAMLET What call you the carriages? + +HORATIO I knew you must be edified by the margent ere you had done. + +OSRIC The carriages, sir, are the hangers. + +HAMLET The phrase would be more german to the matter, if we + could carry cannon by our sides: I would it might + be hangers till then. But, on: six Barbary horses + against six French swords, their assigns, and three + liberal-conceited carriages; that's the French bet + against the Danish. Why is this 'imponed,' as you call it? + +OSRIC The king, sir, hath laid, that in a dozen passes + between yourself and him, he shall not exceed you + three hits: he hath laid on twelve for nine; and it + would come to immediate trial, if your lordship + would vouchsafe the answer. + +HAMLET How if I answer 'no'? + +OSRIC I mean, my lord, the opposition of your person in trial. + +HAMLET Sir, I will walk here in the hall: if it please his + majesty, 'tis the breathing time of day with me; let + the foils be brought, the gentleman willing, and the + king hold his purpose, I will win for him an I can; + if not, I will gain nothing but my shame and the odd hits. + +OSRIC Shall I re-deliver you e'en so? + +HAMLET To this effect, sir; after what flourish your nature will. + +OSRIC I commend my duty to your lordship. + +HAMLET Yours, yours. + + [Exit OSRIC] + + He does well to commend it himself; there are no + tongues else for's turn. + +HORATIO This lapwing runs away with the shell on his head. + +HAMLET He did comply with his dug, before he sucked it. + Thus has he--and many more of the same bevy that I + know the dressy age dotes on--only got the tune of + the time and outward habit of encounter; a kind of + yesty collection, which carries them through and + through the most fond and winnowed opinions; and do + but blow them to their trial, the bubbles are out. + + [Enter a Lord] + +Lord My lord, his majesty commended him to you by young + Osric, who brings back to him that you attend him in + the hall: he sends to know if your pleasure hold to + play with Laertes, or that you will take longer time. + +HAMLET I am constant to my purpose; they follow the king's + pleasure: if his fitness speaks, mine is ready; now + or whensoever, provided I be so able as now. + +Lord The king and queen and all are coming down. + +HAMLET In happy time. + +Lord The queen desires you to use some gentle + entertainment to Laertes before you fall to play. + +HAMLET She well instructs me. + + [Exit Lord] + +HORATIO You will lose this wager, my lord. + +HAMLET I do not think so: since he went into France, I + have been in continual practise: I shall win at the + odds. But thou wouldst not think how ill all's here + about my heart: but it is no matter. + +HORATIO Nay, good my lord,-- + +HAMLET It is but foolery; but it is such a kind of + gain-giving, as would perhaps trouble a woman. + +HORATIO If your mind dislike any thing, obey it: I will + forestall their repair hither, and say you are not + fit. + +HAMLET Not a whit, we defy augury: there's a special + providence in the fall of a sparrow. If it be now, + 'tis not to come; if it be not to come, it will be + now; if it be not now, yet it will come: the + readiness is all: since no man has aught of what he + leaves, what is't to leave betimes? + + [Enter KING CLAUDIUS, QUEEN GERTRUDE, LAERTES, + Lords, OSRIC, and Attendants with foils, &c] + +KING CLAUDIUS Come, Hamlet, come, and take this hand from me. + + [KING CLAUDIUS puts LAERTES' hand into HAMLET's] + +HAMLET Give me your pardon, sir: I've done you wrong; + But pardon't, as you are a gentleman. + This presence knows, + And you must needs have heard, how I am punish'd + With sore distraction. What I have done, + That might your nature, honour and exception + Roughly awake, I here proclaim was madness. + Was't Hamlet wrong'd Laertes? Never Hamlet: + If Hamlet from himself be ta'en away, + And when he's not himself does wrong Laertes, + Then Hamlet does it not, Hamlet denies it. + Who does it, then? His madness: if't be so, + Hamlet is of the faction that is wrong'd; + His madness is poor Hamlet's enemy. + Sir, in this audience, + Let my disclaiming from a purposed evil + Free me so far in your most generous thoughts, + That I have shot mine arrow o'er the house, + And hurt my brother. + +LAERTES I am satisfied in nature, + Whose motive, in this case, should stir me most + To my revenge: but in my terms of honour + I stand aloof; and will no reconcilement, + Till by some elder masters, of known honour, + I have a voice and precedent of peace, + To keep my name ungored. But till that time, + I do receive your offer'd love like love, + And will not wrong it. + +HAMLET I embrace it freely; + And will this brother's wager frankly play. + Give us the foils. Come on. + +LAERTES Come, one for me. + +HAMLET I'll be your foil, Laertes: in mine ignorance + Your skill shall, like a star i' the darkest night, + Stick fiery off indeed. + +LAERTES You mock me, sir. + +HAMLET No, by this hand. + +KING CLAUDIUS Give them the foils, young Osric. Cousin Hamlet, + You know the wager? + +HAMLET Very well, my lord + Your grace hath laid the odds o' the weaker side. + +KING CLAUDIUS I do not fear it; I have seen you both: + But since he is better'd, we have therefore odds. + +LAERTES This is too heavy, let me see another. + +HAMLET This likes me well. These foils have all a length? + + [They prepare to play] + +OSRIC Ay, my good lord. + +KING CLAUDIUS Set me the stoops of wine upon that table. + If Hamlet give the first or second hit, + Or quit in answer of the third exchange, + Let all the battlements their ordnance fire: + The king shall drink to Hamlet's better breath; + And in the cup an union shall he throw, + Richer than that which four successive kings + In Denmark's crown have worn. Give me the cups; + And let the kettle to the trumpet speak, + The trumpet to the cannoneer without, + The cannons to the heavens, the heavens to earth, + 'Now the king dunks to Hamlet.' Come, begin: + And you, the judges, bear a wary eye. + +HAMLET Come on, sir. + +LAERTES Come, my lord. + + [They play] + +HAMLET One. + +LAERTES No. + +HAMLET Judgment. + +OSRIC A hit, a very palpable hit. + +LAERTES Well; again. + +KING CLAUDIUS Stay; give me drink. Hamlet, this pearl is thine; + Here's to thy health. + + [Trumpets sound, and cannon shot off within] + + Give him the cup. + +HAMLET I'll play this bout first; set it by awhile. Come. + + [They play] + + Another hit; what say you? + +LAERTES A touch, a touch, I do confess. + +KING CLAUDIUS Our son shall win. + +QUEEN GERTRUDE He's fat, and scant of breath. + Here, Hamlet, take my napkin, rub thy brows; + The queen carouses to thy fortune, Hamlet. + +HAMLET Good madam! + +KING CLAUDIUS Gertrude, do not drink. + +QUEEN GERTRUDE I will, my lord; I pray you, pardon me. + +KING CLAUDIUS [Aside] It is the poison'd cup: it is too late. + +HAMLET I dare not drink yet, madam; by and by. + +QUEEN GERTRUDE Come, let me wipe thy face. + +LAERTES My lord, I'll hit him now. + +KING CLAUDIUS I do not think't. + +LAERTES [Aside] And yet 'tis almost 'gainst my conscience. + +HAMLET Come, for the third, Laertes: you but dally; + I pray you, pass with your best violence; + I am afeard you make a wanton of me. + +LAERTES Say you so? come on. + + [They play] + +OSRIC Nothing, neither way. + +LAERTES Have at you now! + + [LAERTES wounds HAMLET; then in scuffling, they + change rapiers, and HAMLET wounds LAERTES] + +KING CLAUDIUS Part them; they are incensed. + +HAMLET Nay, come, again. + + [QUEEN GERTRUDE falls] + +OSRIC Look to the queen there, ho! + +HORATIO They bleed on both sides. How is it, my lord? + +OSRIC How is't, Laertes? + +LAERTES Why, as a woodcock to mine own springe, Osric; + I am justly kill'd with mine own treachery. + +HAMLET How does the queen? + +KING CLAUDIUS She swounds to see them bleed. + +QUEEN GERTRUDE No, no, the drink, the drink,--O my dear Hamlet,-- + The drink, the drink! I am poison'd. + + [Dies] + +HAMLET O villany! Ho! let the door be lock'd: + Treachery! Seek it out. + +LAERTES It is here, Hamlet: Hamlet, thou art slain; + No medicine in the world can do thee good; + In thee there is not half an hour of life; + The treacherous instrument is in thy hand, + Unbated and envenom'd: the foul practise + Hath turn'd itself on me lo, here I lie, + Never to rise again: thy mother's poison'd: + I can no more: the king, the king's to blame. + +HAMLET The point!--envenom'd too! + Then, venom, to thy work. + + [Stabs KING CLAUDIUS] + +All Treason! treason! + +KING CLAUDIUS O, yet defend me, friends; I am but hurt. + +HAMLET Here, thou incestuous, murderous, damned Dane, + Drink off this potion. Is thy union here? + Follow my mother. + + [KING CLAUDIUS dies] + +LAERTES He is justly served; + It is a poison temper'd by himself. + Exchange forgiveness with me, noble Hamlet: + Mine and my father's death come not upon thee, + Nor thine on me. + + [Dies] + +HAMLET Heaven make thee free of it! I follow thee. + I am dead, Horatio. Wretched queen, adieu! + You that look pale and tremble at this chance, + That are but mutes or audience to this act, + Had I but time--as this fell sergeant, death, + Is strict in his arrest--O, I could tell you-- + But let it be. Horatio, I am dead; + Thou livest; report me and my cause aright + To the unsatisfied. + +HORATIO Never believe it: + I am more an antique Roman than a Dane: + Here's yet some liquor left. + +HAMLET As thou'rt a man, + Give me the cup: let go; by heaven, I'll have't. + O good Horatio, what a wounded name, + Things standing thus unknown, shall live behind me! + If thou didst ever hold me in thy heart + Absent thee from felicity awhile, + And in this harsh world draw thy breath in pain, + To tell my story. + + [March afar off, and shot within] + + What warlike noise is this? + +OSRIC Young Fortinbras, with conquest come from Poland, + To the ambassadors of England gives + This warlike volley. + +HAMLET O, I die, Horatio; + The potent poison quite o'er-crows my spirit: + I cannot live to hear the news from England; + But I do prophesy the election lights + On Fortinbras: he has my dying voice; + So tell him, with the occurrents, more and less, + Which have solicited. The rest is silence. + + [Dies] + +HORATIO Now cracks a noble heart. Good night sweet prince: + And flights of angels sing thee to thy rest! + Why does the drum come hither? + + [March within] + + [Enter FORTINBRAS, the English Ambassadors, + and others] + +PRINCE FORTINBRAS Where is this sight? + +HORATIO What is it ye would see? + If aught of woe or wonder, cease your search. + +PRINCE FORTINBRAS This quarry cries on havoc. O proud death, + What feast is toward in thine eternal cell, + That thou so many princes at a shot + So bloodily hast struck? + +First Ambassador The sight is dismal; + And our affairs from England come too late: + The ears are senseless that should give us hearing, + To tell him his commandment is fulfill'd, + That Rosencrantz and Guildenstern are dead: + Where should we have our thanks? + +HORATIO Not from his mouth, + Had it the ability of life to thank you: + He never gave commandment for their death. + But since, so jump upon this bloody question, + You from the Polack wars, and you from England, + Are here arrived give order that these bodies + High on a stage be placed to the view; + And let me speak to the yet unknowing world + How these things came about: so shall you hear + Of carnal, bloody, and unnatural acts, + Of accidental judgments, casual slaughters, + Of deaths put on by cunning and forced cause, + And, in this upshot, purposes mistook + Fall'n on the inventors' reads: all this can I + Truly deliver. + +PRINCE FORTINBRAS Let us haste to hear it, + And call the noblest to the audience. + For me, with sorrow I embrace my fortune: + I have some rights of memory in this kingdom, + Which now to claim my vantage doth invite me. + +HORATIO Of that I shall have also cause to speak, + And from his mouth whose voice will draw on more; + But let this same be presently perform'd, + Even while men's minds are wild; lest more mischance + On plots and errors, happen. + +PRINCE FORTINBRAS Let four captains + Bear Hamlet, like a soldier, to the stage; + For he was likely, had he been put on, + To have proved most royally: and, for his passage, + The soldiers' music and the rites of war + Speak loudly for him. + Take up the bodies: such a sight as this + Becomes the field, but here shows much amiss. + Go, bid the soldiers shoot. + + [A dead march. Exeunt, bearing off the dead + bodies; after which a peal of ordnance is shot off] + + diff --git a/datastructures-validation/build.gradle b/datastructures-validation/build.gradle new file mode 100644 index 0000000..340f8a3 --- /dev/null +++ b/datastructures-validation/build.gradle @@ -0,0 +1,5 @@ +dependencies { + api "org.xbib:jsr-305:${project.property('jsr305.version')}" + testImplementation "org.assertj:assertj-core:${project.property('assertj.version')}" + testImplementation "com.google.testing.compile:compile-testing:${project.property('google-testing.version')}" +} \ No newline at end of file diff --git a/datastructures-validation/src/main/java/module-info.java b/datastructures-validation/src/main/java/module-info.java new file mode 100644 index 0000000..0080eeb --- /dev/null +++ b/datastructures-validation/src/main/java/module-info.java @@ -0,0 +1,4 @@ +module org.xbib.datastructures.validation { + requires org.xbib.annotation; + requires java.compiler; +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments.java new file mode 100644 index 0000000..44a6de3 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments.java @@ -0,0 +1,117 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +public final class Arguments { + public static Arguments1 of(@Nullable A1 arg1) { + return new Arguments1<>(arg1); + } + + public static Arguments2 of(@Nullable A1 arg1, @Nullable A2 arg2) { + return new Arguments2<>(arg1, arg2); + } + + public static Arguments3 of(@Nullable A1 arg1, + @Nullable A2 arg2, @Nullable A3 arg3) { + return new Arguments3<>(arg1, arg2, arg3); + } + + public static Arguments4 of(@Nullable A1 arg1, + @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4) { + return new Arguments4<>(arg1, arg2, arg3, arg4); + } + + public static Arguments5 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5) { + return new Arguments5<>(arg1, arg2, arg3, arg4, arg5); + } + + public static Arguments6 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6) { + return new Arguments6<>(arg1, arg2, arg3, arg4, arg5, arg6); + } + + public static Arguments7 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7) { + return new Arguments7<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + public static Arguments8 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8) { + return new Arguments8<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + } + + public static Arguments9 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9) { + return new Arguments9<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); + } + + public static Arguments10 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9, @Nullable A10 arg10) { + return new Arguments10<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, + arg10); + } + + public static Arguments11 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9, @Nullable A10 arg10, @Nullable A11 arg11) { + return new Arguments11<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, + arg10, arg11); + } + + public static Arguments12 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9, @Nullable A10 arg10, @Nullable A11 arg11, + @Nullable A12 arg12) { + return new Arguments12<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, + arg10, arg11, arg12); + } + + public static Arguments13 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9, @Nullable A10 arg10, @Nullable A11 arg11, + @Nullable A12 arg12, @Nullable A13 arg13) { + return new Arguments13<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, + arg10, arg11, arg12, arg13); + } + + public static Arguments14 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9, @Nullable A10 arg10, @Nullable A11 arg11, + @Nullable A12 arg12, @Nullable A13 arg13, @Nullable A14 arg14) { + return new Arguments14<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, + arg10, arg11, arg12, arg13, arg14); + } + + public static Arguments15 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9, @Nullable A10 arg10, @Nullable A11 arg11, + @Nullable A12 arg12, @Nullable A13 arg13, @Nullable A14 arg14, + @Nullable A15 arg15) { + return new Arguments15<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, + arg10, arg11, arg12, arg13, arg14, arg15); + } + + public static Arguments16 of( + @Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9, @Nullable A10 arg10, @Nullable A11 arg11, + @Nullable A12 arg12, @Nullable A13 arg13, @Nullable A14 arg14, + @Nullable A15 arg15, @Nullable A16 arg16) { + return new Arguments16<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, + arg10, arg11, arg12, arg13, arg14, arg15, arg16); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments1.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments1.java new file mode 100644 index 0000000..1635cf4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments1.java @@ -0,0 +1,22 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function1; +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class Arguments1 { + + protected final A1 arg1; + + Arguments1(@Nullable A1 arg1) { + this.arg1 = arg1; + } + + @Nullable + public final A1 arg1() { + return this.arg1; + } + + public final X map(Function1 mapper) { + return mapper.apply(arg1); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10.java new file mode 100644 index 0000000..2b7f698 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function10; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments10 + extends Arguments9 { + + protected final A10 arg10; + + Arguments10(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, + @Nullable A4 arg4, @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, + @Nullable A8 arg8, @Nullable A9 arg9, @Nullable A10 arg10) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); + this.arg10 = arg10; + } + + @Nullable + public final A10 arg10() { + return this.arg10; + } + + public final X map( + Function10 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Combining.java new file mode 100644 index 0000000..c392c27 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Combining.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function10; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments10Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + public Arguments10Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + } + + public Arguments1Validator apply( + Function10 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext), + this.v9.validate(a, locale, constraintContext), + this.v10.validate(a, locale, constraintContext)); + } + + public Arguments11Combining combine( + ValueValidator v11) { + return new Arguments11Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Splitting.java new file mode 100644 index 0000000..d865299 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Splitting.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function10; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments10Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + public Arguments10Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + } + + public Arguments10Validator apply( + Function10 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, locale, + constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext), + this.v9.validate(a9, locale, constraintContext), + this.v10.validate(a10, locale, constraintContext)); + } + + public Arguments11Splitting split( + ValueValidator v11) { + return new Arguments11Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Validator.java new file mode 100644 index 0000000..6814ecc --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments10Validator.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments10Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, Locale locale, + ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments10Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, locale, + constraintContext) -> Arguments10Validator.this.validate(a1, a2, a3, a4, + a5, a6, a7, a8, a9, a10, locale, constraintContext).map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments10Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, locale, + constraintContext) -> Arguments10Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, locale, + constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments10 args = mapper + .apply(a); + return Arguments10Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), args.arg9(), args.arg10(), locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments10Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, Locale.getDefault(), + ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, + ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, Locale.getDefault(), + constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, locale, + ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, Locale locale) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, locale, + constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11.java new file mode 100644 index 0000000..ba18beb --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function11; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments11 + extends Arguments10 { + + protected final A11 arg11; + + Arguments11(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, + @Nullable A4 arg4, @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, + @Nullable A8 arg8, @Nullable A9 arg9, @Nullable A10 arg10, + @Nullable A11 arg11) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); + this.arg11 = arg11; + } + + @Nullable + public final A11 arg11() { + return this.arg11; + } + + public final X map( + Function11 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Combining.java new file mode 100644 index 0000000..ff0305f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Combining.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function11; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments11Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + public Arguments11Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + } + + public Arguments1Validator apply( + Function11 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext), + this.v9.validate(a, locale, constraintContext), + this.v10.validate(a, locale, constraintContext), + this.v11.validate(a, locale, constraintContext)); + } + + public Arguments12Combining combine( + ValueValidator v12) { + return new Arguments12Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Splitting.java new file mode 100644 index 0000000..e0971b8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Splitting.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function11; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments11Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + public Arguments11Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + } + + public Arguments11Validator apply( + Function11 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, locale, + constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext), + this.v9.validate(a9, locale, constraintContext), + this.v10.validate(a10, locale, constraintContext), + this.v11.validate(a11, locale, constraintContext)); + } + + public Arguments12Splitting split( + ValueValidator v12) { + return new Arguments12Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Validator.java new file mode 100644 index 0000000..6238874 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments11Validator.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments11Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + Locale locale, ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments11Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, locale, + constraintContext) -> Arguments11Validator.this.validate(a1, a2, a3, a4, + a5, a6, a7, a8, a9, a10, a11, locale, constraintContext) + .map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments11Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, locale, + constraintContext) -> Arguments11Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, locale, + constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments11 args = mapper + .apply(a); + return Arguments11Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), args.arg9(), args.arg10(), args.arg11(), locale, + constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments11Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, + Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, + Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, locale, + ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + Locale locale) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + Locale locale, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, locale, + constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12.java new file mode 100644 index 0000000..924b43e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function12; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments12 + extends Arguments11 { + + protected final A12 arg12; + + Arguments12(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, + @Nullable A4 arg4, @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, + @Nullable A8 arg8, @Nullable A9 arg9, @Nullable A10 arg10, + @Nullable A11 arg11, @Nullable A12 arg12) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); + this.arg12 = arg12; + } + + @Nullable + public final A12 arg12() { + return this.arg12; + } + + public final X map( + Function12 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Combining.java new file mode 100644 index 0000000..50beef1 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Combining.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function12; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments12Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + public Arguments12Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + } + + public Arguments1Validator apply( + Function12 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext), + this.v9.validate(a, locale, constraintContext), + this.v10.validate(a, locale, constraintContext), + this.v11.validate(a, locale, constraintContext), + this.v12.validate(a, locale, constraintContext)); + } + + public Arguments13Combining combine( + ValueValidator v13) { + return new Arguments13Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Splitting.java new file mode 100644 index 0000000..0b86e38 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Splitting.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function12; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments12Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + public Arguments12Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + } + + public Arguments12Validator apply( + Function12 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, locale, + constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext), + this.v9.validate(a9, locale, constraintContext), + this.v10.validate(a10, locale, constraintContext), + this.v11.validate(a11, locale, constraintContext), + this.v12.validate(a12, locale, constraintContext)); + } + + public Arguments13Splitting split( + ValueValidator v13) { + return new Arguments13Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Validator.java new file mode 100644 index 0000000..940ff11 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments12Validator.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments12Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, Locale locale, ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments12Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, locale, + constraintContext) -> Arguments12Validator.this.validate(a1, a2, a3, a4, + a5, a6, a7, a8, a9, a10, a11, a12, locale, constraintContext) + .map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments12Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, locale, + constraintContext) -> Arguments12Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments12 args = mapper + .apply(a); + return Arguments12Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), args.arg9(), args.arg10(), args.arg11(), args.arg12(), + locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments12Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, locale, + ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, Locale locale) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, Locale locale, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, locale, + constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13.java new file mode 100644 index 0000000..44ec1d0 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function13; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments13 + extends Arguments12 { + + protected final A13 arg13; + + Arguments13(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, + @Nullable A4 arg4, @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, + @Nullable A8 arg8, @Nullable A9 arg9, @Nullable A10 arg10, + @Nullable A11 arg11, @Nullable A12 arg12, @Nullable A13 arg13) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); + this.arg13 = arg13; + } + + @Nullable + public final A13 arg13() { + return this.arg13; + } + + public final X map( + Function13 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12, arg13); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Combining.java new file mode 100644 index 0000000..ec90fbb --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Combining.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function13; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments13Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + protected final ValueValidator v13; + + public Arguments13Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + } + + public Arguments1Validator apply( + Function13 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext), + this.v9.validate(a, locale, constraintContext), + this.v10.validate(a, locale, constraintContext), + this.v11.validate(a, locale, constraintContext), + this.v12.validate(a, locale, constraintContext), + this.v13.validate(a, locale, constraintContext)); + } + + public Arguments14Combining combine( + ValueValidator v14) { + return new Arguments14Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Splitting.java new file mode 100644 index 0000000..0f1dac4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Splitting.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function13; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments13Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + protected final ValueValidator v13; + + public Arguments13Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + } + + public Arguments13Validator apply( + Function13 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, locale, + constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext), + this.v9.validate(a9, locale, constraintContext), + this.v10.validate(a10, locale, constraintContext), + this.v11.validate(a11, locale, constraintContext), + this.v12.validate(a12, locale, constraintContext), + this.v13.validate(a13, locale, constraintContext)); + } + + public Arguments14Splitting split( + ValueValidator v14) { + return new Arguments14Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Validator.java new file mode 100644 index 0000000..776fdce --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments13Validator.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments13Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, Locale locale, + ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments13Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, locale, + constraintContext) -> Arguments13Validator.this.validate(a1, a2, a3, a4, + a5, a6, a7, a8, a9, a10, a11, a12, a13, locale, constraintContext) + .map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments13Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, locale, + constraintContext) -> Arguments13Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments13 args = mapper + .apply(a); + return Arguments13Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), args.arg9(), args.arg10(), args.arg11(), args.arg12(), + args.arg13(), locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments13Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, Locale locale) + throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, locale, + constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14.java new file mode 100644 index 0000000..7f16cbf --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function14; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments14 + extends Arguments13 { + + protected final A14 arg14; + + Arguments14(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, + @Nullable A4 arg4, @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, + @Nullable A8 arg8, @Nullable A9 arg9, @Nullable A10 arg10, + @Nullable A11 arg11, @Nullable A12 arg12, @Nullable A13 arg13, + @Nullable A14 arg14) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13); + this.arg14 = arg14; + } + + @Nullable + public final A14 arg14() { + return this.arg14; + } + + public final X map( + Function14 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12, arg13, arg14); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Combining.java new file mode 100644 index 0000000..c8b277f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Combining.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function14; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments14Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + protected final ValueValidator v13; + + protected final ValueValidator v14; + + public Arguments14Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + } + + public Arguments1Validator apply( + Function14 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext), + this.v9.validate(a, locale, constraintContext), + this.v10.validate(a, locale, constraintContext), + this.v11.validate(a, locale, constraintContext), + this.v12.validate(a, locale, constraintContext), + this.v13.validate(a, locale, constraintContext), + this.v14.validate(a, locale, constraintContext)); + } + + public Arguments15Combining combine( + ValueValidator v15) { + return new Arguments15Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Splitting.java new file mode 100644 index 0000000..825b704 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Splitting.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function14; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments14Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + protected final ValueValidator v13; + + protected final ValueValidator v14; + + public Arguments14Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + } + + public Arguments14Validator apply( + Function14 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, locale, + constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext), + this.v9.validate(a9, locale, constraintContext), + this.v10.validate(a10, locale, constraintContext), + this.v11.validate(a11, locale, constraintContext), + this.v12.validate(a12, locale, constraintContext), + this.v13.validate(a13, locale, constraintContext), + this.v14.validate(a14, locale, constraintContext)); + } + + public Arguments15Splitting split( + ValueValidator v15) { + return new Arguments15Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Validator.java new file mode 100644 index 0000000..854bf0e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments14Validator.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments14Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, Locale locale, + ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments14Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, locale, + constraintContext) -> Arguments14Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + a14, locale, constraintContext) + .map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments14Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, locale, + constraintContext) -> Arguments14Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + a14, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments14 args = mapper + .apply(a); + return Arguments14Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), args.arg9(), args.arg10(), args.arg11(), args.arg12(), + args.arg13(), args.arg14(), locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments14Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, + ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, Locale locale) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + locale).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15.java new file mode 100644 index 0000000..876a3c3 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function15; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments15 + extends Arguments14 { + + protected final A15 arg15; + + Arguments15(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, + @Nullable A4 arg4, @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, + @Nullable A8 arg8, @Nullable A9 arg9, @Nullable A10 arg10, + @Nullable A11 arg11, @Nullable A12 arg12, @Nullable A13 arg13, + @Nullable A14 arg14, @Nullable A15 arg15) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13, arg14); + this.arg15 = arg15; + } + + @Nullable + public final A15 arg15() { + return this.arg15; + } + + public final X map( + Function15 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12, arg13, arg14, arg15); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Combining.java new file mode 100644 index 0000000..03d6de3 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Combining.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function15; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments15Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + protected final ValueValidator v13; + + protected final ValueValidator v14; + + protected final ValueValidator v15; + + public Arguments15Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14, + ValueValidator v15) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + this.v15 = v15; + } + + public Arguments1Validator apply( + Function15 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext), + this.v9.validate(a, locale, constraintContext), + this.v10.validate(a, locale, constraintContext), + this.v11.validate(a, locale, constraintContext), + this.v12.validate(a, locale, constraintContext), + this.v13.validate(a, locale, constraintContext), + this.v14.validate(a, locale, constraintContext), + this.v15.validate(a, locale, constraintContext)); + } + + public Arguments16Combining combine( + ValueValidator v16) { + return new Arguments16Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Splitting.java new file mode 100644 index 0000000..a12be42 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Splitting.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function15; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments15Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + protected final ValueValidator v13; + + protected final ValueValidator v14; + + protected final ValueValidator v15; + + public Arguments15Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14, + ValueValidator v15) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + this.v15 = v15; + } + + public Arguments15Validator apply( + Function15 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, locale, + constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext), + this.v9.validate(a9, locale, constraintContext), + this.v10.validate(a10, locale, constraintContext), + this.v11.validate(a11, locale, constraintContext), + this.v12.validate(a12, locale, constraintContext), + this.v13.validate(a13, locale, constraintContext), + this.v14.validate(a14, locale, constraintContext), + this.v15.validate(a15, locale, constraintContext)); + } + + public Arguments16Splitting split( + ValueValidator v16) { + return new Arguments16Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Validator.java new file mode 100644 index 0000000..414685e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments15Validator.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments15Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + Locale locale, ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments15Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, locale, + constraintContext) -> Arguments15Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + a14, a15, locale, constraintContext) + .map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments15Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, locale, + constraintContext) -> Arguments15Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + a14, a15, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments15 args = mapper + .apply(a); + return Arguments15Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), args.arg9(), args.arg10(), args.arg11(), args.arg12(), + args.arg13(), args.arg14(), args.arg15(), locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments15Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + Locale locale) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, locale).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + Locale locale, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16.java new file mode 100644 index 0000000..00ad5bb --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function16; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments16 + extends + Arguments15 { + + protected final A16 arg16; + + Arguments16(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, + @Nullable A4 arg4, @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, + @Nullable A8 arg8, @Nullable A9 arg9, @Nullable A10 arg10, + @Nullable A11 arg11, @Nullable A12 arg12, @Nullable A13 arg13, + @Nullable A14 arg14, @Nullable A15 arg15, @Nullable A16 arg16) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13, arg14, arg15); + this.arg16 = arg16; + } + + @Nullable + public final A16 arg16() { + return this.arg16; + } + + public final X map( + Function16 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12, arg13, arg14, arg15, arg16); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Combining.java new file mode 100644 index 0000000..24e1e38 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Combining.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function16; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments16Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + protected final ValueValidator v13; + + protected final ValueValidator v14; + + protected final ValueValidator v15; + + protected final ValueValidator v16; + + public Arguments16Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14, + ValueValidator v15, + ValueValidator v16) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + this.v15 = v15; + this.v16 = v16; + } + + public Arguments1Validator apply( + Function16 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext), + this.v9.validate(a, locale, constraintContext), + this.v10.validate(a, locale, constraintContext), + this.v11.validate(a, locale, constraintContext), + this.v12.validate(a, locale, constraintContext), + this.v13.validate(a, locale, constraintContext), + this.v14.validate(a, locale, constraintContext), + this.v15.validate(a, locale, constraintContext), + this.v16.validate(a, locale, constraintContext)); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Splitting.java new file mode 100644 index 0000000..ab2bb9a --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Splitting.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function16; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments16Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + protected final ValueValidator v10; + + protected final ValueValidator v11; + + protected final ValueValidator v12; + + protected final ValueValidator v13; + + protected final ValueValidator v14; + + protected final ValueValidator v15; + + protected final ValueValidator v16; + + public Arguments16Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14, + ValueValidator v15, + ValueValidator v16) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + this.v15 = v15; + this.v16 = v16; + } + + public Arguments16Validator apply( + Function16 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, + locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext), + this.v9.validate(a9, locale, constraintContext), + this.v10.validate(a10, locale, constraintContext), + this.v11.validate(a11, locale, constraintContext), + this.v12.validate(a12, locale, constraintContext), + this.v13.validate(a13, locale, constraintContext), + this.v14.validate(a14, locale, constraintContext), + this.v15.validate(a15, locale, constraintContext), + this.v16.validate(a16, locale, constraintContext)); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Validator.java new file mode 100644 index 0000000..2089437 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments16Validator.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments16Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16, Locale locale, ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments16Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, + locale, constraintContext) -> Arguments16Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + a14, a15, a16, locale, constraintContext) + .map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments16Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, + locale, constraintContext) -> Arguments16Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + a14, a15, a16, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments16 args = mapper + .apply(a); + return Arguments16Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), args.arg9(), args.arg10(), args.arg11(), args.arg12(), + args.arg13(), args.arg14(), args.arg15(), args.arg16(), locale, + constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments16Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, a16, Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16, ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, a16, Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, a16, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, a16).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, a16, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16, Locale locale) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, a16, locale).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16, Locale locale, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15, a16, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments1Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments1Validator.java new file mode 100644 index 0000000..b1796f2 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments1Validator.java @@ -0,0 +1,175 @@ +package org.xbib.datastructures.validation.arguments; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validatable; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +@FunctionalInterface +public interface Arguments1Validator extends ValueValidator { + + /** + * Convert {@link Validatable} instance into {@link Arguments1Validator} + * + * @param validator core validator + * @param target class + * @return arguments1 validator + * @since 0.8.0 + */ + static Arguments1Validator from(Validatable validator) { + return Arguments1Validator.from(validator.applicative()); + } + + /** + * Convert {@link ValueValidator} instance into {@link Arguments1Validator} + * + * @param valueValidator value validator + * @param class of argument1 + * @param target class + * @return arguments1 validator + * @since 0.8.0 + */ + static Arguments1Validator from(ValueValidator valueValidator) { + return valueValidator::validate; + } + + @Override + Validated validate(@Nullable A1 a1, Locale locale, + ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + @Override + default Arguments1Validator andThen( + Function mapper) { + return (a1, locale, constraintContext) -> Arguments1Validator.this + .validate(a1, locale, constraintContext).map(mapper); + } + + /** + * @since 0.11.0 + */ + @Override + default Arguments1Validator andThen( + ValueValidator validator) { + return (a1, locale, constraintContext) -> Arguments1Validator.this + .validate(a1, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + @Override + default Arguments1Validator compose( + Function mapper) { + return (a, locale, constraintContext) -> Arguments1Validator.this + .validate(mapper.apply(a), locale, constraintContext); + } + + /** + * @since 0.10.0 + */ + default Arguments1Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1) { + return this.validate(a1, Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, ConstraintContext constraintContext) { + return this.validate(a1, Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, Locale locale) { + return this.validate(a1, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1) throws ConstraintViolationsException { + return this.validate(a1).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(a1, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, Locale locale) + throws ConstraintViolationsException { + return this.validate(a1, locale).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + /** + * @since 0.7.0 + */ + default Arguments2Splitting split( + ValueValidator validator) { + return new Arguments2Splitting<>(this, validator); + } + + /** + * @since 0.7.0 + */ + default Arguments2Combining combine(ValueValidator validator) { + return new Arguments2Combining<>(this, validator); + } + + /** + * @since 0.7.0 + */ + @Override + default Arguments1Validator indexed(int index) { + return (a1, locale, constraintContext) -> Arguments1Validator.this + .validate(a1, locale, constraintContext).indexed(index); + } + + /** + * @since 0.8.0 + */ + default > Arguments1Validator, C> liftCollection( + Supplier factory) { + return Arguments1Validator.from(ValueValidator.super.liftCollection(factory)); + } + + /** + * @since 0.8.0 + */ + default Arguments1Validator, List> liftList() { + return Arguments1Validator.from(ValueValidator.super.liftList()); + } + + /** + * @since 0.8.0 + */ + default Arguments1Validator, Set> liftSet() { + return Arguments1Validator.from(ValueValidator.super.liftSet()); + } + + /** + * @since 0.8.0 + */ + default Arguments1Validator, Optional> liftOptional() { + return Arguments1Validator.from(ValueValidator.super.liftOptional()); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2.java new file mode 100644 index 0000000..d02f06e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2.java @@ -0,0 +1,23 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function2; +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class Arguments2 extends Arguments1 { + + protected final A2 arg2; + + Arguments2(@Nullable A1 arg1, @Nullable A2 arg2) { + super(arg1); + this.arg2 = arg2; + } + + @Nullable + public final A2 arg2() { + return this.arg2; + } + + public final X map(Function2 mapper) { + return mapper.apply(arg1, arg2); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Combining.java new file mode 100644 index 0000000..4134168 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Combining.java @@ -0,0 +1,29 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function2; +import org.xbib.datastructures.validation.fn.Validations; + +public class Arguments2Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + public Arguments2Combining(ValueValidator v1, + ValueValidator v2) { + this.v1 = v1; + this.v2 = v2; + } + + public Arguments1Validator apply( + Function2 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext)); + } + + public Arguments3Combining combine( + ValueValidator v3) { + return new Arguments3Combining<>(v1, v2, v3); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Splitting.java new file mode 100644 index 0000000..784d5d9 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Splitting.java @@ -0,0 +1,29 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function2; +import org.xbib.datastructures.validation.fn.Validations; + +public class Arguments2Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + public Arguments2Splitting(ValueValidator v1, + ValueValidator v2) { + this.v1 = v1; + this.v2 = v2; + } + + public Arguments2Validator apply( + Function2 f) { + return (a1, a2, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext)); + } + + public Arguments3Splitting split( + ValueValidator v3) { + return new Arguments3Splitting<>(v1, v2, v3); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Validator.java new file mode 100644 index 0000000..2fef962 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments2Validator.java @@ -0,0 +1,83 @@ +package org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +@FunctionalInterface +public interface Arguments2Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, Locale locale, + ConstraintContext constraintContext); + + default Arguments2Validator andThen( + Function mapper) { + return (a1, a2, locale, constraintContext) -> Arguments2Validator.this + .validate(a1, a2, locale, constraintContext).map(mapper); + } + + default Arguments2Validator andThen( + ValueValidator validator) { + return (a1, a2, locale, constraintContext) -> Arguments2Validator.this + .validate(a1, a2, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments2 args = mapper.apply(a); + return Arguments2Validator.this.validate(args.arg1(), args.arg2(), locale, + constraintContext); + }; + } + + default Arguments2Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2) { + return this.validate(a1, a2, Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, + ConstraintContext constraintContext) { + return this.validate(a1, a2, Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, Locale locale) { + return this.validate(a1, a2, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2) + throws ConstraintViolationsException { + return this.validate(a1, a2).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, Locale locale) + throws ConstraintViolationsException { + return this.validate(a1, a2, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3.java new file mode 100644 index 0000000..7467386 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3.java @@ -0,0 +1,24 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function3; +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class Arguments3 extends Arguments2 { + + protected final A3 arg3; + + Arguments3(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3) { + super(arg1, arg2); + this.arg3 = arg3; + } + + @Nullable + public final A3 arg3() { + return this.arg3; + } + + public final X map( + Function3 mapper) { + return mapper.apply(arg1, arg2, arg3); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Combining.java new file mode 100644 index 0000000..d90985f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Combining.java @@ -0,0 +1,34 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function3; +import org.xbib.datastructures.validation.fn.Validations; + +public class Arguments3Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + public Arguments3Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public Arguments1Validator apply( + Function3 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext)); + } + + public Arguments4Combining combine( + ValueValidator v4) { + return new Arguments4Combining<>(v1, v2, v3, v4); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Splitting.java new file mode 100644 index 0000000..0e9dedd --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Splitting.java @@ -0,0 +1,34 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function3; +import org.xbib.datastructures.validation.fn.Validations; + +public class Arguments3Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + public Arguments3Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public Arguments3Validator apply( + Function3 f) { + return (a1, a2, a3, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext)); + } + + public Arguments4Splitting split( + ValueValidator v4) { + return new Arguments4Splitting<>(v1, v2, v3, v4); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Validator.java new file mode 100644 index 0000000..e79098e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments3Validator.java @@ -0,0 +1,85 @@ +package org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +@FunctionalInterface +public interface Arguments3Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + Locale locale, ConstraintContext constraintContext); + + default Arguments3Validator andThen( + Function mapper) { + return (a1, a2, a3, locale, constraintContext) -> Arguments3Validator.this + .validate(a1, a2, a3, locale, constraintContext).map(mapper); + } + + default Arguments3Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, locale, constraintContext) -> Arguments3Validator.this + .validate(a1, a2, a3, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments3 args = mapper + .apply(a); + return Arguments3Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), locale, constraintContext); + }; + } + + default Arguments3Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3) { + return this.validate(a1, a2, a3, Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + Locale locale) { + return this.validate(a1, a2, a3, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, Locale locale) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4.java new file mode 100644 index 0000000..bd64690 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4.java @@ -0,0 +1,25 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function4; +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class Arguments4 extends Arguments3 { + + protected final A4 arg4; + + Arguments4(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, + @Nullable A4 arg4) { + super(arg1, arg2, arg3); + this.arg4 = arg4; + } + + @Nullable + public final A4 arg4() { + return this.arg4; + } + + public final X map( + Function4 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Combining.java new file mode 100644 index 0000000..4f300d0 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Combining.java @@ -0,0 +1,39 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function4; +import org.xbib.datastructures.validation.fn.Validations; + +public class Arguments4Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + public Arguments4Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + } + + public Arguments1Validator apply( + Function4 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext)); + } + + public Arguments5Combining combine( + ValueValidator v5) { + return new Arguments5Combining<>(v1, v2, v3, v4, v5); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Splitting.java new file mode 100644 index 0000000..d5a10ac --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Splitting.java @@ -0,0 +1,39 @@ +package org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function4; +import org.xbib.datastructures.validation.fn.Validations; + +public class Arguments4Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + public Arguments4Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + } + + public Arguments4Validator apply( + Function4 f) { + return (a1, a2, a3, a4, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext)); + } + + public Arguments5Splitting split( + ValueValidator v5) { + return new Arguments5Splitting<>(v1, v2, v3, v4, v5); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Validator.java new file mode 100644 index 0000000..5a6c939 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments4Validator.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments4Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, Locale locale, ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments4Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, locale, constraintContext) -> Arguments4Validator.this + .validate(a1, a2, a3, a4, locale, constraintContext).map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments4Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, locale, constraintContext) -> Arguments4Validator.this + .validate(a1, a2, a3, a4, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments4 args = mapper + .apply(a); + return Arguments4Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments4Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4) { + return this.validate(a1, a2, a3, a4, Locale.getDefault(), + ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, Locale locale) { + return this.validate(a1, a2, a3, a4, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, Locale locale) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, Locale locale, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5.java new file mode 100644 index 0000000..a6cfa07 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function5; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments5 extends Arguments4 { + + protected final A5 arg5; + + Arguments5(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5) { + super(arg1, arg2, arg3, arg4); + this.arg5 = arg5; + } + + @Nullable + public final A5 arg5() { + return this.arg5; + } + + public final X map( + Function5 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Combining.java new file mode 100644 index 0000000..8092135 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Combining.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function5; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments5Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + public Arguments5Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + } + + public Arguments1Validator apply( + Function5 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext)); + } + + public Arguments6Combining combine( + ValueValidator v6) { + return new Arguments6Combining<>(v1, v2, v3, v4, v5, v6); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Splitting.java new file mode 100644 index 0000000..c298d14 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Splitting.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function5; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments5Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + public Arguments5Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + } + + public Arguments5Validator apply( + Function5 f) { + return (a1, a2, a3, a4, a5, locale, constraintContext) -> Validations.apply( + f::apply, this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext)); + } + + public Arguments6Splitting split( + ValueValidator v6) { + return new Arguments6Splitting<>(v1, v2, v3, v4, v5, v6); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Validator.java new file mode 100644 index 0000000..6f1b031 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments5Validator.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments5Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, Locale locale, + ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments5Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, locale, constraintContext) -> Arguments5Validator.this + .validate(a1, a2, a3, a4, a5, locale, constraintContext).map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments5Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, locale, constraintContext) -> Arguments5Validator.this + .validate(a1, a2, a3, a4, a5, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments5 args = mapper + .apply(a); + return Arguments5Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments5Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5) { + return this.validate(a1, a2, a3, a4, a5, Locale.getDefault(), + ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, Locale.getDefault(), constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, Locale locale) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6.java new file mode 100644 index 0000000..341e769 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function6; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments6 extends Arguments5 { + + protected final A6 arg6; + + Arguments6(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6) { + super(arg1, arg2, arg3, arg4, arg5); + this.arg6 = arg6; + } + + @Nullable + public final A6 arg6() { + return this.arg6; + } + + public final X map( + Function6 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Combining.java new file mode 100644 index 0000000..424c1cf --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Combining.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function6; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments6Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + public Arguments6Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + } + + public Arguments1Validator apply( + Function6 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext)); + } + + public Arguments7Combining combine( + ValueValidator v7) { + return new Arguments7Combining<>(v1, v2, v3, v4, v5, v6, v7); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Splitting.java new file mode 100644 index 0000000..fd6bf30 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Splitting.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function6; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments6Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + public Arguments6Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + } + + public Arguments6Validator apply( + Function6 f) { + return (a1, a2, a3, a4, a5, a6, locale, constraintContext) -> Validations.apply( + f::apply, this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext)); + } + + public Arguments7Splitting split( + ValueValidator v7) { + return new Arguments7Splitting<>(v1, v2, v3, v4, v5, v6, v7); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Validator.java new file mode 100644 index 0000000..e3afde5 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments6Validator.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments6Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, Locale locale, + ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments6Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, locale, + constraintContext) -> Arguments6Validator.this + .validate(a1, a2, a3, a4, a5, a6, locale, constraintContext) + .map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments6Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, locale, + constraintContext) -> Arguments6Validator.this + .validate(a1, a2, a3, a4, a5, a6, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments6 args = mapper + .apply(a); + return Arguments6Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), locale, + constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments6Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6) { + return this.validate(a1, a2, a3, a4, a5, a6, Locale.getDefault(), + ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, + ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, Locale.getDefault(), + constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, Locale locale) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7.java new file mode 100644 index 0000000..5c05c19 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function7; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments7 + extends Arguments6 { + + protected final A7 arg7; + + Arguments7(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7) { + super(arg1, arg2, arg3, arg4, arg5, arg6); + this.arg7 = arg7; + } + + @Nullable + public final A7 arg7() { + return this.arg7; + } + + public final X map( + Function7 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Combining.java new file mode 100644 index 0000000..28a7d2a --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Combining.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function7; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments7Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + public Arguments7Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + } + + public Arguments1Validator apply( + Function7 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext)); + } + + public Arguments8Combining combine( + ValueValidator v8) { + return new Arguments8Combining<>(v1, v2, v3, v4, v5, v6, v7, v8); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Splitting.java new file mode 100644 index 0000000..c6c01bc --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Splitting.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function7; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments7Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + public Arguments7Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + } + + public Arguments7Validator apply( + Function7 f) { + return (a1, a2, a3, a4, a5, a6, a7, locale, constraintContext) -> Validations + .apply(f::apply, this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext)); + } + + public Arguments8Splitting split( + ValueValidator v8) { + return new Arguments8Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Validator.java new file mode 100644 index 0000000..1e52d63 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments7Validator.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments7Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + Locale locale, ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments7Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, locale, + constraintContext) -> Arguments7Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, locale, constraintContext) + .map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments7Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, locale, + constraintContext) -> Arguments7Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments7 args = mapper + .apply(a); + return Arguments7Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments7Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, Locale.getDefault(), + ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, Locale.getDefault(), + constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, locale, ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + Locale locale) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + Locale locale, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8.java new file mode 100644 index 0000000..6eb163c --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function8; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments8 + extends Arguments7 { + + protected final A8 arg8; + + Arguments8(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + this.arg8 = arg8; + } + + @Nullable + public final A8 arg8() { + return this.arg8; + } + + public final X map( + Function8 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Combining.java new file mode 100644 index 0000000..5f015c3 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Combining.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function8; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments8Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + public Arguments8Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + } + + public Arguments1Validator apply( + Function8 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext)); + } + + public Arguments9Combining combine( + ValueValidator v9) { + return new Arguments9Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Splitting.java new file mode 100644 index 0000000..d0350d8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Splitting.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function8; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments8Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + public Arguments8Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + } + + public Arguments8Validator apply( + Function8 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, locale, constraintContext) -> Validations + .apply(f::apply, this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext)); + } + + public Arguments9Splitting split( + ValueValidator v9) { + return new Arguments9Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Validator.java new file mode 100644 index 0000000..b5a7946 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments8Validator.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments8Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, Locale locale, ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments8Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, locale, + constraintContext) -> Arguments8Validator.this.validate(a1, a2, a3, a4, + a5, a6, a7, a8, locale, constraintContext).map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments8Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, locale, + constraintContext) -> Arguments8Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, locale, + constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments8 args = mapper + .apply(a); + return Arguments8Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments8Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, Locale.getDefault(), + ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, Locale.getDefault(), + constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, locale, + ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, Locale locale) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, Locale locale, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9.java new file mode 100644 index 0000000..379583a --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.fn.Function9; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public class Arguments9 + extends Arguments8 { + + protected final A9 arg9; + + Arguments9(@Nullable A1 arg1, @Nullable A2 arg2, @Nullable A3 arg3, @Nullable A4 arg4, + @Nullable A5 arg5, @Nullable A6 arg6, @Nullable A7 arg7, @Nullable A8 arg8, + @Nullable A9 arg9) { + super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + this.arg9 = arg9; + } + + @Nullable + public final A9 arg9() { + return this.arg9; + } + + public final X map( + Function9 mapper) { + return mapper.apply(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Combining.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Combining.java new file mode 100644 index 0000000..9745018 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Combining.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function9; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments9Combining { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + public Arguments9Combining(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + } + + public Arguments1Validator apply( + Function9 f) { + return (a, locale, constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a, locale, constraintContext), + this.v2.validate(a, locale, constraintContext), + this.v3.validate(a, locale, constraintContext), + this.v4.validate(a, locale, constraintContext), + this.v5.validate(a, locale, constraintContext), + this.v6.validate(a, locale, constraintContext), + this.v7.validate(a, locale, constraintContext), + this.v8.validate(a, locale, constraintContext), + this.v9.validate(a, locale, constraintContext)); + } + + public Arguments10Combining combine( + ValueValidator v10) { + return new Arguments10Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Splitting.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Splitting.java new file mode 100644 index 0000000..248c373 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Splitting.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.fn.Function9; +import org.xbib.datastructures.validation.fn.Validations; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class Arguments9Splitting { + protected final ValueValidator v1; + + protected final ValueValidator v2; + + protected final ValueValidator v3; + + protected final ValueValidator v4; + + protected final ValueValidator v5; + + protected final ValueValidator v6; + + protected final ValueValidator v7; + + protected final ValueValidator v8; + + protected final ValueValidator v9; + + public Arguments9Splitting(ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + } + + public Arguments9Validator apply( + Function9 f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, locale, + constraintContext) -> Validations.apply(f::apply, + this.v1.validate(a1, locale, constraintContext), + this.v2.validate(a2, locale, constraintContext), + this.v3.validate(a3, locale, constraintContext), + this.v4.validate(a4, locale, constraintContext), + this.v5.validate(a5, locale, constraintContext), + this.v6.validate(a6, locale, constraintContext), + this.v7.validate(a7, locale, constraintContext), + this.v8.validate(a8, locale, constraintContext), + this.v9.validate(a9, locale, constraintContext)); + } + + public Arguments10Splitting split( + ValueValidator v10) { + return new Arguments10Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Validator.java new file mode 100644 index 0000000..0edc912 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/Arguments9Validator.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +@FunctionalInterface +public interface Arguments9Validator { + + Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, Locale locale, + ConstraintContext constraintContext); + + /** + * @since 0.7.0 + */ + default Arguments9Validator andThen( + Function mapper) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, locale, + constraintContext) -> Arguments9Validator.this.validate(a1, a2, a3, a4, + a5, a6, a7, a8, a9, locale, constraintContext).map(mapper); + } + + /** + * @since 0.11.0 + */ + default Arguments9Validator andThen( + ValueValidator validator) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, locale, + constraintContext) -> Arguments9Validator.this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, locale, + constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + /** + * @since 0.7.0 + */ + default Arguments1Validator compose( + Function> mapper) { + return (a, locale, constraintContext) -> { + final Arguments9 args = mapper + .apply(a); + return Arguments9Validator.this.validate(args.arg1(), args.arg2(), + args.arg3(), args.arg4(), args.arg5(), args.arg6(), args.arg7(), + args.arg8(), args.arg9(), locale, constraintContext); + }; + } + + /** + * @since 0.10.0 + */ + default Arguments9Validator> lazy() { + // WARNING:: The default implementation is not really lazy! + return this.andThen(x -> () -> x); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, Locale.getDefault(), + ConstraintGroup.DEFAULT); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, ConstraintContext constraintContext) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, Locale.getDefault(), + constraintContext); + } + + default Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, Locale locale) { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, locale, + ConstraintGroup.DEFAULT); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9) throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, Locale locale) + throws ConstraintViolationsException { + return this.validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, locale) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, Locale locale, + ConstraintContext constraintContext) throws ConstraintViolationsException { + return this + .validate(a1, a2, a3, a4, a5, a6, a7, a8, a9, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ArgumentsValidators.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ArgumentsValidators.java new file mode 100644 index 0000000..ac4e338 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ArgumentsValidators.java @@ -0,0 +1,686 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ValueValidator; + +import static java.util.function.Function.identity; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class ArgumentsValidators { + + public static Arguments2Splitting split( + ValueValidator v1, + ValueValidator v2) { + return new Arguments2Splitting<>(v1, v2); + } + + public static Arguments3Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3) { + return new Arguments3Splitting<>(v1, v2, v3); + } + + public static Arguments4Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4) { + return new Arguments4Splitting<>(v1, v2, v3, v4); + } + + public static Arguments5Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5) { + return new Arguments5Splitting<>(v1, v2, v3, v4, v5); + } + + public static Arguments6Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6) { + return new Arguments6Splitting<>(v1, v2, v3, v4, v5, v6); + } + + public static Arguments7Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7) { + return new Arguments7Splitting<>(v1, v2, v3, v4, v5, v6, v7); + } + + public static Arguments8Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8) { + return new Arguments8Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8); + } + + public static Arguments9Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9) { + return new Arguments9Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9); + } + + public static Arguments10Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10) { + return new Arguments10Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); + } + + public static Arguments11Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11) { + return new Arguments11Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); + } + + public static Arguments12Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12) { + return new Arguments12Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12); + } + + public static Arguments13Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13) { + return new Arguments13Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13); + } + + public static Arguments14Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14) { + return new Arguments14Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14); + } + + public static Arguments15Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14, + ValueValidator v15) { + return new Arguments15Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15); + } + + public static Arguments16Splitting split( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14, + ValueValidator v15, + ValueValidator v16) { + return new Arguments16Splitting<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16); + } + + public static Arguments2Combining combine( + ValueValidator v1, + ValueValidator v2) { + return new Arguments2Combining<>(v1, v2); + } + + public static Arguments3Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3) { + return new Arguments3Combining<>(v1, v2, v3); + } + + public static Arguments4Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4) { + return new Arguments4Combining<>(v1, v2, v3, v4); + } + + public static Arguments5Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5) { + return new Arguments5Combining<>(v1, v2, v3, v4, v5); + } + + public static Arguments6Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6) { + return new Arguments6Combining<>(v1, v2, v3, v4, v5, v6); + } + + public static Arguments7Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7) { + return new Arguments7Combining<>(v1, v2, v3, v4, v5, v6, v7); + } + + public static Arguments8Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8) { + return new Arguments8Combining<>(v1, v2, v3, v4, v5, v6, v7, v8); + } + + public static Arguments9Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9) { + return new Arguments9Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9); + } + + public static Arguments10Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10) { + return new Arguments10Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); + } + + public static Arguments11Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11) { + return new Arguments11Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); + } + + public static Arguments12Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12) { + return new Arguments12Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12); + } + + public static Arguments13Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13) { + return new Arguments13Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13); + } + + public static Arguments14Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14) { + return new Arguments14Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14); + } + + public static Arguments15Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14, + ValueValidator v15) { + return new Arguments15Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15); + } + + public static Arguments16Combining combine( + ValueValidator v1, + ValueValidator v2, + ValueValidator v3, + ValueValidator v4, + ValueValidator v5, + ValueValidator v6, + ValueValidator v7, + ValueValidator v8, + ValueValidator v9, + ValueValidator v10, + ValueValidator v11, + ValueValidator v12, + ValueValidator v13, + ValueValidator v14, + ValueValidator v15, + ValueValidator v16) { + return new Arguments16Combining<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16); + } + + public static Arguments1Validator> traverse1( + Iterable values, + Function> f) { + return (a1, locale, constraintContext) -> Validated.traverse(values, f + .andThen(validator -> validator.validate(a1, locale, constraintContext))); + } + + public static Arguments2Validator> traverse2( + Iterable values, + Function> f) { + return (a1, a2, locale, constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, locale, + constraintContext))); + } + + public static Arguments3Validator> traverse3( + Iterable values, + Function> f) { + return (a1, a2, a3, locale, constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, locale, + constraintContext))); + } + + public static Arguments4Validator> traverse4( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, locale, constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, locale, + constraintContext))); + } + + public static Arguments5Validator> traverse5( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, locale, constraintContext) -> Validated + .traverse(values, f.andThen(validator -> validator.validate(a1, a2, a3, + a4, a5, locale, constraintContext))); + } + + public static Arguments6Validator> traverse6( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, locale, constraintContext) -> Validated + .traverse(values, f.andThen(validator -> validator.validate(a1, a2, a3, + a4, a5, a6, locale, constraintContext))); + } + + public static Arguments7Validator> traverse7( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, locale, constraintContext) -> Validated + .traverse(values, f.andThen(validator -> validator.validate(a1, a2, a3, + a4, a5, a6, a7, locale, constraintContext))); + } + + public static Arguments8Validator> traverse8( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, locale, constraintContext) -> Validated + .traverse(values, f.andThen(validator -> validator.validate(a1, a2, a3, + a4, a5, a6, a7, a8, locale, constraintContext))); + } + + public static Arguments9Validator> traverse9( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, locale, + constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, a5, a6, + a7, a8, a9, locale, constraintContext))); + } + + public static Arguments10Validator> traverse10( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, locale, + constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, locale, constraintContext))); + } + + public static Arguments11Validator> traverse11( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, locale, + constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, locale, constraintContext))); + } + + public static Arguments12Validator> traverse12( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, locale, + constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, a12, locale, constraintContext))); + } + + public static Arguments13Validator> traverse13( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, locale, + constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, a12, a13, locale, + constraintContext))); + } + + public static Arguments14Validator> traverse14( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, locale, + constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, a12, a13, a14, locale, + constraintContext))); + } + + public static Arguments15Validator> traverse15( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, locale, + constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, a12, a13, a14, a15, locale, + constraintContext))); + } + + public static Arguments16Validator> traverse16( + Iterable values, + Function> f) { + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, + locale, constraintContext) -> Validated.traverse(values, + f.andThen(validator -> validator.validate(a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, locale, + constraintContext))); + } + + public static Arguments1Validator> sequence1( + Iterable> values) { + return traverse1(values, identity()); + } + + public static Arguments2Validator> sequence2( + Iterable> values) { + return traverse2(values, identity()); + } + + public static Arguments3Validator> sequence3( + Iterable> values) { + return traverse3(values, identity()); + } + + public static Arguments4Validator> sequence4( + Iterable> values) { + return traverse4(values, identity()); + } + + public static Arguments5Validator> sequence5( + Iterable> values) { + return traverse5(values, identity()); + } + + public static Arguments6Validator> sequence6( + Iterable> values) { + return traverse6(values, identity()); + } + + public static Arguments7Validator> sequence7( + Iterable> values) { + return traverse7(values, identity()); + } + + public static Arguments8Validator> sequence8( + Iterable> values) { + return traverse8(values, identity()); + } + + public static Arguments9Validator> sequence9( + Iterable> values) { + return traverse9(values, identity()); + } + + public static Arguments10Validator> sequence10( + Iterable> values) { + return traverse10(values, identity()); + } + + public static Arguments11Validator> sequence11( + Iterable> values) { + return traverse11(values, identity()); + } + + public static Arguments12Validator> sequence12( + Iterable> values) { + return traverse12(values, identity()); + } + + public static Arguments13Validator> sequence13( + Iterable> values) { + return traverse13(values, identity()); + } + + public static Arguments14Validator> sequence14( + Iterable> values) { + return traverse14(values, identity()); + } + + public static Arguments15Validator> sequence15( + Iterable> values) { + return traverse15(values, identity()); + } + + public static Arguments16Validator> sequence16( + Iterable> values) { + return traverse16(values, identity()); + } + + /** + * @since 0.8.0 + */ + public static > Arguments1Validator, C> liftCollection( + ValueValidator validator, Supplier factory) { + return Arguments1Validator + .from(ValueValidator.liftCollection(validator, factory)); + } + + public static Arguments1Validator, List> liftList( + ValueValidator validator) { + return Arguments1Validator.from(ValueValidator.liftList(validator)); + } + + /** + * @since 0.8.0 + */ + public static Arguments1Validator, Set> liftSet( + ValueValidator validator) { + return Arguments1Validator.from(ValueValidator.liftSet(validator)); + } + + /** + * @since 0.8.0 + */ + public static Arguments1Validator, Optional> liftOptional( + ValueValidator validator) { + return Arguments1Validator.from(ValueValidator.liftOptional(validator)); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BigDecimalValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BigDecimalValidator.java new file mode 100644 index 0000000..057f3c8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BigDecimalValidator.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.math.BigDecimal; +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class BigDecimalValidator extends DefaultArguments1Validator { + + @Override + public BigDecimalValidator andThen( + Function mapper) { + return new BigDecimalValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public BigDecimalValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BigIntegerValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BigIntegerValidator.java new file mode 100644 index 0000000..f4fd883 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BigIntegerValidator.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.math.BigInteger; +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class BigIntegerValidator extends DefaultArguments1Validator { + + @Override + public BigIntegerValidator andThen( + Function mapper) { + return new BigIntegerValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public BigIntegerValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BooleanValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BooleanValidator.java new file mode 100644 index 0000000..6a2abe6 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/BooleanValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class BooleanValidator extends DefaultArguments1Validator { + + @Override + public BooleanValidator andThen(Function mapper) { + return new BooleanValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public BooleanValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments10Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments10Validator.java new file mode 100644 index 0000000..ee905ee --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments10Validator.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function10; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments10Validator + implements Arguments10Validator { + protected final Validator> validator; + protected final Function10 mapper; + + public DefaultArguments10Validator( + Validator> validator, + Function10 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments10Validator> lazy() { + return new DefaultArguments10Validator<>(this.validator, (a1, a2, a3, a4, a5, a6, + a7, a8, a9, + a10) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, Locale locale, + ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10), locale, + constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments11Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments11Validator.java new file mode 100644 index 0000000..70ece37 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments11Validator.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function11; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments11Validator + implements Arguments11Validator { + protected final Validator> validator; + protected final Function11 mapper; + + public DefaultArguments11Validator( + Validator> validator, + Function11 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments11Validator> lazy() { + return new DefaultArguments11Validator<>(this.validator, + (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) -> () -> this.mapper + .apply(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + Locale locale, ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11), + locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments12Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments12Validator.java new file mode 100644 index 0000000..63f0091 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments12Validator.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function12; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments12Validator + implements + Arguments12Validator { + protected final Validator> validator; + protected final Function12 mapper; + + public DefaultArguments12Validator( + Validator> validator, + Function12 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments12Validator> lazy() { + return new DefaultArguments12Validator<>(this.validator, + (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) -> () -> this.mapper + .apply(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, Locale locale, ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12), + locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments13Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments13Validator.java new file mode 100644 index 0000000..085bea4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments13Validator.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function13; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments13Validator + implements + Arguments13Validator { + protected final Validator> validator; + protected final Function13 mapper; + + public DefaultArguments13Validator( + Validator> validator, + Function13 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments13Validator> lazy() { + return new DefaultArguments13Validator<>(this.validator, + (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, Locale locale, + ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments14Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments14Validator.java new file mode 100644 index 0000000..1ac9f93 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments14Validator.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function14; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments14Validator + implements + Arguments14Validator { + protected final Validator> validator; + protected final Function14 mapper; + + public DefaultArguments14Validator( + Validator> validator, + Function14 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments14Validator> lazy() { + return new DefaultArguments14Validator<>(this.validator, + (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + a14) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, Locale locale, + ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments15Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments15Validator.java new file mode 100644 index 0000000..3279750 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments15Validator.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function15; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments15Validator + implements + Arguments15Validator { + protected final Validator> validator; + protected final Function15 mapper; + + public DefaultArguments15Validator( + Validator> validator, + Function15 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments15Validator> lazy() { + return new DefaultArguments15Validator<>(this.validator, + (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + a15) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + Locale locale, ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments16Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments16Validator.java new file mode 100644 index 0000000..76fe91f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments16Validator.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function16; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments16Validator + implements + Arguments16Validator { + protected final Validator> validator; + protected final Function16 mapper; + + public DefaultArguments16Validator( + Validator> validator, + Function16 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments16Validator> lazy() { + return new DefaultArguments16Validator<>(this.validator, + (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, + a16) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, @Nullable A10 a10, @Nullable A11 a11, + @Nullable A12 a12, @Nullable A13 a13, @Nullable A14 a14, @Nullable A15 a15, + @Nullable A16 a16, Locale locale, ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments1Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments1Validator.java new file mode 100644 index 0000000..0128c9e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments1Validator.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments1Validator implements Arguments1Validator { + protected final Validator> validator; + protected final Function1 mapper; + + public DefaultArguments1Validator(Validator> validator, + Function1 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments1Validator> lazy() { + return new DefaultArguments1Validator<>(this.validator, + (a1) -> () -> this.mapper.apply(a1)); + } + + @Override + public Validated validate(@Nullable A1 a1, Locale locale, + ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments2Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments2Validator.java new file mode 100644 index 0000000..6e466ef --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments2Validator.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function2; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments2Validator + implements Arguments2Validator { + protected final Validator> validator; + protected final Function2 mapper; + + public DefaultArguments2Validator(Validator> validator, + Function2 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments2Validator> lazy() { + return new DefaultArguments2Validator<>(this.validator, + (a1, a2) -> () -> this.mapper.apply(a1, a2)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, Locale locale, + ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments3Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments3Validator.java new file mode 100644 index 0000000..00b10eb --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments3Validator.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function3; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments3Validator + implements Arguments3Validator { + protected final Validator> validator; + protected final Function3 mapper; + + public DefaultArguments3Validator(Validator> validator, + Function3 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments3Validator> lazy() { + return new DefaultArguments3Validator<>(this.validator, + (a1, a2, a3) -> () -> this.mapper.apply(a1, a2, a3)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + Locale locale, ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments4Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments4Validator.java new file mode 100644 index 0000000..ba7fed8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments4Validator.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function4; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments4Validator + implements Arguments4Validator { + protected final Validator> validator; + protected final Function4 mapper; + + public DefaultArguments4Validator(Validator> validator, + Function4 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments4Validator> lazy() { + return new DefaultArguments4Validator<>(this.validator, + (a1, a2, a3, a4) -> () -> this.mapper.apply(a1, a2, a3, a4)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, Locale locale, ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments5Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments5Validator.java new file mode 100644 index 0000000..2a5f79d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments5Validator.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function5; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments5Validator + implements Arguments5Validator { + protected final Validator> validator; + protected final Function5 mapper; + + public DefaultArguments5Validator(Validator> validator, + Function5 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments5Validator> lazy() { + return new DefaultArguments5Validator<>(this.validator, + (a1, a2, a3, a4, a5) -> () -> this.mapper.apply(a1, a2, a3, a4, a5)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, Locale locale, + ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments6Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments6Validator.java new file mode 100644 index 0000000..3fbf506 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments6Validator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function6; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments6Validator + implements Arguments6Validator { + protected final Validator> validator; + protected final Function6 mapper; + + public DefaultArguments6Validator( + Validator> validator, + Function6 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments6Validator> lazy() { + return new DefaultArguments6Validator<>(this.validator, (a1, a2, a3, a4, a5, + a6) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, Locale locale, + ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6), locale, constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments7Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments7Validator.java new file mode 100644 index 0000000..ec2d691 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments7Validator.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function7; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments7Validator + implements Arguments7Validator { + protected final Validator> validator; + protected final Function7 mapper; + + public DefaultArguments7Validator( + Validator> validator, + Function7 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments7Validator> lazy() { + return new DefaultArguments7Validator<>(this.validator, (a1, a2, a3, a4, a5, a6, + a7) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6, a7)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + Locale locale, ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7), locale, + constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments8Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments8Validator.java new file mode 100644 index 0000000..babf9e7 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments8Validator.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function8; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments8Validator + implements Arguments8Validator { + protected final Validator> validator; + protected final Function8 mapper; + + public DefaultArguments8Validator( + Validator> validator, + Function8 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments8Validator> lazy() { + return new DefaultArguments8Validator<>(this.validator, (a1, a2, a3, a4, a5, a6, + a7, a8) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6, a7, a8)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, Locale locale, ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8), locale, + constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments9Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments9Validator.java new file mode 100644 index 0000000..b4a461c --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DefaultArguments9Validator.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Locale; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function9; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.7.0 + */ +public class DefaultArguments9Validator + implements Arguments9Validator { + protected final Validator> validator; + protected final Function9 mapper; + + public DefaultArguments9Validator( + Validator> validator, + Function9 mapper) { + this.validator = validator; + this.mapper = mapper; + } + + /** + * @since 0.10.0 + */ + @Override + public DefaultArguments9Validator> lazy() { + return new DefaultArguments9Validator<>(this.validator, (a1, a2, a3, a4, a5, a6, + a7, a8, + a9) -> () -> this.mapper.apply(a1, a2, a3, a4, a5, a6, a7, a8, a9)); + } + + @Override + public Validated validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, + @Nullable A4 a4, @Nullable A5 a5, @Nullable A6 a6, @Nullable A7 a7, + @Nullable A8 a8, @Nullable A9 a9, Locale locale, + ConstraintContext constraintContext) { + return this.validator.applicative() + .validate(Arguments.of(a1, a2, a3, a4, a5, a6, a7, a8, a9), locale, + constraintContext) + .map(values -> values.map(this.mapper)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DoubleValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DoubleValidator.java new file mode 100644 index 0000000..a5c5b38 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/DoubleValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class DoubleValidator extends DefaultArguments1Validator { + + @Override + public DoubleValidator andThen(Function mapper) { + return new DoubleValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public DoubleValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/FloatValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/FloatValidator.java new file mode 100644 index 0000000..c62a91f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/FloatValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class FloatValidator extends DefaultArguments1Validator { + + @Override + public FloatValidator andThen(Function mapper) { + return new FloatValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public FloatValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/InstantValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/InstantValidator.java new file mode 100644 index 0000000..c251d23 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/InstantValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.time.Instant; +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.10.0 + */ +public class InstantValidator extends DefaultArguments1Validator { + + @Override + public InstantValidator andThen(Function mapper) { + return new InstantValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public InstantValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/IntegerValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/IntegerValidator.java new file mode 100644 index 0000000..0c5271c --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/IntegerValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class IntegerValidator extends DefaultArguments1Validator { + + @Override + public IntegerValidator andThen(Function mapper) { + return new IntegerValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public IntegerValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalDateTimeValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalDateTimeValidator.java new file mode 100644 index 0000000..15ffe98 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalDateTimeValidator.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +import java.time.LocalDateTime; +import java.util.function.Function; + +/** + * @since 0.10.0 + */ +public class LocalDateTimeValidator + extends DefaultArguments1Validator { + + @Override + public LocalDateTimeValidator andThen( + Function mapper) { + return new LocalDateTimeValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public LocalDateTimeValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalDateValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalDateValidator.java new file mode 100644 index 0000000..c9a55a4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalDateValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +import java.time.LocalDate; +import java.util.function.Function; + +/** + * @since 0.10.0 + */ +public class LocalDateValidator extends DefaultArguments1Validator { + + @Override + public LocalDateValidator andThen(Function mapper) { + return new LocalDateValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public LocalDateValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalTimeValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalTimeValidator.java new file mode 100644 index 0000000..d8ebaed --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LocalTimeValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +import java.time.LocalTime; +import java.util.function.Function; + +/** + * @since 0.10.0 + */ +public class LocalTimeValidator extends DefaultArguments1Validator { + + @Override + public LocalTimeValidator andThen(Function mapper) { + return new LocalTimeValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public LocalTimeValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LongValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LongValidator.java new file mode 100644 index 0000000..58a8ba1 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/LongValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class LongValidator extends DefaultArguments1Validator { + + @Override + public LongValidator andThen(Function mapper) { + return new LongValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public LongValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ObjectValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ObjectValidator.java new file mode 100644 index 0000000..0ba7ac4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ObjectValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.8.0 + */ +public class ObjectValidator extends DefaultArguments1Validator { + + @Override + public ObjectValidator andThen(Function mapper) { + return new ObjectValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public ObjectValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/OffsetDateTimeValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/OffsetDateTimeValidator.java new file mode 100644 index 0000000..bae5a55 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/OffsetDateTimeValidator.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.time.OffsetDateTime; +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.10.0 + */ +public class OffsetDateTimeValidator + extends DefaultArguments1Validator { + + @Override + public OffsetDateTimeValidator andThen( + Function mapper) { + return new OffsetDateTimeValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public OffsetDateTimeValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ShortValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ShortValidator.java new file mode 100644 index 0000000..4ff52a1 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ShortValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class ShortValidator extends DefaultArguments1Validator { + + @Override + public ShortValidator andThen(Function mapper) { + return new ShortValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public ShortValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/StringValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/StringValidator.java new file mode 100644 index 0000000..b968b56 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/StringValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.7.0 + */ +public class StringValidator extends DefaultArguments1Validator { + + @Override + public StringValidator andThen(Function mapper) { + return new StringValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public StringValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/YearMonthValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/YearMonthValidator.java new file mode 100644 index 0000000..78fd93f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/YearMonthValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.time.YearMonth; +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.11.0 + */ +public class YearMonthValidator extends DefaultArguments1Validator { + + @Override + public YearMonthValidator andThen(Function mapper) { + return new YearMonthValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public YearMonthValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/YearValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/YearValidator.java new file mode 100644 index 0000000..366fbbf --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/YearValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.time.Year; +import java.util.function.Function; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +/** + * @since 0.11.0 + */ +public class YearValidator extends DefaultArguments1Validator { + + @Override + public YearValidator andThen(Function mapper) { + return new YearValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public YearValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ZonedDateTimeValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ZonedDateTimeValidator.java new file mode 100644 index 0000000..a5567ef --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/arguments/ZonedDateTimeValidator.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.fn.Function1; + +import java.time.ZonedDateTime; +import java.util.function.Function; + +/** + * @since 0.10.0 + */ +public class ZonedDateTimeValidator + extends DefaultArguments1Validator { + + @Override + public ZonedDateTimeValidator andThen( + Function mapper) { + return new ZonedDateTimeValidator<>(super.validator, + s -> mapper.apply(super.mapper.apply(s))); + } + + public ZonedDateTimeValidator(Validator> validator, + Function1 mapper) { + super(validator, mapper); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ArgumentsValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ArgumentsValidatorBuilder.java new file mode 100644 index 0000000..5d2622e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ArgumentsValidatorBuilder.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.Arguments1Validator; +import org.xbib.datastructures.validation.arguments.Arguments2; +import org.xbib.datastructures.validation.arguments.Arguments2Validator; +import org.xbib.datastructures.validation.arguments.Arguments3; +import org.xbib.datastructures.validation.arguments.Arguments3Validator; +import org.xbib.datastructures.validation.arguments.Arguments4; +import org.xbib.datastructures.validation.arguments.Arguments4Validator; +import org.xbib.datastructures.validation.arguments.Arguments5; +import org.xbib.datastructures.validation.arguments.Arguments5Validator; +import org.xbib.datastructures.validation.arguments.Arguments6; +import org.xbib.datastructures.validation.arguments.Arguments6Validator; +import org.xbib.datastructures.validation.arguments.Arguments7; +import org.xbib.datastructures.validation.arguments.Arguments7Validator; +import org.xbib.datastructures.validation.arguments.Arguments8; +import org.xbib.datastructures.validation.arguments.Arguments8Validator; +import org.xbib.datastructures.validation.arguments.Arguments9; +import org.xbib.datastructures.validation.arguments.Arguments9Validator; +import org.xbib.datastructures.validation.arguments.Arguments10; +import org.xbib.datastructures.validation.arguments.Arguments10Validator; +import org.xbib.datastructures.validation.arguments.Arguments11; +import org.xbib.datastructures.validation.arguments.Arguments11Validator; +import org.xbib.datastructures.validation.arguments.Arguments12; +import org.xbib.datastructures.validation.arguments.Arguments12Validator; +import org.xbib.datastructures.validation.arguments.Arguments13; +import org.xbib.datastructures.validation.arguments.Arguments13Validator; +import org.xbib.datastructures.validation.arguments.Arguments14; +import org.xbib.datastructures.validation.arguments.Arguments14Validator; +import org.xbib.datastructures.validation.arguments.Arguments15; +import org.xbib.datastructures.validation.arguments.Arguments15Validator; +import org.xbib.datastructures.validation.arguments.Arguments16; +import org.xbib.datastructures.validation.arguments.Arguments16Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments1Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments2Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments3Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments4Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments5Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments6Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments7Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments8Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments9Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments10Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments11Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments12Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments13Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments14Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments15Validator; +import org.xbib.datastructures.validation.arguments.DefaultArguments16Validator; +import org.xbib.datastructures.validation.fn.Function1; +import org.xbib.datastructures.validation.fn.Function2; +import org.xbib.datastructures.validation.fn.Function3; +import org.xbib.datastructures.validation.fn.Function4; +import org.xbib.datastructures.validation.fn.Function5; +import org.xbib.datastructures.validation.fn.Function6; +import org.xbib.datastructures.validation.fn.Function7; +import org.xbib.datastructures.validation.fn.Function8; +import org.xbib.datastructures.validation.fn.Function9; +import org.xbib.datastructures.validation.fn.Function10; +import org.xbib.datastructures.validation.fn.Function11; +import org.xbib.datastructures.validation.fn.Function12; +import org.xbib.datastructures.validation.fn.Function13; +import org.xbib.datastructures.validation.fn.Function14; +import org.xbib.datastructures.validation.fn.Function15; +import org.xbib.datastructures.validation.fn.Function16; + +import java.util.Objects; +import java.util.function.Function; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh + * + * @since 0.3.0 + */ +public final class ArgumentsValidatorBuilder { + public static Arguments1ValidatorBuilder of( + Function1 mapper) { + return new Arguments1ValidatorBuilder<>(mapper); + } + + public static Arguments2ValidatorBuilder of( + Function2 mapper) { + return new Arguments2ValidatorBuilder<>(mapper); + } + + public static Arguments3ValidatorBuilder of( + Function3 mapper) { + return new Arguments3ValidatorBuilder<>(mapper); + } + + public static Arguments4ValidatorBuilder of( + Function4 mapper) { + return new Arguments4ValidatorBuilder<>(mapper); + } + + public static Arguments5ValidatorBuilder of( + Function5 mapper) { + return new Arguments5ValidatorBuilder<>(mapper); + } + + public static Arguments6ValidatorBuilder of( + Function6 mapper) { + return new Arguments6ValidatorBuilder<>(mapper); + } + + public static Arguments7ValidatorBuilder of( + Function7 mapper) { + return new Arguments7ValidatorBuilder<>(mapper); + } + + public static Arguments8ValidatorBuilder of( + Function8 mapper) { + return new Arguments8ValidatorBuilder<>(mapper); + } + + public static Arguments9ValidatorBuilder of( + Function9 mapper) { + return new Arguments9ValidatorBuilder<>(mapper); + } + + public static Arguments10ValidatorBuilder of( + Function10 mapper) { + return new Arguments10ValidatorBuilder<>(mapper); + } + + public static Arguments11ValidatorBuilder of( + Function11 mapper) { + return new Arguments11ValidatorBuilder<>(mapper); + } + + public static Arguments12ValidatorBuilder of( + Function12 mapper) { + return new Arguments12ValidatorBuilder<>(mapper); + } + + public static Arguments13ValidatorBuilder of( + Function13 mapper) { + return new Arguments13ValidatorBuilder<>(mapper); + } + + public static Arguments14ValidatorBuilder of( + Function14 mapper) { + return new Arguments14ValidatorBuilder<>(mapper); + } + + public static Arguments15ValidatorBuilder of( + Function15 mapper) { + return new Arguments15ValidatorBuilder<>(mapper); + } + + public static Arguments16ValidatorBuilder of( + Function16 mapper) { + return new Arguments16ValidatorBuilder<>(mapper); + } + + /** + * @since 0.3.0 + */ + public static final class Arguments1ValidatorBuilder { + private final Function1 mapper; + private ValidatorBuilder> builder; + + public Arguments1ValidatorBuilder(Function1 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments1ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments1Validator build() { + return new DefaultArguments1Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments2ValidatorBuilder { + private final Function2 mapper; + private ValidatorBuilder> builder; + + public Arguments2ValidatorBuilder( + Function2 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments2ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments2Validator build() { + return new DefaultArguments2Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments3ValidatorBuilder { + private final Function3 mapper; + private ValidatorBuilder> builder; + + public Arguments3ValidatorBuilder( + Function3 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments3ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments3Validator build() { + return new DefaultArguments3Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments4ValidatorBuilder { + private final Function4 mapper; + private ValidatorBuilder> builder; + + public Arguments4ValidatorBuilder( + Function4 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments4ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments4Validator build() { + return new DefaultArguments4Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments5ValidatorBuilder { + private final Function5 mapper; + private ValidatorBuilder> builder; + + public Arguments5ValidatorBuilder( + Function5 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments5ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments5Validator build() { + return new DefaultArguments5Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments6ValidatorBuilder { + private final Function6 mapper; + private ValidatorBuilder> builder; + + public Arguments6ValidatorBuilder( + Function6 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments6ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments6Validator build() { + return new DefaultArguments6Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments7ValidatorBuilder { + private final Function7 mapper; + private ValidatorBuilder> builder; + + public Arguments7ValidatorBuilder( + Function7 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments7ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments7Validator build() { + return new DefaultArguments7Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments8ValidatorBuilder { + private final Function8 mapper; + private ValidatorBuilder> builder; + + public Arguments8ValidatorBuilder( + Function8 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments8ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments8Validator build() { + return new DefaultArguments8Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments9ValidatorBuilder { + private final Function9 mapper; + private ValidatorBuilder> builder; + + public Arguments9ValidatorBuilder( + Function9 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments9ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments9Validator build() { + return new DefaultArguments9Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments10ValidatorBuilder { + private final Function10 mapper; + private ValidatorBuilder> builder; + + public Arguments10ValidatorBuilder( + Function10 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments10ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments10Validator build() { + return new DefaultArguments10Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments11ValidatorBuilder { + private final Function11 mapper; + private ValidatorBuilder> builder; + + public Arguments11ValidatorBuilder( + Function11 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments11ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments11Validator build() { + return new DefaultArguments11Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments12ValidatorBuilder { + private final Function12 mapper; + private ValidatorBuilder> builder; + + public Arguments12ValidatorBuilder( + Function12 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments12ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments12Validator build() { + return new DefaultArguments12Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments13ValidatorBuilder { + private final Function13 mapper; + private ValidatorBuilder> builder; + + public Arguments13ValidatorBuilder( + Function13 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments13ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments13Validator build() { + return new DefaultArguments13Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments14ValidatorBuilder { + private final Function14 mapper; + private ValidatorBuilder> builder; + + public Arguments14ValidatorBuilder( + Function14 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments14ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments14Validator build() { + return new DefaultArguments14Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments15ValidatorBuilder { + private final Function15 mapper; + private ValidatorBuilder> builder; + + public Arguments15ValidatorBuilder( + Function15 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments15ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments15Validator build() { + return new DefaultArguments15Validator<>(this.builder.build(), this.mapper); + } + } + + /** + * @since 0.3.0 + */ + public static final class Arguments16ValidatorBuilder { + private final Function16 mapper; + private ValidatorBuilder> builder; + + public Arguments16ValidatorBuilder( + Function16 mapper) { + this.mapper = Objects.requireNonNull(mapper, "'mapper' must not be null."); + } + + public Arguments16ValidatorBuilder builder( + Function>, ? extends ValidatorBuilder>> definition) { + this.builder = definition.apply(ValidatorBuilder.of()); + return this; + } + + public Arguments16Validator build() { + return new DefaultArguments16Validator<>(this.builder.build(), this.mapper); + } + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BigDecimalValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BigDecimalValidatorBuilder.java new file mode 100644 index 0000000..beb2b30 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BigDecimalValidatorBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.math.BigDecimal; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.BigDecimalValidator; +import org.xbib.datastructures.validation.constraint.BigDecimalConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class BigDecimalValidatorBuilder { + private final String name; + + private final Function>, BigDecimalConstraint>> constraints; + + public static BigDecimalValidatorBuilder of(String name, + Function>, BigDecimalConstraint>> constraints) { + return new BigDecimalValidatorBuilder(name, constraints); + } + + BigDecimalValidatorBuilder(String name, + Function>, BigDecimalConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public BigDecimalValidator build( + Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new BigDecimalValidator<>(validator, mapper::apply); + } + + public BigDecimalValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BigIntegerValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BigIntegerValidatorBuilder.java new file mode 100644 index 0000000..75d8e17 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BigIntegerValidatorBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.math.BigInteger; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.BigIntegerValidator; +import org.xbib.datastructures.validation.constraint.BigIntegerConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class BigIntegerValidatorBuilder { + private final String name; + + private final Function>, BigIntegerConstraint>> constraints; + + public static BigIntegerValidatorBuilder of(String name, + Function>, BigIntegerConstraint>> constraints) { + return new BigIntegerValidatorBuilder(name, constraints); + } + + BigIntegerValidatorBuilder(String name, + Function>, BigIntegerConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public BigIntegerValidator build( + Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new BigIntegerValidator<>(validator, mapper::apply); + } + + public BigIntegerValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BooleanValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BooleanValidatorBuilder.java new file mode 100644 index 0000000..8c9fff3 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/BooleanValidatorBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.BooleanValidator; +import org.xbib.datastructures.validation.constraint.BooleanConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class BooleanValidatorBuilder { + private final String name; + + private final Function>, BooleanConstraint>> constraints; + + public static BooleanValidatorBuilder of(String name, + Function>, BooleanConstraint>> constraints) { + return new BooleanValidatorBuilder(name, constraints); + } + + BooleanValidatorBuilder(String name, + Function>, BooleanConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public BooleanValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new BooleanValidator<>(validator, mapper::apply); + } + + public BooleanValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/DoubleValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/DoubleValidatorBuilder.java new file mode 100644 index 0000000..6d47314 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/DoubleValidatorBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.DoubleValidator; +import org.xbib.datastructures.validation.constraint.DoubleConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class DoubleValidatorBuilder { + private final String name; + + private final Function>, DoubleConstraint>> constraints; + + public static DoubleValidatorBuilder of(String name, + Function>, DoubleConstraint>> constraints) { + return new DoubleValidatorBuilder(name, constraints); + } + + DoubleValidatorBuilder(String name, + Function>, DoubleConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public DoubleValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of().constraint(Arguments1::arg1, name, constraints) + .build(); + return new DoubleValidator<>(validator, mapper::apply); + } + + public DoubleValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/FloatValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/FloatValidatorBuilder.java new file mode 100644 index 0000000..d7df9e0 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/FloatValidatorBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.FloatValidator; +import org.xbib.datastructures.validation.constraint.FloatConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class FloatValidatorBuilder { + private final String name; + + private final Function>, FloatConstraint>> constraints; + + public static FloatValidatorBuilder of(String name, + Function>, FloatConstraint>> constraints) { + return new FloatValidatorBuilder(name, constraints); + } + + FloatValidatorBuilder(String name, + Function>, FloatConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public FloatValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of().constraint(Arguments1::arg1, name, constraints) + .build(); + return new FloatValidator<>(validator, mapper::apply); + } + + public FloatValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/InstantValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/InstantValidatorBuilder.java new file mode 100644 index 0000000..9ad8e53 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/InstantValidatorBuilder.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.time.Instant; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.InstantValidator; +import org.xbib.datastructures.validation.constraint.InstantConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.10.0 + */ +public class InstantValidatorBuilder { + private final String name; + + private final Function>, InstantConstraint>> constraints; + + public static InstantValidatorBuilder of(String name, + Function>, InstantConstraint>> constraints) { + return new InstantValidatorBuilder(name, constraints); + } + + InstantValidatorBuilder(String name, + Function>, InstantConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public InstantValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new InstantValidator<>(validator, mapper::apply); + } + + public InstantValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/IntegerValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/IntegerValidatorBuilder.java new file mode 100644 index 0000000..01d327e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/IntegerValidatorBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.IntegerValidator; +import org.xbib.datastructures.validation.constraint.IntegerConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class IntegerValidatorBuilder { + private final String name; + + private final Function>, IntegerConstraint>> constraints; + + public static IntegerValidatorBuilder of(String name, + Function>, IntegerConstraint>> constraints) { + return new IntegerValidatorBuilder(name, constraints); + } + + IntegerValidatorBuilder(String name, + Function>, IntegerConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public IntegerValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new IntegerValidator<>(validator, mapper::apply); + } + + public IntegerValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LocalDateTimeValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LocalDateTimeValidatorBuilder.java new file mode 100644 index 0000000..c454eb8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LocalDateTimeValidatorBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.time.LocalDateTime; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.LocalDateTimeValidator; +import org.xbib.datastructures.validation.constraint.LocalDateTimeConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.10.0 + */ +public class LocalDateTimeValidatorBuilder { + private final String name; + + private final Function>, LocalDateTimeConstraint>> constraints; + + public static LocalDateTimeValidatorBuilder of(String name, + Function>, LocalDateTimeConstraint>> constraints) { + return new LocalDateTimeValidatorBuilder(name, constraints); + } + + LocalDateTimeValidatorBuilder(String name, + Function>, LocalDateTimeConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public LocalDateTimeValidator build( + Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new LocalDateTimeValidator<>(validator, mapper::apply); + } + + public LocalDateTimeValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LocalDateValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LocalDateValidatorBuilder.java new file mode 100644 index 0000000..677036d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LocalDateValidatorBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.time.LocalDate; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.LocalDateValidator; +import org.xbib.datastructures.validation.constraint.LocalDateConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.10.0 + */ +public class LocalDateValidatorBuilder { + private final String name; + + private final Function>, LocalDateConstraint>> constraints; + + public static LocalDateValidatorBuilder of(String name, + Function>, LocalDateConstraint>> constraints) { + return new LocalDateValidatorBuilder(name, constraints); + } + + LocalDateValidatorBuilder(String name, + Function>, LocalDateConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public LocalDateValidator build( + Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new LocalDateValidator<>(validator, mapper::apply); + } + + public LocalDateValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LongValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LongValidatorBuilder.java new file mode 100644 index 0000000..4a5f41c --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/LongValidatorBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.LongValidator; +import org.xbib.datastructures.validation.constraint.LongConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class LongValidatorBuilder { + private final String name; + + private final Function>, LongConstraint>> constraints; + + public static LongValidatorBuilder of(String name, + Function>, LongConstraint>> constraints) { + return new LongValidatorBuilder(name, constraints); + } + + LongValidatorBuilder(String name, + Function>, LongConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public LongValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of().constraint(Arguments1::arg1, name, constraints) + .build(); + return new LongValidator<>(validator, mapper::apply); + } + + public LongValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ObjectValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ObjectValidatorBuilder.java new file mode 100644 index 0000000..05ea018 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ObjectValidatorBuilder.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.ObjectValidator; +import org.xbib.datastructures.validation.constraint.ObjectConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.8.0 + */ +public class ObjectValidatorBuilder { + private final String name; + + private final Function, X>, ObjectConstraint, X>> constraints; + + public static ObjectValidatorBuilder of(String name, + Function, X>, ObjectConstraint, X>> constraints) { + return new ObjectValidatorBuilder<>(name, constraints); + } + + ObjectValidatorBuilder(String name, + Function, X>, ObjectConstraint, X>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public ObjectValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder.> of() + .constraintOnObject(Arguments1::arg1, name, constraints).build(); + return new ObjectValidator<>(validator, mapper::apply); + } + + public ObjectValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/OffsetDateTimeValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/OffsetDateTimeValidatorBuilder.java new file mode 100644 index 0000000..ab6b90b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/OffsetDateTimeValidatorBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.time.OffsetDateTime; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.OffsetDateTimeValidator; +import org.xbib.datastructures.validation.constraint.OffsetDateTimeConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.10.0 + */ +public class OffsetDateTimeValidatorBuilder { + private final String name; + + private final Function>, OffsetDateTimeConstraint>> constraints; + + public static OffsetDateTimeValidatorBuilder of(String name, + Function>, OffsetDateTimeConstraint>> constraints) { + return new OffsetDateTimeValidatorBuilder(name, constraints); + } + + OffsetDateTimeValidatorBuilder(String name, + Function>, OffsetDateTimeConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public OffsetDateTimeValidator build( + Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new OffsetDateTimeValidator<>(validator, mapper::apply); + } + + public OffsetDateTimeValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ShortValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ShortValidatorBuilder.java new file mode 100644 index 0000000..132ea7b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ShortValidatorBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.ShortValidator; +import org.xbib.datastructures.validation.constraint.ShortConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class ShortValidatorBuilder { + private final String name; + + private final Function>, ShortConstraint>> constraints; + + public static ShortValidatorBuilder of(String name, + Function>, ShortConstraint>> constraints) { + return new ShortValidatorBuilder(name, constraints); + } + + ShortValidatorBuilder(String name, + Function>, ShortConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public ShortValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of().constraint(Arguments1::arg1, name, constraints) + .build(); + return new ShortValidator<>(validator, mapper::apply); + } + + public ShortValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/StringValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/StringValidatorBuilder.java new file mode 100644 index 0000000..15efa10 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/StringValidatorBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.StringValidator; +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.7.0 + */ +public class StringValidatorBuilder { + private final String name; + + private final Function, String>, CharSequenceConstraint, String>> constraints; + + public static StringValidatorBuilder of(String name, + Function, String>, CharSequenceConstraint, String>> constraints) { + return new StringValidatorBuilder(name, constraints); + } + + StringValidatorBuilder(String name, + Function, String>, CharSequenceConstraint, String>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public StringValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of().constraint(Arguments1::arg1, name, constraints) + .build(); + return new StringValidator<>(validator, mapper::apply); + } + + public StringValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ValidatorBuilder.java new file mode 100644 index 0000000..a897e40 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ValidatorBuilder.java @@ -0,0 +1,1197 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.constraint.BigDecimalConstraint; +import org.xbib.datastructures.validation.constraint.BigIntegerConstraint; +import org.xbib.datastructures.validation.constraint.BooleanConstraint; +import org.xbib.datastructures.validation.constraint.ByteConstraint; +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.xbib.datastructures.validation.constraint.CharacterConstraint; +import org.xbib.datastructures.validation.constraint.CollectionConstraint; +import org.xbib.datastructures.validation.constraint.DoubleConstraint; +import org.xbib.datastructures.validation.constraint.FloatConstraint; +import org.xbib.datastructures.validation.constraint.InstantConstraint; +import org.xbib.datastructures.validation.constraint.IntegerConstraint; +import org.xbib.datastructures.validation.constraint.LocalDateConstraint; +import org.xbib.datastructures.validation.constraint.LocalDateTimeConstraint; +import org.xbib.datastructures.validation.constraint.LocalTimeConstraint; +import org.xbib.datastructures.validation.constraint.LongConstraint; +import org.xbib.datastructures.validation.constraint.MapConstraint; +import org.xbib.datastructures.validation.constraint.ObjectConstraint; +import org.xbib.datastructures.validation.constraint.OffsetDateTimeConstraint; +import org.xbib.datastructures.validation.constraint.ShortConstraint; +import org.xbib.datastructures.validation.constraint.YearConstraint; +import org.xbib.datastructures.validation.constraint.YearMonthConstraint; +import org.xbib.datastructures.validation.constraint.ZonedDateTimeConstraint; +import org.xbib.datastructures.validation.constraint.array.BooleanArrayConstraint; +import org.xbib.datastructures.validation.constraint.array.ByteArrayConstraint; +import org.xbib.datastructures.validation.constraint.array.CharArrayConstraint; +import org.xbib.datastructures.validation.constraint.array.DoubleArrayConstraint; +import org.xbib.datastructures.validation.constraint.array.FloatArrayConstraint; +import org.xbib.datastructures.validation.constraint.array.IntArrayConstraint; +import org.xbib.datastructures.validation.constraint.array.LongArrayConstraint; +import org.xbib.datastructures.validation.constraint.array.ObjectArrayConstraint; +import org.xbib.datastructures.validation.constraint.array.ShortArrayConstraint; +import org.xbib.datastructures.validation.core.BiValidator; +import org.xbib.datastructures.validation.core.CollectionValidator; +import org.xbib.datastructures.validation.core.Constraint; +import org.xbib.datastructures.validation.core.ConstraintCondition; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintPredicate; +import org.xbib.datastructures.validation.core.ConstraintPredicates; +import org.xbib.datastructures.validation.core.CustomConstraint; +import org.xbib.datastructures.validation.core.NestedCollectionValidator; +import org.xbib.datastructures.validation.core.NestedConstraintCondition; +import org.xbib.datastructures.validation.core.NestedConstraintPredicates; +import org.xbib.datastructures.validation.core.NestedValidator; +import org.xbib.datastructures.validation.core.NullAs; +import org.xbib.datastructures.validation.core.Validatable; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.xbib.datastructures.validation.core.ViolatedArguments; +import org.xbib.datastructures.validation.core.ViolationMessage; +import org.xbib.datastructures.validation.fn.Pair; +import org.xbib.datastructures.validation.message.MessageFormatter; +import org.xbib.datastructures.validation.message.SimpleMessageFormatter; +import org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta; +import org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta; +import org.xbib.datastructures.validation.meta.BooleanConstraintMeta; +import org.xbib.datastructures.validation.meta.ByteConstraintMeta; +import org.xbib.datastructures.validation.meta.CharacterConstraintMeta; +import org.xbib.datastructures.validation.meta.DoubleConstraintMeta; +import org.xbib.datastructures.validation.meta.FloatConstraintMeta; +import org.xbib.datastructures.validation.meta.InstantConstraintMeta; +import org.xbib.datastructures.validation.meta.IntegerConstraintMeta; +import org.xbib.datastructures.validation.meta.LocalDateConstraintMeta; +import org.xbib.datastructures.validation.meta.LocalDateTimeConstraintMeta; +import org.xbib.datastructures.validation.meta.LocalTimeConstraintMeta; +import org.xbib.datastructures.validation.meta.LongConstraintMeta; +import org.xbib.datastructures.validation.meta.ObjectConstraintMeta; +import org.xbib.datastructures.validation.meta.OffsetDateTimeConstraintMeta; +import org.xbib.datastructures.validation.meta.ShortConstraintMeta; +import org.xbib.datastructures.validation.meta.StringConstraintMeta; +import org.xbib.datastructures.validation.meta.YearConstraintMeta; +import org.xbib.datastructures.validation.meta.YearMonthConstraintMeta; +import org.xbib.datastructures.validation.meta.ZonedDateTimeConstraintMeta; + +public class ValidatorBuilder implements Cloneable { + private static final String DEFAULT_SEPARATOR = "."; + + final List> collectionValidators = new ArrayList<>(); + + final List, Validatable>> conditionalValidators = new ArrayList<>(); + + final String messageKeySeparator; + + final List> predicatesList = new ArrayList<>(); + + MessageFormatter messageFormatter; + + boolean failFast = false; + + public ValidatorBuilder() { + this(DEFAULT_SEPARATOR); + } + + public ValidatorBuilder(String messageKeySeparator) { + this.messageKeySeparator = messageKeySeparator; + } + + /** + * The copy constructor + * + * @since 0.10.0 + */ + public ValidatorBuilder(ValidatorBuilder cloningSource) { + this(cloningSource.messageKeySeparator); + this.collectionValidators.addAll(cloningSource.collectionValidators); + this.conditionalValidators.addAll(cloningSource.conditionalValidators); + this.predicatesList.addAll(cloningSource.predicatesList); + this.messageFormatter = cloningSource.messageFormatter; + this.failFast = cloningSource.failFast; + } + + @SuppressWarnings("unchecked") + public ValidatorBuilder cast(Class clazz) { + return (ValidatorBuilder) this; + } + + @SuppressWarnings("unchecked") + public ValidatorBuilder cast() { + return (ValidatorBuilder) this; + } + + /** + * @deprecated please use the copy constructor + * {@link #ValidatorBuilder(ValidatorBuilder)} + * @return the cloned builder + */ + @Deprecated + public ValidatorBuilder clone() { + return new ValidatorBuilder<>(this); + } + + /** + * Factory method for a {@code ValidatorBuilder} to build {@code Validator} instance. + * @param the type of the instance to validate + * @return builder instance + */ + public static ValidatorBuilder of(Class clazz) { + return new ValidatorBuilder<>(); + } + + /** + * Factory method for a {@code ValidatorBuilder} to build {@code Validator} instance. + * @param the type of the instance to validate + * @return builder instance + */ + public static ValidatorBuilder of() { + return new ValidatorBuilder<>(); + } + + public Validator build() { + return new Validator<>(messageKeySeparator, this.predicatesList, + this.collectionValidators, this.conditionalValidators, + this.messageFormatter == null ? new SimpleMessageFormatter() + : this.messageFormatter, + this.failFast); + } + + /** + * Create a BiValidator instance using the given constraints. + * + * In case of Spring Framework's Validator integration + * + *
+	 * BiValidator<CartItem, Errors> validator = ValidatorBuilder
+	 *   .<CartItem>of()
+	 *   .constraint(...)
+	 *   .build(Errors::rejectValue);
+	 * 
+ * + * @param errorHandler handler that handle if the validation fails + * @param the type of the error object + * @return BiValidator instance + */ + public BiValidator build(BiValidator.ErrorHandler errorHandler) { + final Validator validator = this.build(); + return new BiValidator<>(validator, errorHandler); + } + + public ValidatorBuilder constraint(ToCharSequence f, String name, + Function, CharSequenceConstraint> c) { + return this.constraint(f, name, c, CharSequenceConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(StringConstraintMeta meta, + Function, CharSequenceConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, + CharSequenceConstraint::new); + } + + public ValidatorBuilder _charSequence( + ToCharSequence f, String name, + Function, CharSequenceConstraint> c) { + return this.constraint(f, name, c, CharSequenceConstraint::new); + } + + public ValidatorBuilder _string(ToCharSequence f, String name, + Function, CharSequenceConstraint> c) { + return this.constraint(f, name, c, CharSequenceConstraint::new); + } + + public ValidatorBuilder constraint(ToBoolean f, String name, + Function, BooleanConstraint> c) { + return this.constraint(f, name, c, BooleanConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(BooleanConstraintMeta meta, + Function, BooleanConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, BooleanConstraint::new); + } + + public ValidatorBuilder _boolean(ToBoolean f, String name, + Function, BooleanConstraint> c) { + return this.constraint(f, name, c, BooleanConstraint::new); + } + + public ValidatorBuilder constraint(ToCharacter f, String name, + Function, CharacterConstraint> c) { + return this.constraint(f, name, c, CharacterConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(CharacterConstraintMeta meta, + Function, CharacterConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, CharacterConstraint::new); + } + + public ValidatorBuilder _character(ToCharacter f, String name, + Function, CharacterConstraint> c) { + return this.constraint(f, name, c, CharacterConstraint::new); + } + + public ValidatorBuilder constraint(ToByte f, String name, + Function, ByteConstraint> c) { + return this.constraint(f, name, c, ByteConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(ByteConstraintMeta meta, + Function, ByteConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, ByteConstraint::new); + } + + public ValidatorBuilder _byte(ToByte f, String name, + Function, ByteConstraint> c) { + return this.constraint(f, name, c, ByteConstraint::new); + } + + public ValidatorBuilder constraint(ToShort f, String name, + Function, ShortConstraint> c) { + return this.constraint(f, name, c, ShortConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(ShortConstraintMeta meta, + Function, ShortConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, ShortConstraint::new); + } + + public ValidatorBuilder _short(ToShort f, String name, + Function, ShortConstraint> c) { + return this.constraint(f, name, c, ShortConstraint::new); + } + + public ValidatorBuilder constraint(ToInteger f, String name, + Function, IntegerConstraint> c) { + return this.constraint(f, name, c, IntegerConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(IntegerConstraintMeta meta, + Function, IntegerConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, IntegerConstraint::new); + } + + public ValidatorBuilder _integer(ToInteger f, String name, + Function, IntegerConstraint> c) { + return this.constraint(f, name, c, IntegerConstraint::new); + } + + public ValidatorBuilder constraint(ToLong f, String name, + Function, LongConstraint> c) { + return this.constraint(f, name, c, LongConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(LongConstraintMeta meta, + Function, LongConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, LongConstraint::new); + } + + public ValidatorBuilder _long(ToLong f, String name, + Function, LongConstraint> c) { + return this.constraint(f, name, c, LongConstraint::new); + } + + public ValidatorBuilder constraint(ToFloat f, String name, + Function, FloatConstraint> c) { + return this.constraint(f, name, c, FloatConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(FloatConstraintMeta meta, + Function, FloatConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, FloatConstraint::new); + } + + public ValidatorBuilder _float(ToFloat f, String name, + Function, FloatConstraint> c) { + return this.constraint(f, name, c, FloatConstraint::new); + } + + public ValidatorBuilder constraint(ToDouble f, String name, + Function, DoubleConstraint> c) { + return this.constraint(f, name, c, DoubleConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(DoubleConstraintMeta meta, + Function, DoubleConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, DoubleConstraint::new); + } + + public ValidatorBuilder _double(ToDouble f, String name, + Function, DoubleConstraint> c) { + return this.constraint(f, name, c, DoubleConstraint::new); + } + + public ValidatorBuilder constraint(ToBigInteger f, String name, + Function, BigIntegerConstraint> c) { + return this.constraint(f, name, c, BigIntegerConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(BigIntegerConstraintMeta meta, + Function, BigIntegerConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, BigIntegerConstraint::new); + } + + public ValidatorBuilder _bigInteger(ToBigInteger f, String name, + Function, BigIntegerConstraint> c) { + return this.constraint(f, name, c, BigIntegerConstraint::new); + } + + public ValidatorBuilder constraint(ToBigDecimal f, String name, + Function, BigDecimalConstraint> c) { + return this.constraint(f, name, c, BigDecimalConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder constraint(BigDecimalConstraintMeta meta, + Function, BigDecimalConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, BigDecimalConstraint::new); + } + + public ValidatorBuilder _bigDecimal(ToBigDecimal f, String name, + Function, BigDecimalConstraint> c) { + return this.constraint(f, name, c, BigDecimalConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(ToLocalTimeConstraint f, String name, + Function, LocalTimeConstraint> c) { + return this.constraint(f, name, c, LocalTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(LocalTimeConstraintMeta meta, + Function, LocalTimeConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, LocalTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder _localTime(ToLocalTimeConstraint f, String name, + Function, LocalTimeConstraint> c) { + return this.constraint(f, name, c, LocalTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(ToLocalDateConstraint f, String name, + Function, LocalDateConstraint> c) { + return this.constraint(f, name, c, LocalDateConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(LocalDateConstraintMeta meta, + Function, LocalDateConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, LocalDateConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder _localDate(ToLocalDateConstraint f, String name, + Function, LocalDateConstraint> c) { + return this.constraint(f, name, c, LocalDateConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(ToLocalDateTimeConstraint f, String name, + Function, LocalDateTimeConstraint> c) { + return this.constraint(f, name, c, LocalDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(LocalDateTimeConstraintMeta meta, + Function, LocalDateTimeConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, + LocalDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder _localDateTime(ToLocalDateTimeConstraint f, String name, + Function, LocalDateTimeConstraint> c) { + return this.constraint(f, name, c, LocalDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(ToZonedDateTimeConstraint f, String name, + Function, ZonedDateTimeConstraint> c) { + return this.constraint(f, name, c, ZonedDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(ZonedDateTimeConstraintMeta meta, + Function, ZonedDateTimeConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, + ZonedDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder _zonedDateTime(ToZonedDateTimeConstraint f, String name, + Function, ZonedDateTimeConstraint> c) { + return this.constraint(f, name, c, ZonedDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(ToOffsetDateTimeConstraint f, String name, + Function, OffsetDateTimeConstraint> c) { + return this.constraint(f, name, c, OffsetDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(OffsetDateTimeConstraintMeta meta, + Function, OffsetDateTimeConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, + OffsetDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder _offsetDateTime(ToOffsetDateTimeConstraint f, + String name, + Function, OffsetDateTimeConstraint> c) { + return this.constraint(f, name, c, OffsetDateTimeConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(ToInstantConstraint f, String name, + Function, InstantConstraint> c) { + return this.constraint(f, name, c, InstantConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder constraint(InstantConstraintMeta meta, + Function, InstantConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, InstantConstraint::new); + } + + /** + * @since 0.10.0 + */ + public ValidatorBuilder _instant(ToInstantConstraint f, String name, + Function, InstantConstraint> c) { + return this.constraint(f, name, c, InstantConstraint::new); + } + + /** + * @since 0.11.0 + */ + public ValidatorBuilder constraint(ToYearMonthConstraint f, String name, + Function, YearMonthConstraint> c) { + return this.constraint(f, name, c, YearMonthConstraint::new); + } + + /** + * @since 0.11.0 + */ + public ValidatorBuilder constraint(YearMonthConstraintMeta meta, + Function, YearMonthConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, YearMonthConstraint::new); + } + + /** + * @since 0.11.0 + */ + public ValidatorBuilder _yearMonth(ToYearMonthConstraint f, String name, + Function, YearMonthConstraint> c) { + return this.constraint(f, name, c, YearMonthConstraint::new); + } + + /** + * @since 0.11.0 + */ + public ValidatorBuilder constraint(ToYearConstraint f, String name, + Function, YearConstraint> c) { + return this.constraint(f, name, c, YearConstraint::new); + } + + /** + * @since 0.11.0 + */ + public ValidatorBuilder constraint(YearConstraintMeta meta, + Function, YearConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, YearConstraint::new); + } + + /** + * @since 0.11.0 + */ + public ValidatorBuilder _year(ToYearConstraint f, String name, + Function, YearConstraint> c) { + return this.constraint(f, name, c, YearConstraint::new); + } + + public , E> ValidatorBuilder constraint( + ToCollection f, String name, + Function, CollectionConstraint> c) { + return this.constraint(f, name, c, CollectionConstraint::new); + } + + public , E> ValidatorBuilder _collection( + ToCollection f, String name, + Function, CollectionConstraint> c) { + return this.constraint(f, name, c, CollectionConstraint::new); + } + + public ValidatorBuilder constraint(ToMap f, String name, + Function, MapConstraint> c) { + return this.constraint(f, name, c, MapConstraint::new); + } + + public ValidatorBuilder _map(ToMap f, String name, + Function, MapConstraint> c) { + return this.constraint(f, name, c, MapConstraint::new); + } + + public ValidatorBuilder constraint(ToObjectArray f, String name, + Function, ObjectArrayConstraint> c) { + return this.constraint(f, name, c, ObjectArrayConstraint::new); + } + + public ValidatorBuilder _objectArray(ToObjectArray f, String name, + Function, ObjectArrayConstraint> c) { + return this.constraint(f, name, c, ObjectArrayConstraint::new); + } + + public ValidatorBuilder constraint(ToBooleanArray f, String name, + Function, BooleanArrayConstraint> c) { + return this.constraint(f, name, c, BooleanArrayConstraint::new); + } + + public ValidatorBuilder _booleanArray(ToBooleanArray f, String name, + Function, BooleanArrayConstraint> c) { + return this.constraint(f, name, c, BooleanArrayConstraint::new); + } + + public ValidatorBuilder constraint(ToCharArray f, String name, + Function, CharArrayConstraint> c) { + return this.constraint(f, name, c, CharArrayConstraint::new); + } + + public ValidatorBuilder _charArray(ToCharArray f, String name, + Function, CharArrayConstraint> c) { + return this.constraint(f, name, c, CharArrayConstraint::new); + } + + public ValidatorBuilder constraint(ToByteArray f, String name, + Function, ByteArrayConstraint> c) { + return this.constraint(f, name, c, ByteArrayConstraint::new); + } + + public ValidatorBuilder _byteArray(ToByteArray f, String name, + Function, ByteArrayConstraint> c) { + return this.constraint(f, name, c, ByteArrayConstraint::new); + } + + public ValidatorBuilder constraint(ToShortArray f, String name, + Function, ShortArrayConstraint> c) { + return this.constraint(f, name, c, ShortArrayConstraint::new); + } + + public ValidatorBuilder _shortArray(ToShortArray f, String name, + Function, ShortArrayConstraint> c) { + return this.constraint(f, name, c, ShortArrayConstraint::new); + } + + public ValidatorBuilder constraint(ToIntArray f, String name, + Function, IntArrayConstraint> c) { + return this.constraint(f, name, c, IntArrayConstraint::new); + } + + public ValidatorBuilder _intArray(ToIntArray f, String name, + Function, IntArrayConstraint> c) { + return this.constraint(f, name, c, IntArrayConstraint::new); + } + + public ValidatorBuilder constraint(ToLongArray f, String name, + Function, LongArrayConstraint> c) { + return this.constraint(f, name, c, LongArrayConstraint::new); + } + + public ValidatorBuilder _longArray(ToLongArray f, String name, + Function, LongArrayConstraint> c) { + return this.constraint(f, name, c, LongArrayConstraint::new); + } + + public ValidatorBuilder constraint(ToFloatArray f, String name, + Function, FloatArrayConstraint> c) { + return this.constraint(f, name, c, FloatArrayConstraint::new); + } + + public ValidatorBuilder _floatArray(ToFloatArray f, String name, + Function, FloatArrayConstraint> c) { + return this.constraint(f, name, c, FloatArrayConstraint::new); + } + + public ValidatorBuilder constraint(ToDoubleArray f, String name, + Function, DoubleArrayConstraint> c) { + return this.constraint(f, name, c, DoubleArrayConstraint::new); + } + + public ValidatorBuilder _doubleArray(ToDoubleArray f, String name, + Function, DoubleArrayConstraint> c) { + return this.constraint(f, name, c, DoubleArrayConstraint::new); + } + + /** + * @since 0.11.0 + */ + public ValidatorBuilder constraintOnCondition(ConstraintCondition condition, + ValueValidator applicative) { + this.conditionalValidators + .add(new Pair<>(condition, Validatable.from(applicative))); + return this; + } + + public ValidatorBuilder constraintOnCondition(ConstraintCondition condition, + Validator validator) { + this.conditionalValidators.add(new Pair<>(condition, validator)); + return this; + } + + public ValidatorBuilder constraintOnCondition(ConstraintCondition condition, + ValidatorBuilderConverter converter) { + ValidatorBuilder builder = converter.apply(new ValidatorBuilder<>()); + Validator validator = builder.build(); + return this.constraintOnCondition(condition, validator); + } + + public ValidatorBuilder constraintOnGroup(ConstraintGroup group, + ValueValidator applicative) { + return this.constraintOnCondition(group.toCondition(), applicative); + } + + public ValidatorBuilder constraintOnGroup(ConstraintGroup group, + Validator validator) { + return this.constraintOnCondition(group.toCondition(), validator); + } + + public ValidatorBuilder constraintOnGroup(ConstraintGroup group, + ValidatorBuilderConverter converter) { + return this.constraintOnCondition(group.toCondition(), converter); + } + + public ValidatorBuilder constraintOnObject(Function f, String name, + Function, ObjectConstraint> c) { + return this.constraint(f, name, c, ObjectConstraint::new); + } + + /** + * @since 0.4.0 + */ + public ValidatorBuilder _object(Function f, String name, + Function, ObjectConstraint> c) { + return this.constraint(f, name, c, ObjectConstraint::new); + } + + public ValidatorBuilder constraint(ObjectConstraintMeta meta, + Function, ObjectConstraint> c) { + return this.constraint(meta.toValue(), meta.name(), c, ObjectConstraint::new); + } + + public ValidatorBuilder constraintOnTarget(Predicate predicate, String name, + ViolatedArguments violatedArguments, ViolationMessage violationMessage) { + Deque> predicates = new LinkedList<>(); + predicates.add(ConstraintPredicate.of(predicate, violationMessage, + violatedArguments, NullAs.INVALID)); + this.predicatesList + .add(new ConstraintPredicates<>(Function.identity(), name, predicates)); + return this; + } + + public ValidatorBuilder constraintOnTarget(Predicate predicate, String name, + ViolationMessage violationMessage) { + return this.constraintOnTarget(predicate, name, + violatedValue -> CustomConstraint.EMPTY_ARRAY, violationMessage); + } + + public ValidatorBuilder constraintOnTarget(Predicate predicate, String name, + String messageKey, String defaultMessage) { + return this.constraintOnTarget(predicate, name, + ViolationMessage.of(messageKey, defaultMessage)); + } + + public ValidatorBuilder constraintOnTarget(CustomConstraint customConstraint, + String name) { + return this.constraintOnTarget(customConstraint, name, customConstraint, + customConstraint); + } + + /** + * @since 0.7.0 + */ + public ValidatorBuilder constraintOnTarget(String name, + Function, ObjectConstraint> c) { + return this.constraint(Function.identity(), name, c, ObjectConstraint::new); + } + + public , E> ValidatorBuilder forEach( + ToCollection toCollection, String name, Validator validator) { + return this.forEach(toCollection, name, validator, NullAs.INVALID); + } + + public , E> ValidatorBuilder forEach( + ToCollection toCollection, String name, + ValidatorBuilderConverter converter) { + return this.forEach(toCollection, name, converter, NullAs.INVALID); + } + + public ValidatorBuilder forEach(ToMap toMap, String name, + Validator validator) { + return this.forEach(this.toMapToCollection(toMap), name, validator); + } + + public ValidatorBuilder forEach(ToMap toMap, String name, + ValidatorBuilderConverter converter) { + return this.forEach(this.toMapToCollection(toMap), name, converter); + } + + public ValidatorBuilder forEach(ToObjectArray toObjectArray, String name, + Validator validator) { + return this.forEach(this.toObjectArrayToCollection(toObjectArray), name, + validator); + } + + public ValidatorBuilder forEach(ToObjectArray toObjectArray, String name, + ValidatorBuilderConverter converter) { + return this.forEach(this.toObjectArrayToCollection(toObjectArray), name, + converter); + } + + public , E> ValidatorBuilder forEachIfPresent( + ToCollection toCollection, String name, Validator validator) { + return this.forEach(toCollection, name, validator, NullAs.VALID); + } + + public , E> ValidatorBuilder forEachIfPresent( + ToCollection toCollection, String name, + ValidatorBuilderConverter converter) { + return this.forEach(toCollection, name, converter, NullAs.VALID); + } + + public ValidatorBuilder forEachIfPresent(ToMap toMap, String name, + Validator validator) { + return this.forEachIfPresent(this.toMapToCollection(toMap), name, validator); + } + + public ValidatorBuilder forEachIfPresent(ToMap toMap, String name, + ValidatorBuilderConverter converter) { + return this.forEachIfPresent(this.toMapToCollection(toMap), name, converter); + } + + public ValidatorBuilder forEachIfPresent(ToObjectArray toObjectArray, + String name, Validator validator) { + return this.forEachIfPresent(this.toObjectArrayToCollection(toObjectArray), name, + validator); + } + + public ValidatorBuilder forEachIfPresent(ToObjectArray toObjectArray, + String name, ValidatorBuilderConverter converter) { + return this.forEachIfPresent(this.toObjectArrayToCollection(toObjectArray), name, + converter); + } + + public ValidatorBuilder messageFormatter(MessageFormatter messageFormatter) { + this.messageFormatter = messageFormatter; + return this; + } + + /** + * Set whether to enable fail fast mode. If enabled, Validator returns from the + * current validation as soon as the first constraint violation occurs. + * + * @param failFast whether to enable fail fast mode + * @since 0.8.0 + */ + public ValidatorBuilder failFast(boolean failFast) { + this.failFast = failFast; + return this; + } + + public ValidatorBuilder nest(Function nested, String name, + Validator validator) { + return this.nest(nested, name, validator, NullAs.INVALID); + } + + public ValidatorBuilder nest(ObjectConstraintMeta meta, + Validator validator) { + return this.nest(meta.toValue(), meta.name(), validator, NullAs.INVALID); + } + + public ValidatorBuilder nest(Function nested, String name, + ValidatorBuilderConverter converter) { + return this.nest(nested, name, converter, NullAs.INVALID); + } + + public ValidatorBuilder nest(ObjectConstraintMeta meta, + ValidatorBuilderConverter converter) { + return this.nest(meta.toValue(), meta.name(), converter, NullAs.INVALID); + } + + public ValidatorBuilder nestIfPresent(Function nested, String name, + Validator validator) { + return this.nest(nested, name, validator, NullAs.VALID); + } + + public ValidatorBuilder nestIfPresent(ObjectConstraintMeta meta, + Validator validator) { + return this.nest(meta.toValue(), meta.name(), validator, NullAs.VALID); + } + + public ValidatorBuilder nestIfPresent(Function nested, String name, + ValidatorBuilderConverter converter) { + return this.nest(nested, name, converter, NullAs.VALID); + } + + public ValidatorBuilder nestIfPresent(ObjectConstraintMeta meta, + ValidatorBuilderConverter converter) { + return this.nest(meta.toValue(), meta.name(), converter, NullAs.VALID); + } + + protected final > ValidatorBuilder constraint( + Function f, String name, Function c, Supplier supplier) { + C constraint = supplier.get(); + Deque> predicates = c.apply(constraint).predicates(); + this.predicatesList.add(new ConstraintPredicates<>(f, name, predicates)); + return this; + } + + protected , E> ValidatorBuilder forEach( + ToCollection toCollection, String name, + ValidatorBuilderConverter converter, NullAs nullAs) { + ValidatorBuilder builder = converter.apply(new ValidatorBuilder<>()); + return this.forEach(toCollection, name, builder.build(), nullAs); + } + + protected final , E> ValidatorBuilder forEach( + ToCollection toCollection, String name, Validator validator, + NullAs nullAs) { + if (!nullAs.skipNull()) { + this.constraintOnObject(toCollection, name, Constraint::notNull); + } + this.collectionValidators + .add(new CollectionValidator<>(toCollection, name, validator)); + return this; + } + + protected final ValidatorBuilder nest(Function nested, String name, + ValidatorBuilderConverter converter, NullAs nullAs) { + if (!nullAs.skipNull()) { + this.constraintOnObject(nested, name, Constraint::notNull); + } + ValidatorBuilder builder = converter.apply(new ValidatorBuilder<>()); + builder.predicatesList.forEach(this.appendNestedPredicates(nested, name)); + builder.conditionalValidators + .forEach(this.appendNestedConditionalValidator(nested, name)); + builder.collectionValidators + .forEach(appendNestedCollectionValidator(nested, name)); + return this; + } + + protected final ValidatorBuilder nest(Function nested, String name, + Validator validator, NullAs nullAs) { + if (!nullAs.skipNull()) { + this.constraintOnObject(nested, name, Constraint::notNull); + } + validator.forEachPredicates(this.appendNestedPredicates(nested, name)); + validator.forEachConditionalValidator( + this.appendNestedConditionalValidator(nested, name)); + validator.forEachCollectionValidator( + appendNestedCollectionValidator(nested, name)); + return this; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Consumer> appendNestedPredicates( + Function nested, String name) { + return predicates -> { + String nestedName = name + this.messageKeySeparator + predicates.name(); + ConstraintPredicates constraintPredicates = new NestedConstraintPredicates( + this.toNestedValue(nested, predicates), nestedName, + predicates.predicates(), this.toNestedFunction(nested, predicates)); + this.predicatesList.add(constraintPredicates); + }; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Function toNestedFunction(Function nested, + ConstraintPredicates predicates) { + if (predicates instanceof NestedConstraintPredicates) { + return target -> { + N nestedValue = nested.apply(target); + if (nestedValue == null) { + return null; + } + return (N) ((NestedConstraintPredicates) predicates) + .nestedValue(nestedValue); + }; + } + return nested; + } + + private Consumer, Validatable>> appendNestedConditionalValidator( + Function nested, String name) { + return conditionalValidator -> { + final ConstraintCondition condition = new NestedConstraintCondition<>( + nested, conditionalValidator.first()); + final Validatable validator = conditionalValidator.second(); + final String nestedPrefix = toNestedPrefix(name, validator); + final Validatable v = new NestedValidator<>(nested, validator, + nestedPrefix); + this.conditionalValidators.add(new Pair<>(condition, v)); + }; + } + + private String toNestedPrefix(String name, Validatable validator) { + if (validator instanceof NestedValidator) { + final NestedValidator nestedValidator = (NestedValidator) validator; + return name + this.messageKeySeparator + nestedValidator.getPrefix(); + } + return name + this.messageKeySeparator; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Consumer> appendNestedCollectionValidator( + Function nested, String name) { + return collectionValidator -> { + String nestedName = name + this.messageKeySeparator + + collectionValidator.name(); + CollectionValidator validator = new NestedCollectionValidator( + toNestedCollection(nested, collectionValidator), nestedName, + collectionValidator.validator(), nested); + this.collectionValidators.add(validator); + }; + } + + private ToCollection, V> toMapToCollection( + ToMap toMap) { + return t -> { + Map map = toMap.apply(t); + if (map == null) { + return null; + } + return map.values(); + }; + } + + private Function toNestedValue(Function nested, + ConstraintPredicates predicates) { + return (T target) -> { + N nestedValue = nested.apply(target); + if (nestedValue == null) { + return null; + } + return predicates.toValue().apply(nestedValue); + }; + } + + private > Function toNestedCollection( + Function nested, CollectionValidator validator) { + return (T target) -> { + N nestedCollection = nested.apply(target); + if (nestedCollection == null) { + return null; + } + return validator.toCollection().apply(nestedCollection); + }; + } + + private ToCollection, E> toObjectArrayToCollection( + ToObjectArray toObjectArray) { + return t -> { + E[] array = toObjectArray.apply(t); + if (array == null) { + return null; + } + return Arrays.asList(array); + }; + } + + public interface ToBigDecimal extends Function { + } + + public interface ToBigInteger extends Function { + } + + public interface ToBoolean extends Function { + } + + public interface ToBooleanArray extends Function { + } + + public interface ToByte extends Function { + } + + public interface ToByteArray extends Function { + } + + public interface ToCharArray extends Function { + } + + public interface ToCharSequence extends Function { + } + + public interface ToCharacter extends Function { + } + + public interface ToCollection, E> extends Function { + } + + public interface ToDouble extends Function { + } + + public interface ToDoubleArray extends Function { + } + + public interface ToFloat extends Function { + } + + public interface ToFloatArray extends Function { + } + + public interface ToIntArray extends Function { + } + + public interface ToInteger extends Function { + } + + public interface ToLong extends Function { + } + + public interface ToLongArray extends Function { + } + + public interface ToMap extends Function> { + } + + public interface ToObjectArray extends Function { + } + + public interface ToShort extends Function { + } + + public interface ToShortArray extends Function { + } + + public interface ToLocalTimeConstraint extends Function { + } + + public interface ToZonedDateTimeConstraint extends Function { + } + + public interface ToOffsetDateTimeConstraint extends Function { + } + + public interface ToLocalDateTimeConstraint extends Function { + } + + public interface ToLocalDateConstraint extends Function { + } + + public interface ToInstantConstraint extends Function { + } + + public interface ToYearConstraint extends Function { + } + + public interface ToYearMonthConstraint extends Function { + } + + public interface ValidatorBuilderConverter + extends Function, ValidatorBuilder> { + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/YearMonthValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/YearMonthValidatorBuilder.java new file mode 100644 index 0000000..ddcae33 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/YearMonthValidatorBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.time.YearMonth; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.YearMonthValidator; +import org.xbib.datastructures.validation.constraint.YearMonthConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.11.0 + */ +public class YearMonthValidatorBuilder { + private final String name; + + private final Function>, YearMonthConstraint>> constraints; + + public static YearMonthValidatorBuilder of(String name, + Function>, YearMonthConstraint>> constraints) { + return new YearMonthValidatorBuilder(name, constraints); + } + + YearMonthValidatorBuilder(String name, + Function>, YearMonthConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public YearMonthValidator build( + Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new YearMonthValidator<>(validator, mapper::apply); + } + + public YearMonthValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/YearValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/YearValidatorBuilder.java new file mode 100644 index 0000000..de101c7 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/YearValidatorBuilder.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.time.Year; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.YearValidator; +import org.xbib.datastructures.validation.constraint.YearConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.11.0 + */ +public class YearValidatorBuilder { + private final String name; + + private final Function>, YearConstraint>> constraints; + + public static YearValidatorBuilder of(String name, + Function>, YearConstraint>> constraints) { + return new YearValidatorBuilder(name, constraints); + } + + YearValidatorBuilder(String name, + Function>, YearConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public YearValidator build(Function mapper) { + final Validator> validator = ValidatorBuilder + .> of().constraint(Arguments1::arg1, name, constraints) + .build(); + return new YearValidator<>(validator, mapper::apply); + } + + public YearValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ZonedDateTimeValidatorBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ZonedDateTimeValidatorBuilder.java new file mode 100644 index 0000000..a7483fb --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/builder/ZonedDateTimeValidatorBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.builder; + +import java.time.ZonedDateTime; +import java.util.function.Function; + +import org.xbib.datastructures.validation.arguments.Arguments1; +import org.xbib.datastructures.validation.arguments.ZonedDateTimeValidator; +import org.xbib.datastructures.validation.constraint.ZonedDateTimeConstraint; +import org.xbib.datastructures.validation.core.Validator; + +/** + * @since 0.10.0 + */ +public class ZonedDateTimeValidatorBuilder { + private final String name; + + private final Function>, ZonedDateTimeConstraint>> constraints; + + public static ZonedDateTimeValidatorBuilder of(String name, + Function>, ZonedDateTimeConstraint>> constraints) { + return new ZonedDateTimeValidatorBuilder(name, constraints); + } + + ZonedDateTimeValidatorBuilder(String name, + Function>, ZonedDateTimeConstraint>> constraints) { + this.name = name; + this.constraints = constraints; + } + + public ZonedDateTimeValidator build( + Function mapper) { + final Validator> validator = ValidatorBuilder + .> of() + .constraint(Arguments1::arg1, name, constraints).build(); + return new ZonedDateTimeValidator<>(validator, mapper::apply); + } + + public ZonedDateTimeValidator build() { + return build(x -> x); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BigDecimalConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BigDecimalConstraint.java new file mode 100644 index 0000000..8a18fe0 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BigDecimalConstraint.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.math.BigDecimal; +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class BigDecimalConstraint + extends NumericConstraintBase> { + @Override + public BigDecimalConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(BigDecimal min) { + return x -> x.compareTo(min) > 0; + } + + @Override + protected Predicate isGreaterThanOrEqual(BigDecimal min) { + return x -> x.compareTo(min) >= 0; + } + + @Override + protected Predicate isLessThan(BigDecimal max) { + return x -> x.compareTo(max) < 0; + } + + @Override + protected Predicate isLessThanOrEqual(BigDecimal max) { + return x -> x.compareTo(max) <= 0; + } + + @Override + protected BigDecimal zeroValue() { + return BigDecimal.ZERO; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BigIntegerConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BigIntegerConstraint.java new file mode 100644 index 0000000..839b7ec --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BigIntegerConstraint.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.math.BigInteger; +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class BigIntegerConstraint + extends NumericConstraintBase> { + @Override + public BigIntegerConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(BigInteger min) { + return x -> x.compareTo(min) > 0; + } + + @Override + protected Predicate isGreaterThanOrEqual(BigInteger min) { + return x -> x.compareTo(min) >= 0; + } + + @Override + protected Predicate isLessThan(BigInteger max) { + return x -> x.compareTo(max) < 0; + } + + @Override + protected Predicate isLessThanOrEqual(BigInteger max) { + return x -> x.compareTo(max) <= 0; + } + + @Override + protected BigInteger zeroValue() { + return BigInteger.ZERO; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BooleanConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BooleanConstraint.java new file mode 100644 index 0000000..85cf762 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/BooleanConstraint.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.BOOLEAN_IS_FALSE; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.BOOLEAN_IS_TRUE; + +import org.xbib.datastructures.validation.constraint.base.ConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class BooleanConstraint + extends ConstraintBase> { + + @Override + public BooleanConstraint cast() { + return this; + } + + public BooleanConstraint isFalse() { + this.predicates().add(ConstraintPredicate.of(x -> !x, BOOLEAN_IS_FALSE, + () -> new Object[] {}, VALID)); + return this; + } + + public BooleanConstraint isTrue() { + this.predicates().add(ConstraintPredicate.of(x -> x, BOOLEAN_IS_TRUE, + () -> new Object[] {}, VALID)); + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ByteConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ByteConstraint.java new file mode 100644 index 0000000..8c253f8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ByteConstraint.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class ByteConstraint extends NumericConstraintBase> { + @Override + public ByteConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(Byte min) { + return x -> x > min; + } + + @Override + protected Predicate isGreaterThanOrEqual(Byte min) { + return x -> x >= min; + } + + @Override + protected Predicate isLessThan(Byte max) { + return x -> x < max; + } + + @Override + protected Predicate isLessThanOrEqual(Byte max) { + return x -> x <= max; + } + + @Override + protected Byte zeroValue() { + return 0; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CharSequenceConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CharSequenceConstraint.java new file mode 100644 index 0000000..230426b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CharSequenceConstraint.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.regex.Pattern; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.constraint.charsequence.ByteSizeConstraint; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.CodePointsRanges; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.CodePointsSet; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.Range; +import org.xbib.datastructures.validation.constraint.charsequence.CodePointsConstraint; +import org.xbib.datastructures.validation.constraint.charsequence.EmojiConstraint; +import org.xbib.datastructures.validation.constraint.charsequence.variant.VariantOptions; +import org.xbib.datastructures.validation.constraint.inetaddress.InetAddressUtils; +import org.xbib.datastructures.validation.constraint.password.CharSequencePasswordPoliciesBuilder; +import org.xbib.datastructures.validation.core.ConstraintPredicate; +import org.xbib.datastructures.validation.core.ViolationMessage; + +import static org.xbib.datastructures.validation.core.NullAs.INVALID; +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.*; + +public class CharSequenceConstraint + extends ContainerConstraintBase> { + private static final String EMAIL_PART = "[^\\x00-\\x1F()<>@,;:\\\\\".\\[\\]\\s]"; + + private static final String DOMAIN_PATTERN = EMAIL_PART + "+(\\." + EMAIL_PART + + "+)*"; + + private static final Pattern VALID_EMAIL_ADDRESS_REGEX = Pattern + .compile( + "^" + EMAIL_PART + "+(\\." + EMAIL_PART + "+)*@(" + DOMAIN_PATTERN + + "|" + InetAddressUtils.IPV4_REGEX + ")$", + Pattern.CASE_INSENSITIVE); + + private static final Pattern VALID_UUID_REGEX = Pattern + .compile("\\p{XDigit}{8}(-\\p{XDigit}{4}){4}\\p{XDigit}{8}"); + + protected final Normalizer.Form normalizerForm; + + protected final VariantOptions variantOptions; + + public CharSequenceConstraint() { + this(Normalizer.Form.NFC, VariantOptions.builder().build()); + } + + public CharSequenceConstraint(Normalizer.Form normalizerForm, + VariantOptions variantOptions) { + this.normalizerForm = normalizerForm; + this.variantOptions = variantOptions; + } + + public ByteSizeConstraint asByteArray(Charset charset) { + return new ByteSizeConstraint<>(this, charset); + } + + public ByteSizeConstraint asByteArray() { + return this.asByteArray(StandardCharsets.UTF_8); + } + + @Override + public CharSequenceConstraint cast() { + return this; + } + + public CodePointsConstraint.Builder codePoints(CodePoints codePoints) { + return new CodePointsConstraint.Builder<>(this, codePoints); + } + + public CodePointsConstraint.Builder codePoints(Set allowedCodePoints) { + return this.codePoints((CodePointsSet) () -> allowedCodePoints); + } + + public CodePointsConstraint.Builder codePoints(int begin, int end) { + return this.codePoints(Range.of(begin, end)); + } + + public CodePointsConstraint.Builder codePoints(Range range, Range... ranges) { + return this.codePoints((CodePointsRanges) () -> { + List list = new ArrayList<>(); + list.add(range); + list.addAll(Arrays.asList(ranges)); + return list; + }); + } + + public CharSequenceConstraint contains(CharSequence s) { + this.predicates().add(ConstraintPredicate.of(x -> x.toString().contains(s), + CHAR_SEQUENCE_CONTAINS, () -> new Object[] { s }, VALID)); + return this; + } + + /** + * Does the given value start with the {@code prefix} + * @param prefix the prefix the value has to start with + * @since 0.10.0 + */ + public CharSequenceConstraint startsWith(CharSequence prefix) { + this.predicates() + .add(ConstraintPredicate.of( + x -> x.toString().startsWith(prefix.toString()), + CHAR_SEQUENCE_STARTSWITH, () -> new Object[] { prefix }, VALID)); + return this; + } + + /** + * Does the given value end with the {@code suffix} + * @param suffix the suffix the value has to end with + * @since 0.10.0 + */ + public CharSequenceConstraint endsWith(CharSequence suffix) { + this.predicates() + .add(ConstraintPredicate.of(x -> x.toString().endsWith(suffix.toString()), + CHAR_SEQUENCE_ENDSWITH, () -> new Object[] { suffix }, VALID)); + return this; + } + + public CharSequenceConstraint email() { + this.predicates().add(ConstraintPredicate.of(x -> { + if (size().applyAsInt(x) == 0) { + return true; + } + return VALID_EMAIL_ADDRESS_REGEX.matcher(x).matches(); + }, CHAR_SEQUENCE_EMAIL, () -> new Object[] {}, VALID)); + return this; + } + + /** + * @since 0.7.0 + */ + public CharSequenceConstraint password( + Function, List>> builder) { + final List> predicates = builder + .apply(new CharSequencePasswordPoliciesBuilder<>()); + this.predicates().addAll(predicates); + return this; + } + + private CharSequenceConstraint isValidRepresentationOf( + Function converter, ViolationMessage message) { + this.predicates().add(ConstraintPredicate.of(x -> { + if (size().applyAsInt(x) == 0) { + return true; + } + try { + converter.apply(x.toString()); + return true; + } + catch (NumberFormatException ignored) { + return false; + } + }, message, () -> new Object[] {}, VALID)); + return this; + } + + /** + * @since 0.6.0 + */ + public CharSequenceConstraint isByte() { + return this.isValidRepresentationOf(Byte::parseByte, CHAR_SEQUENCE_BYTE); + } + + /** + * @since 0.6.0 + */ + public CharSequenceConstraint isShort() { + return this.isValidRepresentationOf(Short::parseShort, CHAR_SEQUENCE_SHORT); + } + + /** + * @since 0.6.0 + */ + public CharSequenceConstraint isInteger() { + return this.isValidRepresentationOf(Integer::parseInt, CHAR_SEQUENCE_INTEGER); + } + + /** + * @since 0.6.0 + */ + public CharSequenceConstraint isLong() { + return this.isValidRepresentationOf(Long::parseLong, CHAR_SEQUENCE_LONG); + } + + /** + * @since 0.6.0 + */ + public CharSequenceConstraint isFloat() { + return this.isValidRepresentationOf(Float::parseFloat, CHAR_SEQUENCE_FLOAT); + } + + /** + * @since 0.6.0 + */ + public CharSequenceConstraint isDouble() { + return this.isValidRepresentationOf(Double::parseDouble, CHAR_SEQUENCE_DOUBLE); + } + + /** + * @since 0.6.0 + */ + public CharSequenceConstraint isBigInteger() { + return this.isValidRepresentationOf(BigInteger::new, CHAR_SEQUENCE_BIGINTEGER); + } + + /** + * @since 0.6.0 + */ + public CharSequenceConstraint isBigDecimal() { + return this.isValidRepresentationOf(BigDecimal::new, CHAR_SEQUENCE_BIGDECIMAL); + } + + public EmojiConstraint emoji() { + return new EmojiConstraint<>(this, this.normalizerForm, this.variantOptions); + } + + public CharSequenceConstraint normalizer(Normalizer.Form normalizerForm) { + CharSequenceConstraint constraint = new CharSequenceConstraint<>( + normalizerForm, this.variantOptions); + constraint.predicates().addAll(this.predicates()); + return constraint; + } + + public CharSequenceConstraint notBlank() { + this.predicates() + .add(ConstraintPredicate.of( + x -> x != null && trim(x.toString()).length() != 0, + CHAR_SEQUENCE_NOT_BLANK, () -> new Object[] {}, INVALID)); + return this; + } + + public CharSequenceConstraint pattern(String regex) { + this.predicates().add(ConstraintPredicate.of(x -> Pattern.matches(regex, x), + CHAR_SEQUENCE_PATTERN, () -> new Object[] { regex }, VALID)); + return this; + } + + /** + * @since 0.7.0 + */ + public CharSequenceConstraint ipv4() { + this.predicates() + .add(ConstraintPredicate.of(x -> InetAddressUtils.isIpv4(x.toString()), + CHAR_SEQUENCE_IPV4, () -> new Object[] {}, VALID)); + return this; + } + + /** + * @since 0.7.0 + */ + public CharSequenceConstraint ipv6() { + this.predicates() + .add(ConstraintPredicate.of(x -> InetAddressUtils.isIpv6(x.toString()), + CHAR_SEQUENCE_IPV6, () -> new Object[] {}, VALID)); + return this; + } + + public CharSequenceConstraint url() { + this.predicates().add(ConstraintPredicate.of(x -> { + if (size().applyAsInt(x) == 0) { + return true; + } + try { + new URL(x.toString()); + return true; + } + catch (MalformedURLException e) { + return false; + } + }, CHAR_SEQUENCE_URL, () -> new Object[] {}, VALID)); + return this; + } + + /** + * @since 0.10.0 + */ + public CharSequenceConstraint uuid() { + this.predicates().add(ConstraintPredicate.of(x -> { + if (size().applyAsInt(x) == 0) { + return true; + } + return VALID_UUID_REGEX.matcher(x).matches(); + }, CHAR_SEQUENCE_UUID, () -> new Object[] {}, VALID)); + return this; + } + + /** + * @since 0.7.0 + */ + public CharSequenceConstraint luhn() { + this.predicates().add(ConstraintPredicate.of(CharSequenceConstraint::luhnCheck, + CHAR_SEQUENCE_LUHN, () -> new Object[] {}, VALID)); + return this; + } + + // https://github.com/apache/commons-validator/blob/master/src/main/java/org/apache/commons/validator/CreditCardValidator.java + static boolean luhnCheck(CharSequence cardNumber) { + // number must be validated as 0..9 numeric first!! + final int digits = cardNumber.length(); + final int oddOrEven = digits & 1; + long sum = 0; + for (int count = 0; count < digits; count++) { + int digit; + try { + digit = Integer.parseInt(cardNumber.charAt(count) + ""); + } + catch (NumberFormatException e) { + return false; + } + if (((count & 1) ^ oddOrEven) == 0) { // not + digit *= 2; + if (digit > 9) { + digit -= 9; + } + } + sum += digit; + } + return sum != 0 && (sum % 10 == 0); + } + + public CharSequenceConstraint variant( + Function opts) { + VariantOptions.Builder builder = VariantOptions.builder(); + CharSequenceConstraint constraint = new CharSequenceConstraint<>( + this.normalizerForm, opts.apply(builder).build()); + constraint.predicates().addAll(this.predicates()); + return constraint; + } + + protected String normalize(String s) { + String str = this.variantOptions.ignored(s); + return this.normalizerForm == null ? str + : Normalizer.normalize(str, this.normalizerForm); + } + + @Override + protected ToIntFunction size() { + return cs -> { + String s = this.normalize(cs.toString()); + return s.codePointCount(0, s.length()); + }; + } + + private static String trim(String s) { + if (s.length() == 0) { + return s; + } + StringBuilder sb = new StringBuilder(s); + while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) { + sb.deleteCharAt(0); + } + while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CharacterConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CharacterConstraint.java new file mode 100644 index 0000000..2518284 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CharacterConstraint.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class CharacterConstraint + extends NumericConstraintBase> { + @Override + public CharacterConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(Character min) { + return x -> x > min; + } + + @Override + protected Predicate isGreaterThanOrEqual(Character min) { + return x -> x >= min; + } + + @Override + protected Predicate isLessThan(Character max) { + return x -> x < max; + } + + @Override + protected Predicate isLessThanOrEqual(Character max) { + return x -> x <= max; + } + + @Override + protected Character zeroValue() { + return Character.MIN_VALUE; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CollectionConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CollectionConstraint.java new file mode 100644 index 0000000..31faf64 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/CollectionConstraint.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.ToIntFunction; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; +import org.xbib.datastructures.validation.core.ViolatedValue; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.COLLECTION_CONTAINS; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.COLLECTION_UNIQUE; + +public class CollectionConstraint, E> + extends ContainerConstraintBase> { + + @Override + public CollectionConstraint cast() { + return this; + } + + public CollectionConstraint contains(E s) { + this.predicates().add(ConstraintPredicate.of(x -> x.contains(s), + COLLECTION_CONTAINS, () -> new Object[] { s }, VALID)); + return this; + } + + /** + * @since 0.8.3 + */ + public CollectionConstraint unique() { + this.predicates().add(ConstraintPredicate.withViolatedValue(collection -> { + final List duplicates = new ArrayList<>(); + final Set uniqElements = new LinkedHashSet<>(collection.size()); + for (E element : collection) { + if (uniqElements.contains(element)) { + duplicates.add(element); + } + else { + uniqElements.add(element); + } + } + if (duplicates.isEmpty()) { + return Optional.empty(); + } + else { + return Optional.of(new ViolatedValue(duplicates)); + } + }, COLLECTION_UNIQUE, () -> new Object[] {}, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return Collection::size; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/DoubleConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/DoubleConstraint.java new file mode 100644 index 0000000..b4a8239 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/DoubleConstraint.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class DoubleConstraint + extends NumericConstraintBase> { + @Override + public DoubleConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(Double min) { + return x -> x > min; + } + + @Override + protected Predicate isGreaterThanOrEqual(Double min) { + return x -> x >= min; + } + + @Override + protected Predicate isLessThan(Double max) { + return x -> x < max; + } + + @Override + protected Predicate isLessThanOrEqual(Double max) { + return x -> x <= max; + } + + @Override + protected Double zeroValue() { + return 0.0; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/FloatConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/FloatConstraint.java new file mode 100644 index 0000000..e7fd89d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/FloatConstraint.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class FloatConstraint + extends NumericConstraintBase> { + @Override + public FloatConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(Float min) { + return x -> x > min; + } + + @Override + protected Predicate isGreaterThanOrEqual(Float min) { + return x -> x >= min; + } + + @Override + protected Predicate isLessThan(Float max) { + return x -> x < max; + } + + @Override + protected Predicate isLessThanOrEqual(Float max) { + return x -> x <= max; + } + + @Override + protected Float zeroValue() { + return 0f; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/InstantConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/InstantConstraint.java new file mode 100644 index 0000000..3c6d0e2 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/InstantConstraint.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; + +import org.xbib.datastructures.validation.constraint.base.TemporalConstraintBase; + +/** + * This is the actual class for constraints on Instant. + * @since 0.10.0 + */ +public class InstantConstraint + extends TemporalConstraintBase> { + @Override + protected boolean isAfter(Instant a, Instant b) { + return a.isAfter(b); + } + + @Override + protected boolean isBefore(Instant a, Instant b) { + return a.isBefore(b); + } + + @Override + protected Instant getNow(Clock clock) { + return Instant.now(clock); + } + + @Override + public InstantConstraint cast() { + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/IntegerConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/IntegerConstraint.java new file mode 100644 index 0000000..3986fcf --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/IntegerConstraint.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class IntegerConstraint + extends NumericConstraintBase> { + @Override + public IntegerConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(Integer min) { + return x -> x > min; + } + + @Override + protected Predicate isGreaterThanOrEqual(Integer min) { + return x -> x >= min; + } + + @Override + protected Predicate isLessThan(Integer max) { + return x -> x < max; + } + + @Override + protected Predicate isLessThanOrEqual(Integer max) { + return x -> x <= max; + } + + @Override + protected Integer zeroValue() { + return 0; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalDateConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalDateConstraint.java new file mode 100644 index 0000000..e03e23b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalDateConstraint.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.LocalDate; + +import org.xbib.datastructures.validation.constraint.base.ChronoLocalDateConstraintBase; + +/** + * This is the actual class for constraints on LocalDate. + * + * @author Diego Krupitza + * @since 0.10.0 + */ +public class LocalDateConstraint + extends ChronoLocalDateConstraintBase> { + + @Override + public LocalDateConstraint cast() { + return this; + } + + @Override + protected LocalDate getNow(Clock clock) { + return LocalDate.now(clock); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalDateTimeConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalDateTimeConstraint.java new file mode 100644 index 0000000..8bd6f0a --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalDateTimeConstraint.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.LocalDateTime; + +import org.xbib.datastructures.validation.constraint.base.ChronoLocalDateTimeConstraintBase; + +/** + * This is the actual class for constraints on LocalDateTime. + * + * @author Diego Krupitza + * @since 0.10.0 + */ +public class LocalDateTimeConstraint extends + ChronoLocalDateTimeConstraintBase> { + + @Override + public LocalDateTimeConstraint cast() { + return this; + } + + @Override + protected LocalDateTime getNow(Clock clock) { + return LocalDateTime.now(clock); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalTimeConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalTimeConstraint.java new file mode 100644 index 0000000..4564f1f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LocalTimeConstraint.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.LocalTime; + +import org.xbib.datastructures.validation.constraint.base.TemporalConstraintBase; + +/** + * This is the actual class for constraints on LocalTime. + * + * @author Diego Krupitza + * @since 0.10.0 + */ +public class LocalTimeConstraint + extends TemporalConstraintBase> { + @Override + protected boolean isAfter(LocalTime a, LocalTime b) { + return a.isAfter(b); + } + + @Override + protected boolean isBefore(LocalTime a, LocalTime b) { + return a.isBefore(b); + } + + @Override + protected LocalTime getNow(Clock clock) { + return LocalTime.now(clock); + } + + @Override + public LocalTimeConstraint cast() { + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LongConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LongConstraint.java new file mode 100644 index 0000000..4e8f281 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/LongConstraint.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class LongConstraint extends NumericConstraintBase> { + @Override + public LongConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(Long min) { + return x -> x > min; + } + + @Override + protected Predicate isGreaterThanOrEqual(Long min) { + return x -> x >= min; + } + + @Override + protected Predicate isLessThan(Long max) { + return x -> x < max; + } + + @Override + protected Predicate isLessThanOrEqual(Long max) { + return x -> x <= max; + } + + @Override + protected Long zeroValue() { + return 0L; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/MapConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/MapConstraint.java new file mode 100644 index 0000000..bc50bfa --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/MapConstraint.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.Map; +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.MAP_CONTAINS_KEY; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.MAP_CONTAINS_VALUE; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class MapConstraint + extends ContainerConstraintBase, MapConstraint> { + + @Override + public MapConstraint cast() { + return this; + } + + public MapConstraint containsKey(K k) { + this.predicates().add(ConstraintPredicate.of(x -> x.containsKey(k), + MAP_CONTAINS_KEY, () -> new Object[] { k }, VALID)); + return this; + } + + public MapConstraint containsValue(V v) { + this.predicates().add(ConstraintPredicate.of(x -> x.containsValue(v), + MAP_CONTAINS_VALUE, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction> size() { + return Map::size; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ObjectConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ObjectConstraint.java new file mode 100644 index 0000000..61292b0 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ObjectConstraint.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.List; +import java.util.function.Function; + +import org.xbib.datastructures.validation.constraint.base.ConstraintBase; +import org.xbib.datastructures.validation.constraint.password.ObjectPasswordPoliciesBuilder; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class ObjectConstraint extends ConstraintBase> { + + @Override + public ObjectConstraint cast() { + return this; + } + + /** + * @since 0.7.0 + */ + public ObjectConstraint password( + Function, List>> builder) { + final List> predicates = builder + .apply(new ObjectPasswordPoliciesBuilder<>()); + this.predicates().addAll(predicates); + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/OffsetDateTimeConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/OffsetDateTimeConstraint.java new file mode 100644 index 0000000..a53838e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/OffsetDateTimeConstraint.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.OffsetDateTime; + +import org.xbib.datastructures.validation.constraint.base.TemporalConstraintBase; + +/** + * This is the actual class for constraints on OffsetDateTime. + * @since 0.10.0 + */ +public class OffsetDateTimeConstraint + extends TemporalConstraintBase> { + @Override + protected boolean isAfter(OffsetDateTime a, OffsetDateTime b) { + return a.isAfter(b); + } + + @Override + protected boolean isBefore(OffsetDateTime a, OffsetDateTime b) { + return a.isBefore(b); + } + + @Override + protected OffsetDateTime getNow(Clock clock) { + return OffsetDateTime.now(clock); + } + + @Override + public OffsetDateTimeConstraint cast() { + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ShortConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ShortConstraint.java new file mode 100644 index 0000000..28f881b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ShortConstraint.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; + +public class ShortConstraint + extends NumericConstraintBase> { + @Override + public ShortConstraint cast() { + return this; + } + + @Override + protected Predicate isGreaterThan(Short min) { + return x -> x > min; + } + + @Override + protected Predicate isGreaterThanOrEqual(Short min) { + return x -> x >= min; + } + + @Override + protected Predicate isLessThan(Short max) { + return x -> x < max; + } + + @Override + protected Predicate isLessThanOrEqual(Short max) { + return x -> x <= max; + } + + @Override + protected Short zeroValue() { + return 0; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/YearConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/YearConstraint.java new file mode 100644 index 0000000..f7530b9 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/YearConstraint.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Year; + +import org.xbib.datastructures.validation.constraint.base.TemporalConstraintBase; + +/** + * This is the actual class for constraints on Year. + * + * @since 0.11.0 + */ +public class YearConstraint + extends TemporalConstraintBase> { + @Override + protected boolean isAfter(Year a, Year b) { + return a.isAfter(b); + } + + @Override + protected boolean isBefore(Year a, Year b) { + return a.isBefore(b); + } + + @Override + protected Year getNow(Clock clock) { + return Year.now(clock); + } + + @Override + public YearConstraint cast() { + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/YearMonthConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/YearMonthConstraint.java new file mode 100644 index 0000000..2ee75bc --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/YearMonthConstraint.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.YearMonth; + +import org.xbib.datastructures.validation.constraint.base.TemporalConstraintBase; + +/** + * This is the actual class for constraints on YearMonth. + * + * @since 0.11.0 + */ +public class YearMonthConstraint + extends TemporalConstraintBase> { + @Override + protected boolean isAfter(YearMonth a, YearMonth b) { + return a.isAfter(b); + } + + @Override + protected boolean isBefore(YearMonth a, YearMonth b) { + return a.isBefore(b); + } + + @Override + protected YearMonth getNow(Clock clock) { + return YearMonth.now(clock); + } + + @Override + public YearMonthConstraint cast() { + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ZonedDateTimeConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ZonedDateTimeConstraint.java new file mode 100644 index 0000000..e45c247 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/ZonedDateTimeConstraint.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.ZonedDateTime; + +import org.xbib.datastructures.validation.constraint.base.ChronoZonedDateTimeConstraintBase; + +/** + * This is the actual class for constraints on ZonedDateTime. + * + * @author Diego Krupitza + * @since 0.10.0 + */ +public class ZonedDateTimeConstraint extends + ChronoZonedDateTimeConstraintBase> { + + @Override + protected ZonedDateTime getNow(Clock clock) { + return ZonedDateTime.now(clock); + } + + @Override + public ZonedDateTimeConstraint cast() { + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/BooleanArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/BooleanArrayConstraint.java new file mode 100644 index 0000000..5fe5c0f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/BooleanArrayConstraint.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class BooleanArrayConstraint + extends ContainerConstraintBase> { + + @Override + public BooleanArrayConstraint cast() { + return this; + } + + public BooleanArrayConstraint contains(boolean v) { + this.predicates().add(ConstraintPredicate.of(x -> { + for (boolean e : x) { + if (e == v) { + return true; + } + } + return false; + }, ARRAY_CONTAINS, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ByteArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ByteArrayConstraint.java new file mode 100644 index 0000000..dcc022f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ByteArrayConstraint.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class ByteArrayConstraint + extends ContainerConstraintBase> { + + @Override + public ByteArrayConstraint cast() { + return this; + } + + public ByteArrayConstraint contains(byte v) { + this.predicates().add(ConstraintPredicate.of(x -> { + for (byte e : x) { + if (e == v) { + return true; + } + } + return false; + }, ARRAY_CONTAINS, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/CharArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/CharArrayConstraint.java new file mode 100644 index 0000000..6f2b342 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/CharArrayConstraint.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class CharArrayConstraint + extends ContainerConstraintBase> { + + @Override + public CharArrayConstraint cast() { + return this; + } + + public CharArrayConstraint contains(char v) { + this.predicates().add(ConstraintPredicate.of(x -> { + for (char e : x) { + if (e == v) { + return true; + } + } + return false; + }, ARRAY_CONTAINS, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/DoubleArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/DoubleArrayConstraint.java new file mode 100644 index 0000000..8d26c4b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/DoubleArrayConstraint.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.Arrays; +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class DoubleArrayConstraint + extends ContainerConstraintBase> { + + @Override + public DoubleArrayConstraint cast() { + return this; + } + + public DoubleArrayConstraint contains(double v) { + this.predicates() + .add(ConstraintPredicate.of(x -> Arrays.stream(x).anyMatch(e -> e == v), + ARRAY_CONTAINS, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/FloatArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/FloatArrayConstraint.java new file mode 100644 index 0000000..0fa4371 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/FloatArrayConstraint.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class FloatArrayConstraint + extends ContainerConstraintBase> { + + @Override + public FloatArrayConstraint cast() { + return this; + } + + public FloatArrayConstraint contains(float v) { + this.predicates().add(ConstraintPredicate.of(x -> { + for (float e : x) { + if (e == v) { + return true; + } + } + return false; + }, ARRAY_CONTAINS, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/IntArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/IntArrayConstraint.java new file mode 100644 index 0000000..b8011b8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/IntArrayConstraint.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.Arrays; +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class IntArrayConstraint + extends ContainerConstraintBase> { + + @Override + public IntArrayConstraint cast() { + return this; + } + + public IntArrayConstraint contains(int v) { + this.predicates() + .add(ConstraintPredicate.of(x -> Arrays.stream(x).anyMatch(e -> e == v), + ARRAY_CONTAINS, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/LongArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/LongArrayConstraint.java new file mode 100644 index 0000000..dd836d8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/LongArrayConstraint.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.Arrays; +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class LongArrayConstraint + extends ContainerConstraintBase> { + + @Override + public LongArrayConstraint cast() { + return this; + } + + public LongArrayConstraint contains(long v) { + this.predicates() + .add(ConstraintPredicate.of(x -> Arrays.stream(x).anyMatch(e -> e == v), + ARRAY_CONTAINS, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ObjectArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ObjectArrayConstraint.java new file mode 100644 index 0000000..49690f4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ObjectArrayConstraint.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.Arrays; +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class ObjectArrayConstraint + extends ContainerConstraintBase> { + + @Override + public ObjectArrayConstraint cast() { + return this; + } + + public ObjectArrayConstraint contains(E s) { + this.predicates().add(ConstraintPredicate.of(x -> Arrays.asList(x).contains(s), + ARRAY_CONTAINS, () -> new Object[] { s }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ShortArrayConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ShortArrayConstraint.java new file mode 100644 index 0000000..12926ff --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/ShortArrayConstraint.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.ARRAY_CONTAINS; + +import org.xbib.datastructures.validation.constraint.base.ContainerConstraintBase; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class ShortArrayConstraint + extends ContainerConstraintBase> { + + @Override + public ShortArrayConstraint cast() { + return this; + } + + public ShortArrayConstraint contains(short v) { + this.predicates().add(ConstraintPredicate.of(x -> { + for (short e : x) { + if (e == v) { + return true; + } + } + return false; + }, ARRAY_CONTAINS, () -> new Object[] { v }, VALID)); + return this; + } + + @Override + protected ToIntFunction size() { + return x -> x.length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/package-info.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/package-info.java new file mode 100644 index 0000000..b706cdc --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/array/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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. + */ +@NonNullApi +package org.xbib.datastructures.validation.constraint.array; + +import org.xbib.datastructures.validation.jsr305.NonNullApi; diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoLocalDateConstraintBase.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoLocalDateConstraintBase.java new file mode 100644 index 0000000..37a8d7b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoLocalDateConstraintBase.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.base; + +import java.time.chrono.ChronoLocalDate; + +import org.xbib.datastructures.validation.core.Constraint; + +/** + * This is the base class for constraints on ChronoLocalDate. + * + * @author Diego Krupitza + * @since 0.10.0 + */ +public abstract class ChronoLocalDateConstraintBase> + extends TemporalConstraintBase { + @Override + protected boolean isAfter(V a, V b) { + return a.isAfter(b); + } + + @Override + protected boolean isBefore(V a, V b) { + return a.isBefore(b); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoLocalDateTimeConstraintBase.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoLocalDateTimeConstraintBase.java new file mode 100644 index 0000000..4ceb651 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoLocalDateTimeConstraintBase.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.base; + +import java.time.chrono.ChronoLocalDateTime; + +import org.xbib.datastructures.validation.core.Constraint; + +/** + * This is the base class for constraints on ChronoLocalDateTime. + * + * @author Diego Krupitza + * @since 0.10.0 + */ +public abstract class ChronoLocalDateTimeConstraintBase, C extends Constraint> + extends TemporalConstraintBase { + @Override + protected boolean isAfter(V a, V b) { + return a.isAfter(b); + } + + @Override + protected boolean isBefore(V a, V b) { + return a.isBefore(b); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoZonedDateTimeConstraintBase.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoZonedDateTimeConstraintBase.java new file mode 100644 index 0000000..dc5229c --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ChronoZonedDateTimeConstraintBase.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.base; + +import java.time.chrono.ChronoZonedDateTime; + +import org.xbib.datastructures.validation.core.Constraint; + +/** + * This is the base class for constraints on ChronoZonedDateTime. + * + * @author Diego Krupitza + * @since 0.10.0 + */ +public abstract class ChronoZonedDateTimeConstraintBase, C extends Constraint> + extends TemporalConstraintBase { + @Override + protected boolean isAfter(V a, V b) { + return a.isAfter(b); + } + + @Override + protected boolean isBefore(V a, V b) { + return a.isBefore(b); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ConstraintBase.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ConstraintBase.java new file mode 100644 index 0000000..5a68ddd --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ConstraintBase.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.base; + +import java.util.Deque; +import java.util.LinkedList; + +import org.xbib.datastructures.validation.core.Constraint; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public abstract class ConstraintBase> + implements Constraint { + private final Deque> predicates = new LinkedList<>(); + + @Override + public Deque> predicates() { + return this.predicates; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ContainerConstraintBase.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ContainerConstraintBase.java new file mode 100644 index 0000000..70fcf10 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/ContainerConstraintBase.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.base; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.ToIntFunction; + +import static org.xbib.datastructures.validation.core.NullAs.INVALID; +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_FIXED_SIZE; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_GREATER_THAN; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_GREATER_THAN_OR_EQUAL; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_LESS_THAN; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_LESS_THAN_OR_EQUAL; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_NOT_EMPTY; + +import org.xbib.datastructures.validation.core.Constraint; +import org.xbib.datastructures.validation.core.ConstraintPredicate; +import org.xbib.datastructures.validation.core.ViolatedValue; + +public abstract class ContainerConstraintBase> + extends ConstraintBase { + + public C fixedSize(int size) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size().applyAsInt(x) == size, + this.size()), + CONTAINER_FIXED_SIZE, () -> new Object[] { size }, VALID)); + return cast(); + } + + public C greaterThan(int min) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size().applyAsInt(x) > min, + this.size()), + CONTAINER_GREATER_THAN, () -> new Object[] { min }, VALID)); + return cast(); + } + + public C greaterThanOrEqual(int min) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size().applyAsInt(x) >= min, + this.size()), + CONTAINER_GREATER_THAN_OR_EQUAL, () -> new Object[] { min }, + VALID)); + return cast(); + } + + public C lessThan(int max) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size().applyAsInt(x) < max, + this.size()), + CONTAINER_LESS_THAN, () -> new Object[] { max }, VALID)); + return cast(); + } + + public C lessThanOrEqual(int max) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size().applyAsInt(x) <= max, + this.size()), + CONTAINER_LESS_THAN_OR_EQUAL, () -> new Object[] { max }, VALID)); + return cast(); + } + + public C notEmpty() { + this.predicates() + .add(ConstraintPredicate.of(x -> x != null && size().applyAsInt(x) != 0, + CONTAINER_NOT_EMPTY, () -> new Object[] {}, INVALID)); + return cast(); + } + + protected Function> checkSizePredicate( + Predicate predicate, ToIntFunction size) { + return v -> { + if (predicate.test(v)) { + return Optional.empty(); + } + int s = size.applyAsInt(v); + return Optional.of(new ViolatedValue(s)); + }; + } + + protected abstract ToIntFunction size(); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/NumericConstraintBase.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/NumericConstraintBase.java new file mode 100644 index 0000000..148d540 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/NumericConstraintBase.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.base; + +import java.util.function.Predicate; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.*; + +import org.xbib.datastructures.validation.core.Constraint; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public abstract class NumericConstraintBase> + extends ConstraintBase { + + public C greaterThan(V min) { + this.predicates().add(ConstraintPredicate.of(this.isGreaterThan(min), + NUMERIC_GREATER_THAN, () -> new Object[] { min }, VALID)); + return cast(); + } + + public C greaterThanOrEqual(V min) { + this.predicates().add(ConstraintPredicate.of(this.isGreaterThanOrEqual(min), + NUMERIC_GREATER_THAN_OR_EQUAL, () -> new Object[] { min }, VALID)); + return cast(); + } + + public C lessThan(V max) { + this.predicates().add(ConstraintPredicate.of(this.isLessThan(max), + NUMERIC_LESS_THAN, () -> new Object[] { max }, VALID)); + return cast(); + } + + public C lessThanOrEqual(V max) { + this.predicates().add(ConstraintPredicate.of(this.isLessThanOrEqual(max), + NUMERIC_LESS_THAN_OR_EQUAL, () -> new Object[] { max }, VALID)); + return cast(); + } + + /** + * @since 0.10.0 + */ + public C positive() { + this.predicates().add(ConstraintPredicate.of(this.isGreaterThan(zeroValue()), + NUMERIC_POSITIVE, () -> new Object[] {}, VALID)); + return cast(); + } + + /** + * @since 0.10.0 + */ + public C positiveOrZero() { + this.predicates() + .add(ConstraintPredicate.of(this.isGreaterThanOrEqual(zeroValue()), + NUMERIC_POSITIVE_OR_ZERO, () -> new Object[] {}, VALID)); + return cast(); + } + + /** + * @since 0.10.0 + */ + public C negative() { + this.predicates().add(ConstraintPredicate.of(this.isLessThan(zeroValue()), + NUMERIC_NEGATIVE, () -> new Object[] {}, VALID)); + return cast(); + } + + /** + * @since 0.10.0 + */ + public C negaitveOrZero() { + this.predicates().add(ConstraintPredicate.of(this.isLessThanOrEqual(zeroValue()), + NUMERIC_NEGATIVE_OR_ZERO, () -> new Object[] {}, VALID)); + return cast(); + } + + protected abstract Predicate isGreaterThan(V min); + + protected abstract Predicate isGreaterThanOrEqual(V min); + + protected abstract Predicate isLessThan(V max); + + protected abstract Predicate isLessThanOrEqual(V max); + + protected abstract V zeroValue(); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/TemporalConstraintBase.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/TemporalConstraintBase.java new file mode 100644 index 0000000..8b7a7f8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/TemporalConstraintBase.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.base; + +import java.time.Clock; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.LongPredicate; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.core.Constraint; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_AFTER; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_AFTER_OR_EQUAL; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_BEFORE; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_BEFORE_OR_EQUAL; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_BETWEEN; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_FIELD; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_FUTURE; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_FUTURE_OR_PRESENT; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_PAST; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.TEMPORAL_PAST_OR_PRESENT; + +/** + * This is the base class for constraints on Temporal classes. Methods in the class + * require the {@link V} to extend Temporal. + * + * @author Diego Krupitza + * @author Toshiaki Maki + * @since 0.10.0 + */ +public abstract class TemporalConstraintBase> + extends ConstraintBase { + abstract protected boolean isAfter(V a, V b); + + abstract protected boolean isBefore(V a, V b); + + abstract protected V getNow(Clock clock); + + public C past() { + return this.past(Clock.systemDefaultZone()); + } + + public C past(Clock clock) { + return this.before(() -> this.getNow(clock)).message(TEMPORAL_PAST); + } + + public C pastOrPresent() { + return this.pastOrPresent(Clock.systemDefaultZone()); + } + + public C pastOrPresent(Clock clock) { + return this.beforeOrEqual(() -> this.getNow(clock)) + .message(TEMPORAL_PAST_OR_PRESENT); + } + + public C future() { + return this.future(Clock.systemDefaultZone()); + } + + public C future(Clock clock) { + return this.after(() -> this.getNow(clock)).message(TEMPORAL_FUTURE); + } + + public C futureOrPresent() { + return this.futureOrPresent(Clock.systemDefaultZone()); + } + + public C futureOrPresent(Clock clock) { + return this.afterOrEqual(() -> this.getNow(clock)) + .message(TEMPORAL_FUTURE_OR_PRESENT); + } + + /** + * Is the given temporal before the supplied {@code other} + * + * @param other the supplier providing the other temporal that is before + */ + public C before(Supplier other) { + final Supplier memoized = memoize(other); + this.predicates() + .add(ConstraintPredicate.of(x -> this.isBefore(x, memoized.get()), + TEMPORAL_BEFORE, () -> new Object[] { memoized.get() }, VALID)); + return cast(); + } + + public C beforeOrEqual(Supplier other) { + final Supplier memoized = memoize(other); + this.predicates() + .add(ConstraintPredicate.of(x -> !this.isAfter(x, memoized.get()), + TEMPORAL_BEFORE_OR_EQUAL, () -> new Object[] { memoized.get() }, + VALID)); + return cast(); + } + + /** + * Is the given temporal after the supplied {@code other} + * + * @param other the supplier providing the other temporal that is before + */ + public C after(Supplier other) { + final Supplier memoized = memoize(other); + this.predicates().add(ConstraintPredicate.of(x -> this.isAfter(x, memoized.get()), + TEMPORAL_AFTER, () -> new Object[] { memoized.get() }, VALID)); + return cast(); + } + + public C afterOrEqual(Supplier other) { + final Supplier memoized = memoize(other); + this.predicates() + .add(ConstraintPredicate.of(x -> !this.isBefore(x, memoized.get()), + TEMPORAL_AFTER_OR_EQUAL, () -> new Object[] { memoized.get() }, + VALID)); + return cast(); + } + + /** + * Is the given temporal between the supplied {@code rangeFrom} and {@code rangeTo}. + * The range is not inclusive. This means if the dates are equal (rangeFrom = x = + * rangeTo) it is invalid + * + * @param rangeFrom the supplier provide the start of the range the temporal has to be + * in + * @param rangeTo the supplier provide the end of the range the temporal has to be in + */ + public C between(Supplier rangeFrom, Supplier rangeTo) { + final Supplier memoizedFrom = memoize(rangeFrom); + final Supplier memoizedTo = memoize(rangeTo); + this.predicates().add(ConstraintPredicate.of(x -> { + final V from = memoizedFrom.get(); + final V to = memoizedTo.get(); + if (this.isAfter(from, to)) { + throw new IllegalArgumentException( + "Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + return this.isBefore(from, x) && this.isAfter(to, x); + }, TEMPORAL_BETWEEN, () -> new Object[] { memoizedFrom.get(), memoizedTo.get() }, + VALID)); + return cast(); + } + + public C fieldPredicate(TemporalField field, LongPredicate predicate) { + this.predicates() + .add(ConstraintPredicate.of(x -> predicate.test(x.getLong(field)), + TEMPORAL_FIELD, () -> new Object[] { field }, VALID)); + return cast(); + } + + static Supplier memoize(Supplier delegate) { + final AtomicReference supplier = new AtomicReference<>(); + return () -> { + final T value = supplier.get(); + if (value == null) { + return supplier + .updateAndGet(prev -> prev == null ? delegate.get() : prev); + } + return value; + }; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/package-info.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/package-info.java new file mode 100644 index 0000000..7d7255f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/base/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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. + */ +@NonNullApi +package org.xbib.datastructures.validation.constraint.base; + +import org.xbib.datastructures.validation.jsr305.NonNullApi; diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/ByteSizeConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/ByteSizeConstraint.java new file mode 100644 index 0000000..f08aa53 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/ByteSizeConstraint.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import java.nio.charset.Charset; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.BYTE_SIZE_FIXED_SIZE; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.BYTE_SIZE_GREATER_THAN; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.BYTE_SIZE_GREATER_THAN_OR_EQUAL; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.BYTE_SIZE_LESS_THAN; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.BYTE_SIZE_LESS_THAN_OR_EQUAL; + +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class ByteSizeConstraint + extends CharSequenceConstraint { + private final Charset charset; + + public ByteSizeConstraint(CharSequenceConstraint delegate, Charset charset) { + super(); + this.charset = charset; + this.predicates().addAll(delegate.predicates()); + } + + @Override + public ByteSizeConstraint cast() { + return this; + } + + @Override + public ByteSizeConstraint fixedSize(int size) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) == size, this::size), + BYTE_SIZE_FIXED_SIZE, () -> new Object[] { size }, VALID)); + return this; + } + + @Override + public ByteSizeConstraint greaterThan(int min) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) > min, this::size), + BYTE_SIZE_GREATER_THAN, () -> new Object[] { min }, VALID)); + return this; + } + + @Override + public ByteSizeConstraint greaterThanOrEqual(int min) { + this.predicates().add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) >= min, this::size), + BYTE_SIZE_GREATER_THAN_OR_EQUAL, () -> new Object[] { min }, VALID)); + return this; + } + + @Override + public ByteSizeConstraint lessThan(int max) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) < max, this::size), + BYTE_SIZE_LESS_THAN, () -> new Object[] { max }, VALID)); + return this; + } + + @Override + public ByteSizeConstraint lessThanOrEqual(int max) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) <= max, this::size), + BYTE_SIZE_LESS_THAN_OR_EQUAL, () -> new Object[] { max }, VALID)); + return this; + } + + private int size(E x) { + return x.toString().getBytes(charset).length; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/CodePoints.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/CodePoints.java new file mode 100644 index 0000000..2c4aaef --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/CodePoints.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +@FunctionalInterface +public interface CodePoints { + + Set allExcludedCodePoints(E s); + + @FunctionalInterface + interface CodePointsRanges extends CodePoints { + @Override + default Set allExcludedCodePoints(@Nullable E s) { + if (s == null || s.length() == 0) { + return Collections.emptySet(); + } + String str = s.toString(); + Set excludedCodePoints = new LinkedHashSet<>(); + int len = str.length(); + Integer codePoint; + List ranges = this.asRanges(); + for (int i = 0; i < len; i += Character.charCount(codePoint)) { + codePoint = str.codePointAt(i); + boolean included = false; + for (Range range : ranges) { + if (range.begin() <= codePoint && codePoint <= range.end()) { + included = true; + break; + } + } + if (!included) { + excludedCodePoints.add(codePoint); + } + } + return excludedCodePoints; + } + + List asRanges(); + + } + + @FunctionalInterface + interface CodePointsSet extends CodePoints { + @Override + default Set allExcludedCodePoints(@Nullable E s) { + if (s == null || s.length() == 0) { + return Collections.emptySet(); + } + String str = s.toString(); + Set excludedCodePoints = new LinkedHashSet<>(); + int len = str.length(); + Integer codePoint; + Set set = this.asSet(); + for (int i = 0; i < len; i += Character.charCount(codePoint)) { + codePoint = str.codePointAt(i); + if (!set.contains(codePoint)) { + excludedCodePoints.add(codePoint); + } + } + return excludedCodePoints; + } + + Set asSet(); + } + + interface Range { + static Range of(String begin, String end) { + return Range.of(begin.codePoints().sorted().findFirst().orElse(0), + end.codePoints().sorted().findFirst().orElse(0)); + } + + static Range of(int begin, int end) { + if (begin > end) { + throw new IllegalArgumentException("begin must not be greater than end [" + + begin + ", " + end + "]"); + } + return new Range() { + @Override + public int begin() { + return begin; + } + + @Override + public int end() { + return end; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Range)) { + return false; + } + Range range = (Range) obj; + return Objects.equals(begin, range.begin()) + && Objects.equals(end, range.end()); + } + + @Override + public int hashCode() { + return Objects.hash(begin, end); + } + }; + } + + static Range single(int value) { + return of(value, value); + } + + int begin(); + + int end(); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/CodePointsConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/CodePointsConstraint.java new file mode 100644 index 0000000..b6894b3 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/CodePointsConstraint.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CODE_POINTS_ALL_INCLUDED; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CODE_POINTS_NOT_INCLUDED; + +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.xbib.datastructures.validation.core.ConstraintPredicate; +import org.xbib.datastructures.validation.core.ViolatedValue; + +public class CodePointsConstraint + extends CharSequenceConstraint { + private final CodePoints codePoints; + + public CodePointsConstraint(CharSequenceConstraint delegate, + CodePoints codePoints) { + super(); + this.codePoints = codePoints; + this.predicates().addAll(delegate.predicates()); + } + + public CodePointsConstraint asBlackList() { + this.predicates().add(ConstraintPredicate.withViolatedValue(x -> { + Set excludedFromBlackList = this.codePoints.allExcludedCodePoints(x); + Integer codePoint; + String str = x.toString(); + int len = str.length(); + Set included = new LinkedHashSet<>(); + for (int i = 0; i < len; i += Character.charCount(codePoint)) { + codePoint = str.codePointAt(i); + if (!excludedFromBlackList.contains(codePoint)) { + included.add(codePoint); + } + } + if (included.isEmpty()) { + return Optional.empty(); + } + List includedList = included.stream() // + .map(i -> new String(new int[] { i }, 0, 1)) // + .collect(Collectors.toList()); + return Optional.of(new ViolatedValue(includedList)); + }, CODE_POINTS_NOT_INCLUDED, () -> new Object[] {}, VALID)); + return this; + } + + public CodePointsConstraint asWhiteList() { + this.predicates().add(ConstraintPredicate.withViolatedValue(x -> { + Set excludedFromWhiteList = this.codePoints.allExcludedCodePoints(x); + if (excludedFromWhiteList.isEmpty()) { + return Optional.empty(); + } + List excludedList = excludedFromWhiteList.stream() // + .map(i -> new String(new int[] { i }, 0, 1)) // + .collect(Collectors.toList()); + return Optional.of(new ViolatedValue(excludedList)); + }, CODE_POINTS_ALL_INCLUDED, () -> new Object[] {}, VALID)); + return this; + } + + @Override + public CodePointsConstraint cast() { + return this; + } + + public static class Builder { + private final CodePoints codePoints; + + private final CharSequenceConstraint delegate; + + public Builder(CharSequenceConstraint delegate, CodePoints codePoints) { + this.delegate = delegate; + this.codePoints = codePoints; + } + + public CodePointsConstraint asBlackList() { + return new CodePointsConstraint<>(this.delegate, this.codePoints) + .asBlackList(); + } + + public CodePointsConstraint asWhiteList() { + return new CodePointsConstraint<>(this.delegate, this.codePoints) + .asWhiteList(); + } + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/Emoji.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/Emoji.java new file mode 100644 index 0000000..6050832 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/Emoji.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import org.xbib.datastructures.validation.constraint.charsequence.variant.StandardizedVariationSequence; +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class Emoji { + private static final String COMBINING_ENCLOSING_KEYCAP = new String( + new int[] { 0x20E3 }, 0, 1); + + private static final String DUMMY_REPLACEMENT = "X"; + + private static final String ELF = new String(new int[] { 0x1F9DD }, 0, 1); + + private static final String ENGLAND = new String( + new int[] { 0x1F3F4, 0xE0067, 0xE0062, 0xE0065, 0xE006E, 0xE0067, 0xE007F }, + 0, 7); + + private static final String PERSON = new String(new int[] { 0x1F9D1 }, 0, 1); + + private static final String REGIONAL_INDICATOR_SYMBOL_LETTER_RANGE = new String( + new int[] { 0x1F1E6 }, 0, 1) + "-" + new String(new int[] { 0x1F1FF }, 0, 1); + + private static final String SCOTLAND = new String( + new int[] { 0x1F3F4, 0xE0067, 0xE0062, 0xE0073, 0xE0063, 0xE0074, 0xE007F }, + 0, 7); + + private static final String SKIN_TONE_SELECTOR_RANGE = new String( + new int[] { 0x1F3FB }, 0, 1) + "-" + new String(new int[] { 0x1F3FF }, 0, 1); + + private static final String SKUL_AND_CROSSBONES = new String(new int[] { 0x2620 }, 0, + 1); + + private static final String WALES = new String( + new int[] { 0x1F3F4, 0xE0067, 0xE0062, 0xE0077, 0xE006C, 0xE0073, 0xE007F }, + 0, 7); + + private static final String WHITE_UP_POINTING_INDEX = new String(new int[] { 0x261D }, + 0, 1); + + private static final String ZERO_WIDTH_JOINER = "\u200D"; + + /** + * Try to return the length of the given string.
+ * This method does not grantee the exact length. + * @see
Emoji 12.0 + * @param str + * @return the length of the given string which may be true + */ + public static int bestEffortCount(@Nullable String str) { + if (str == null || str.isEmpty()) { + return 0; + } + String s = str + .replaceAll("[" + StandardizedVariationSequence.RANGE + + COMBINING_ENCLOSING_KEYCAP + "]", "") // + .replaceAll("([" + WHITE_UP_POINTING_INDEX + "-" + ELF + "][" + + SKIN_TONE_SELECTOR_RANGE + "])", DUMMY_REPLACEMENT) + .replaceAll("([" + ZERO_WIDTH_JOINER + "][" + SKUL_AND_CROSSBONES + "-" + + PERSON + "])", "") // + .replaceAll("([" + REGIONAL_INDICATOR_SYMBOL_LETTER_RANGE + "]{2})", + DUMMY_REPLACEMENT) // + .replace(ENGLAND, DUMMY_REPLACEMENT) // + .replace(SCOTLAND, DUMMY_REPLACEMENT) // + .replace(WALES, DUMMY_REPLACEMENT) // + // Support emojis that contains two skin tone selectors introduced in 12 + .replaceAll("(" + DUMMY_REPLACEMENT + ZERO_WIDTH_JOINER + + DUMMY_REPLACEMENT + ")", DUMMY_REPLACEMENT); + return s.codePointCount(0, s.length()); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/EmojiConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/EmojiConstraint.java new file mode 100644 index 0000000..462a884 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/EmojiConstraint.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import java.text.Normalizer; + +import static org.xbib.datastructures.validation.core.NullAs.VALID; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_FIXED_SIZE; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_GREATER_THAN; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_GREATER_THAN_OR_EQUAL; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_LESS_THAN; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.CONTAINER_LESS_THAN_OR_EQUAL; + +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.xbib.datastructures.validation.constraint.charsequence.variant.VariantOptions; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +public class EmojiConstraint + extends CharSequenceConstraint { + + public EmojiConstraint(CharSequenceConstraint delegate, + Normalizer.Form normalizerForm, VariantOptions variantOptions) { + super(normalizerForm, variantOptions); + this.predicates().addAll(delegate.predicates()); + } + + @Override + public EmojiConstraint fixedSize(int size) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) == size, this::size), + CONTAINER_FIXED_SIZE, () -> new Object[] { size }, VALID)); + return this; + } + + @Override + public EmojiConstraint greaterThan(int min) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) > min, this::size), + CONTAINER_GREATER_THAN, () -> new Object[] { min }, VALID)); + return this; + } + + @Override + public EmojiConstraint greaterThanOrEqual(int min) { + this.predicates().add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) >= min, this::size), + CONTAINER_GREATER_THAN_OR_EQUAL, () -> new Object[] { min }, VALID)); + return this; + } + + @Override + public EmojiConstraint lessThan(int max) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) < max, this::size), + CONTAINER_LESS_THAN, () -> new Object[] { max }, VALID)); + return this; + } + + @Override + public EmojiConstraint lessThanOrEqual(int max) { + this.predicates() + .add(ConstraintPredicate.withViolatedValue( + this.checkSizePredicate(x -> size(x) <= max, this::size), + CONTAINER_LESS_THAN_OR_EQUAL, () -> new Object[] { max }, VALID)); + return this; + } + + private int size(E x) { + return Emoji.bestEffortCount(this.normalize(x.toString())); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/AsciiCodePoints.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/AsciiCodePoints.java new file mode 100644 index 0000000..0b9acc4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/AsciiCodePoints.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence.codepoints; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints; + +public enum AsciiCodePoints implements CodePoints { + ASCII_PRINTABLE_CHARS((CodePointsRanges) () -> { + return Collections.singletonList(Range.of(0x0020 /* */, 0x007E /* ~ */)); + }), // + ASCII_CONTROL_CHARS((CodePointsRanges) () -> { + return Arrays.asList(Range.of(0x0000 /* NULL */, 0x001F /* UNIT SEPARATOR */), + Range.single(0x007F /* DELETE */)); + }), // + CRLF((CodePointsSet) () -> { + return new HashSet<>( + Arrays.asList(0x000A /* LINE FEED */, 0x000D /* CARRIAGE RETURN */)); + }); + + private final CodePoints delegate; + + AsciiCodePoints(CodePoints delegate) { + this.delegate = delegate; + } + + @Override + public Set allExcludedCodePoints(String s) { + return this.delegate.allExcludedCodePoints(s); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/CompositeCodePoints.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/CompositeCodePoints.java new file mode 100644 index 0000000..a15dd0b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/CompositeCodePoints.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence.codepoints; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints; + +public class CompositeCodePoints implements CodePoints { + private final List> composite = new ArrayList<>(); + + @SafeVarargs + public CompositeCodePoints(CodePoints... codePoints) { + final Set codePointsSet = new LinkedHashSet<>(); + final List ranges = new ArrayList<>(); + for (CodePoints points : codePoints) { + if (points instanceof CodePointsSet) { + codePointsSet.addAll(((CodePointsSet) points).asSet()); + } + else if (points instanceof CodePointsRanges) { + ranges.addAll(((CodePointsRanges) points).asRanges()); + } + else { + composite.add(points); + } + } + if (!codePointsSet.isEmpty()) { + composite.add((CodePointsSet) () -> codePointsSet); + } + if (!ranges.isEmpty()) { + composite.add((CodePointsRanges) () -> ranges); + } + if (composite.isEmpty()) { + throw new IllegalArgumentException("No code point is included"); + } + } + + @Override + public Set allExcludedCodePoints(E s) { + Set excluded = null; + for (CodePoints codePoints : this.composite) { + Set e = codePoints.allExcludedCodePoints(s); + if (e.isEmpty()) { + return e; + } + if (excluded == null) { + excluded = e; + } + else { + excluded.retainAll(e); + } + } + return excluded == null ? Collections.emptySet() : excluded; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/UnicodeCodePoints.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/UnicodeCodePoints.java new file mode 100644 index 0000000..6bcf556 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/UnicodeCodePoints.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence.codepoints; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints; + +public enum UnicodeCodePoints implements CodePoints { + /** + * Hiragana listed in https://www.unicode.org/charts/nameslist/c_3040.html
+ * Note that this is a bit different from JIS X 0208's row 4 (Hiragana, 0x3041-0x3093) + */ + HIRAGANA((CodePointsRanges) () -> { + return Collections.singletonList(Range.of(0x3041 /* ぁ */, 0x309F /* ゟ */)); + }), // + /** + * Katakana and Katakana Phonetic Extensions listed in + *
    + *
  • https://www.unicode.org/charts/nameslist/c_30A0.html
  • + *
  • https://www.unicode.org/charts/nameslist/c_31F0.html
  • + *
+ * Note that this is different from JIS X 0208's row 5 (Katanaka, 0x30A1-0x30F6) + */ + KATAKANA((CodePointsRanges) () -> { + return Arrays.asList( // + Range.of(0x30A0 /* ゠ */, 0x30FF /* ヿ */), + Range.of(0x31F0 /* ㇰ */, 0x31FF /* ㇿ */)); + }); + + private final CodePoints delegate; + + UnicodeCodePoints(CodePoints delegate) { + this.delegate = delegate; + } + + @Override + public Set allExcludedCodePoints(String s) { + return this.delegate.allExcludedCodePoints(s); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/package-info.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/package-info.java new file mode 100644 index 0000000..fa8bb99 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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. + */ +@NonNullApi +package org.xbib.datastructures.validation.constraint.charsequence.codepoints; + +import org.xbib.datastructures.validation.jsr305.NonNullApi; diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/package-info.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/package-info.java new file mode 100644 index 0000000..346d26d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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. + */ +@NonNullApi +package org.xbib.datastructures.validation.constraint.charsequence; + +import org.xbib.datastructures.validation.jsr305.NonNullApi; diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/IdeographicVariationSequence.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/IdeographicVariationSequence.java new file mode 100644 index 0000000..36d168e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/IdeographicVariationSequence.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence.variant; + +public enum IdeographicVariationSequence { + IGNORE(true), NOT_IGNORE(false); + + public static final String RANGE = new String(new int[] { 0xE0100 }, 0, 1) + "-" + + new String(new int[] { 0xE01EF }, 0, 1); + private final boolean ignore; + + IdeographicVariationSequence(boolean ignore) { + this.ignore = ignore; + } + + public boolean ignore() { + return this.ignore; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/MongolianFreeVariationSelector.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/MongolianFreeVariationSelector.java new file mode 100644 index 0000000..0f4a346 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/MongolianFreeVariationSelector.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence.variant; + +public enum MongolianFreeVariationSelector { + IGNORE(true), NOT_IGNORE(false); + + public static final String RANGE = "\u180B-\u180D"; + private final boolean ignore; + + MongolianFreeVariationSelector(boolean ignore) { + this.ignore = ignore; + } + + public boolean ignore() { + return this.ignore; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/StandardizedVariationSequence.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/StandardizedVariationSequence.java new file mode 100644 index 0000000..debcf62 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/StandardizedVariationSequence.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence.variant; + +public enum StandardizedVariationSequence { + IGNORE(true), NOT_IGNORE(false); + + public static final String RANGE = "\uFE00-\uFE0F"; + private final boolean ignore; + + StandardizedVariationSequence(boolean ignore) { + this.ignore = ignore; + } + + public boolean ignore() { + return this.ignore; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/VariantOptions.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/VariantOptions.java new file mode 100644 index 0000000..757f100 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/VariantOptions.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence.variant; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class VariantOptions { + private final MongolianFreeVariationSelector fvs; + + private final IdeographicVariationSequence ivs; + + private final StandardizedVariationSequence svs; + + public VariantOptions(StandardizedVariationSequence svs, + IdeographicVariationSequence ivs, MongolianFreeVariationSelector fvs) { + this.svs = svs; + this.ivs = ivs; + this.fvs = fvs; + } + + public static Builder builder() { + return new Builder(); + } + + private boolean isNotIgnoreAll() { + return !this.svs.ignore() && !this.fvs.ignore() && !this.ivs.ignore(); + } + + public String ignored(@Nullable String s) { + if (s == null || s.isEmpty()) { + return ""; + } + if (this.isNotIgnoreAll()) { + return s; + } + StringBuilder regex = new StringBuilder("["); + if (this.svs.ignore()) { + regex.append(StandardizedVariationSequence.RANGE); + } + if (this.ivs.ignore()) { + regex.append(IdeographicVariationSequence.RANGE); + } + if (this.fvs.ignore()) { + regex.append(MongolianFreeVariationSelector.RANGE); + } + regex.append("]"); + return regex.length() == 2 ? s : s.replaceAll(regex.toString(), ""); + } + + public static class Builder { + private MongolianFreeVariationSelector fvs; + + private IdeographicVariationSequence ivs; + + private StandardizedVariationSequence svs; + + Builder() { + this.notIgnoreAll(); + } + + public VariantOptions build() { + return new VariantOptions(this.svs, this.ivs, this.fvs); + } + + public Builder fvs(MongolianFreeVariationSelector fvs) { + this.fvs = fvs; + return this; + } + + public Builder ignoreAll() { + this.svs = StandardizedVariationSequence.IGNORE; + this.ivs = IdeographicVariationSequence.IGNORE; + this.fvs = MongolianFreeVariationSelector.IGNORE; + return this; + } + + public Builder ivs(IdeographicVariationSequence ivs) { + this.ivs = ivs; + return this; + } + + public Builder notIgnoreAll() { + this.svs = StandardizedVariationSequence.NOT_IGNORE; + this.ivs = IdeographicVariationSequence.NOT_IGNORE; + this.fvs = MongolianFreeVariationSelector.NOT_IGNORE; + return this; + } + + public Builder svs(StandardizedVariationSequence svs) { + this.svs = svs; + return this; + } + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/package-info.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/package-info.java new file mode 100644 index 0000000..161c916 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/charsequence/variant/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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. + */ +@NonNullApi +package org.xbib.datastructures.validation.constraint.charsequence.variant; + +import org.xbib.datastructures.validation.jsr305.NonNullApi; diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/inetaddress/InetAddressUtils.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/inetaddress/InetAddressUtils.java new file mode 100644 index 0000000..6d199b2 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/inetaddress/InetAddressUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.inetaddress; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +/** + * @since 0.7.0 + */ +public class InetAddressUtils { + public static final String IPV4_REGEX = "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$"; + + private static final Pattern IPV4_PATTERN = Pattern.compile(IPV4_REGEX); + + private static final int MAX_BYTE = 128; + + private static final int MAX_UNSIGNED_SHORT = 0xffff; + + private static final int BASE_16 = 16; + + // Max number of hex groups (separated by :) in an IPV6 address + private static final int IPV6_MAX_HEX_GROUPS = 8; + + // Max hex digits in each IPv6 group + private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; + + public static boolean isIpv4(String s) { + return IPV4_PATTERN.matcher(s).matches(); + } + + // https://github.com/apache/commons-validator/blob/master/src/main/java/org/apache/commons/validator/routines/InetAddressValidator.java + public static boolean isIpv6(String s) { + // remove prefix size. This will appear after the zone id (if any) + final String[] parts = s.split("/", -1); + if (parts.length > 2) { + return false; // can only have one prefix specifier + } + if (parts.length == 2) { + if (!parts[1].matches("\\d{1,3}")) { + return false; // not a valid number + } + final int bits = Integer.parseInt(parts[1]); // cannot fail because of RE + // check + if (bits < 0 || bits > MAX_BYTE) { + return false; // out of range + } + } + // remove zone-id + final String[] partsZoneIdRemoved = parts[0].split("%", -1); + if (partsZoneIdRemoved.length > 2) { + return false; + } + // The id syntax is implementation independent, but it presumably cannot allow: + // whitespace, '/' or '%' + if ((partsZoneIdRemoved.length == 2) + && !partsZoneIdRemoved[1].matches("[^\\s/%]+")) { + return false; // invalid id + } + + final String firstPart = partsZoneIdRemoved[0]; + final boolean containsCompressedZeroes = firstPart.contains("::"); + if (containsCompressedZeroes + && (firstPart.indexOf("::") != firstPart.lastIndexOf("::"))) { + return false; + } + if ((firstPart.startsWith(":") && !firstPart.startsWith("::")) + || (firstPart.endsWith(":") && !firstPart.endsWith("::"))) { + return false; + } + String[] octets = firstPart.split(":"); + if (containsCompressedZeroes) { + final List octetList = new ArrayList<>(Arrays.asList(octets)); + if (firstPart.endsWith("::")) { + // String.split() drops ending empty segments + octetList.add(""); + } + else if (firstPart.startsWith("::") && !octetList.isEmpty()) { + octetList.remove(0); + } + octets = octetList.toArray(new String[0]); + } + if (octets.length > IPV6_MAX_HEX_GROUPS) { + return false; + } + int validOctets = 0; + int emptyOctets = 0; // consecutive empty chunks + for (int index = 0; index < octets.length; index++) { + String octet = octets[index]; + if (octet.isEmpty()) { + emptyOctets++; + if (emptyOctets > 1) { + return false; + } + } + else { + emptyOctets = 0; + // Is last chunk an IPv4 address? + if (index == octets.length - 1 && octet.contains(".")) { + if (!isIpv4(octet)) { + return false; + } + validOctets += 2; + continue; + } + if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) { + return false; + } + int octetInt = 0; + try { + octetInt = Integer.parseInt(octet, BASE_16); + } + catch (NumberFormatException e) { + return false; + } + if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) { + return false; + } + } + validOctets++; + } + if (validOctets > IPV6_MAX_HEX_GROUPS + || (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes)) { + return false; + } + return true; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/inetaddress/package-info.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/inetaddress/package-info.java new file mode 100644 index 0000000..dd53c01 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/inetaddress/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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. + */ +@NonNullApi +package org.xbib.datastructures.validation.constraint.inetaddress; + +import org.xbib.datastructures.validation.jsr305.NonNullApi; diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/CharSequencePasswordPoliciesBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/CharSequencePasswordPoliciesBuilder.java new file mode 100644 index 0000000..2936554 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/CharSequencePasswordPoliciesBuilder.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.password; + +import java.util.List; + +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +/** + * @since 0.7.0 + */ +public final class CharSequencePasswordPoliciesBuilder + extends PasswordPoliciesBuilder> { + + @Override + protected CharSequencePasswordPoliciesBuilder cast() { + return this; + } + + public CharSequencePasswordPoliciesBuilder uppercase() { + return this.uppercase(1); + } + + /** + * @since 0.8.1 + */ + @SuppressWarnings("unchecked") + public CharSequencePasswordPoliciesBuilder uppercase(int count) { + return super.required((PasswordPolicy) PasswordPolicy.UPPERCASE.count(count)); + } + + public CharSequencePasswordPoliciesBuilder lowercase() { + return this.lowercase(1); + } + + /** + * @since 0.8.1 + */ + @SuppressWarnings("unchecked") + public CharSequencePasswordPoliciesBuilder lowercase(int count) { + return super.required((PasswordPolicy) PasswordPolicy.LOWERCASE.count(count)); + } + + /** + * @since 0.8.1 + */ + public CharSequencePasswordPoliciesBuilder alphabets() { + return this.alphabets(1); + } + + /** + * @since 0.8.1 + */ + @SuppressWarnings("unchecked") + public CharSequencePasswordPoliciesBuilder alphabets(int count) { + return super.required((PasswordPolicy) PasswordPolicy.ALPHABETS.count(count)); + } + + public CharSequencePasswordPoliciesBuilder numbers() { + return this.numbers(1); + } + + /** + * @since 0.8.1 + */ + @SuppressWarnings("unchecked") + public CharSequencePasswordPoliciesBuilder numbers(int count) { + return super.required((PasswordPolicy) PasswordPolicy.NUMBERS.count(count)); + } + + public CharSequencePasswordPoliciesBuilder symbols() { + return this.symbols(1); + } + + /** + * @since 0.8.1 + */ + @SuppressWarnings("unchecked") + public CharSequencePasswordPoliciesBuilder symbols(int count) { + return super.required((PasswordPolicy) PasswordPolicy.SYMBOLS.count(count)); + } + + public List> strong() { + return this.uppercase().lowercase().numbers().symbols().build(); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/ObjectPasswordPoliciesBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/ObjectPasswordPoliciesBuilder.java new file mode 100644 index 0000000..af1bda7 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/ObjectPasswordPoliciesBuilder.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.password; + +/** + * @since 0.7.0 + */ +public class ObjectPasswordPoliciesBuilder + extends PasswordPoliciesBuilder> { + @Override + protected ObjectPasswordPoliciesBuilder cast() { + return this; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/PasswordPoliciesBuilder.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/PasswordPoliciesBuilder.java new file mode 100644 index 0000000..e324150 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/PasswordPoliciesBuilder.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.password; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.xbib.datastructures.validation.core.ConstraintPredicate; +import org.xbib.datastructures.validation.core.NullAs; +import org.xbib.datastructures.validation.core.ViolationMessage.Default; + +/** + * @since 0.7.0 + */ +public abstract class PasswordPoliciesBuilder> { + private final Set> requiredPolicies = new LinkedHashSet<>(); + + private final Set> optionalPolicies = new LinkedHashSet<>(); + + private int minimumOptionalPoliciesRequirement; + + protected abstract B cast(); + + public B required(PasswordPolicy policy) { + this.requiredPolicies.add(policy); + return this.cast(); + } + + @SafeVarargs + public final B required(PasswordPolicy... policies) { + this.requiredPolicies.addAll(Arrays.asList(policies)); + return this.cast(); + } + + @SafeVarargs + public final B optional(int minimumRequirement, PasswordPolicy... policies) { + this.optionalPolicies.addAll(Arrays.asList(policies)); + this.minimumOptionalPoliciesRequirement = minimumRequirement; + return this.cast(); + } + + public List> build() { + final List> predicates = new ArrayList<>(); + if (!requiredPolicies.isEmpty()) { + predicates.addAll(this.requiredConstraintPredicates()); + } + if (!optionalPolicies.isEmpty()) { + predicates.add(this.optionalConstraintPredicate()); + } + return predicates; + } + + private List> requiredConstraintPredicates() { + return this.requiredPolicies.stream() + .map(policy -> ConstraintPredicate.of(policy, Default.PASSWORD_REQUIRED, + () -> new Object[] { policy.name() }, NullAs.VALID)) + .collect(Collectors.toList()); + } + + private ConstraintPredicate optionalConstraintPredicate() { + final Predicate predicate = input -> { + int matched = 0; + for (PasswordPolicy policy : this.optionalPolicies) { + if (policy.test(input)) { + matched++; + } + } + return matched >= this.minimumOptionalPoliciesRequirement; + }; + return ConstraintPredicate.of(predicate, Default.PASSWORD_OPTIONAL, + () -> new Object[] { this.minimumOptionalPoliciesRequirement, + this.optionalPolicies.stream().map(PasswordPolicy::name) + .collect(Collectors.toList()) }, + NullAs.VALID); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/PasswordPolicy.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/PasswordPolicy.java new file mode 100644 index 0000000..a1d69be --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/PasswordPolicy.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.password; + +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @param target class + * @since 0.7.0 + */ +public interface PasswordPolicy extends Predicate { + + default String name() { + return this.getClass().getSimpleName() + .replace(PasswordPolicy.class.getSimpleName(), ""); + } + + /** + * Rename the password policy + * + * @param name new name + * @return renamed policy + * @since 0.8.1 + */ + default PasswordPolicy name(String name) { + return new PasswordPolicy() { + @Override + public String name() { + return name; + } + + @Override + public boolean test(T t) { + return PasswordPolicy.this.test(t); + } + }; + } + + static PasswordPolicy of(String name, Predicate predicate) { + return new PasswordPolicy() { + @Override + public boolean test(T t) { + return predicate.test(t); + } + + @Override + public String name() { + return name; + } + }; + } + + static PatternPasswordPolicy pattern(String name, + String regex) { + return new PatternPasswordPolicy<>(name, regex); + } + + /** + * @since 0.8.1 + */ + class PatternPasswordPolicy implements PasswordPolicy { + private final String name; + + private final String regex; + + private final Pattern pattern; + + private final int count; + + public PatternPasswordPolicy(String name, String regex) { + this(name, regex, 1); + } + + public PatternPasswordPolicy(String name, String regex, int count) { + if (count <= 0) { + throw new IllegalArgumentException("'count' must be greater than 0"); + } + this.name = name; + this.regex = regex; + this.pattern = Pattern.compile(regex); + this.count = count; + } + + @Override + public String name() { + return this.name; + } + + @Override + public boolean test(T input) { + final Matcher matcher = this.pattern.matcher(input); + int count = 0; + while (matcher.find()) { + if (++count >= this.count) { + return true; + } + } + return false; + } + + /** + * Change the count of the policy + * + * @param count new count + * @return new policy + */ + public PatternPasswordPolicy count(int count) { + if (this.count == count) { + return this; + } + return new PatternPasswordPolicy<>(this.name, this.regex, count); + } + } + + PatternPasswordPolicy UPPERCASE = PasswordPolicy.pattern("Uppercase", + "[A-Z]"); + + PatternPasswordPolicy LOWERCASE = PasswordPolicy.pattern("Lowercase", + "[a-z]"); + + /** + * @since 0.8.1 + */ + PatternPasswordPolicy ALPHABETS = PasswordPolicy.pattern("Alphabets", + "[a-zA-Z]"); + + PatternPasswordPolicy NUMBERS = PasswordPolicy.pattern("Numbers", "[0-9]"); + + PatternPasswordPolicy SYMBOLS = PasswordPolicy.pattern("Symbols", + "[!-/:-@\\[-`{-\\~]"); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/package-info.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/package-info.java new file mode 100644 index 0000000..53af970 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/constraint/password/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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. + */ +@NonNullApi +package org.xbib.datastructures.validation.constraint.password; + +import org.xbib.datastructures.validation.jsr305.NonNullApi; diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ApplicativeValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ApplicativeValidator.java new file mode 100644 index 0000000..b89ec02 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ApplicativeValidator.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +/** + * Applicative validator class + * @param Target class + * @since 0.6.0 + */ +@FunctionalInterface +public interface ApplicativeValidator extends ValueValidator { +} \ No newline at end of file diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/BiValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/BiValidator.java new file mode 100644 index 0000000..dc43eb6 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/BiValidator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.function.BiConsumer; + +/** + * BiValidator is a wrapper class of Validator that takes target + * object to validate and arbitrary errors object.
+ * The result of validation is contained in the errors object instead of returning it. + * This class is useful for a library to adapt both YAVI and other validation library such + * as Spring Framework's org.springframework.validation.Validator. + * + * Validator can be wrapped as follows: + * + *
+ * Validator<CartItem> validator = ValidatorBuilder.<CartItem> of()
+ *                        .constraint(CartItem::getQuantity, "quantity", c -> c.greaterThan(0))
+ *                        .constraint(...)
+ *                        .build();
+ * BiValidator<CartItem, Errors> validator = new BiValidator<>(validator, Errors::rejectValue);
+ * 
+ * + * @param the type of the instance to validate + * @param the type of the errors object + * @author Toshiaki Maki + * @since 0.5.0 + */ +public class BiValidator implements BiConsumer { + private final Validator validator; + + private final ErrorHandler errorHandler; + + public BiValidator(Validator validator, ErrorHandler errorHandler) { + this.validator = validator; + this.errorHandler = errorHandler; + } + + public void accept(T target, E errors) { + final ConstraintViolations violations = this.validator.validate(target); + violations.apply((name, messageKey, args, defaultMessage) -> this.errorHandler + .handleError(errors, name, messageKey, args, defaultMessage)); + } + + @FunctionalInterface + public interface ErrorHandler { + void handleError(E errors, String name, String messageKey, Object[] args, + String defaultMessage); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/CollectionValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/CollectionValidator.java new file mode 100644 index 0000000..80d3a6d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/CollectionValidator.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Collection; +import java.util.function.Function; + +public class CollectionValidator, E> { + private final String name; + + private final Function toCollection; + + private final Validator validator; + + public CollectionValidator(Function toCollection, String name, + Validator validator) { + this.toCollection = toCollection; + this.name = name; + this.validator = validator; + } + + public String name() { + return this.name; + } + + public Function toCollection() { + return this.toCollection; + } + + public Validator validator() { + return this.validator; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Constraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Constraint.java new file mode 100644 index 0000000..3ed21c8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Constraint.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Collection; +import java.util.Deque; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.OBJECT_EQUAL_TO; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.OBJECT_IS_NULL; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.OBJECT_NOT_NULL; +import static org.xbib.datastructures.validation.core.ViolationMessage.Default.OBJECT_ONE_OF; + +public interface Constraint> { + + C cast(); + + default C isNull() { + this.predicates().add(ConstraintPredicate.of(Objects::isNull, OBJECT_IS_NULL, + () -> new Object[] {}, NullAs.INVALID)); + return this.cast(); + } + + /** + * @since 0.10.0 + */ + default C equalTo(@Nullable V other) { + this.predicates().add(ConstraintPredicate.of(Predicate.isEqual(other), + OBJECT_EQUAL_TO, () -> new Object[] { other }, NullAs.INVALID)); + return this.cast(); + } + + /** + * @since 0.10.0 + */ + default C oneOf(Collection values) { + this.predicates().add(ConstraintPredicate.of(values::contains, OBJECT_ONE_OF, + () -> new Object[] { values }, NullAs.INVALID)); + return this.cast(); + } + + default C message(String message) { + ConstraintPredicate predicate = this.predicates().pollLast(); + if (predicate == null) { + throw new IllegalStateException("no constraint found to override!"); + } + this.predicates().addLast(predicate.overrideMessage(message)); + return this.cast(); + } + + default C message(ViolationMessage message) { + ConstraintPredicate predicate = this.predicates().pollLast(); + if (predicate == null) { + throw new IllegalStateException("no constraint found to override!"); + } + this.predicates().addLast(predicate.overrideMessage(message)); + return this.cast(); + } + + default C notNull() { + this.predicates().add(ConstraintPredicate.of(Objects::nonNull, OBJECT_NOT_NULL, + () -> new Object[] {}, NullAs.INVALID)); + return this.cast(); + } + + default C predicate(Predicate predicate, ViolationMessage violationMessage) { + final Supplier arguments = ViolatedArguments.supplyArguments(predicate); + this.predicates().add(ConstraintPredicate.of(predicate, violationMessage, + arguments, NullAs.VALID)); + return this.cast(); + } + + /** + * @since 0.8.2 + */ + default C predicate(Predicate predicate, String messageKey, + String defaultMessageFormat) { + return this.predicate(predicate, + ViolationMessage.of(messageKey, defaultMessageFormat)); + } + + default C predicate(CustomConstraint constraint) { + return this.predicate(constraint, constraint); + } + + default C predicateNullable(Predicate predicate, + ViolationMessage violationMessage) { + final Supplier arguments = ViolatedArguments.supplyArguments(predicate); + this.predicates().add(ConstraintPredicate.of(predicate, violationMessage, + arguments, NullAs.INVALID)); + return this.cast(); + } + + /** + * @since 0.8.2 + */ + default C predicateNullable(Predicate predicate, String messageKey, + String defaultMessageFormat) { + return this.predicateNullable(predicate, + ViolationMessage.of(messageKey, defaultMessageFormat)); + } + + default C predicateNullable(CustomConstraint constraint) { + return this.predicateNullable(constraint, constraint); + } + + Deque> predicates(); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintCondition.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintCondition.java new file mode 100644 index 0000000..ee7cfa7 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintCondition.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.function.BiPredicate; + +@FunctionalInterface +public interface ConstraintCondition extends BiPredicate { + /** + * @since 0.11.0 + */ + static ConstraintCondition hasAttribute(String key) { + return (t, context) -> context.attribute(key).exists(); + } + + /** + * @since 0.11.0 + */ + static ConstraintCondition hasAttributeWithValue(String key, Object value) { + return (t, context) -> context.attribute(key).isEqualTo(value); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintContext.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintContext.java new file mode 100644 index 0000000..0fc635c --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintContext.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Constraint Context that contains the name and attributes. + * + * @since 0.11.0 + */ +public interface ConstraintContext { + /** + * Returns the attribute for the given key. + * @param key key + * @return the attribute + */ + Attribute attribute(String key); + + /** + * Returns the context name.
+ * This method exists on this interface for backwards compatibility with YAVI 0.10 and + * earlier.
+ * It is effectively used in the lower interface ConstraintGroup. + * + * @return context name + */ + String name(); + + static ConstraintContext from(Map map) { + return new ConstraintContext() { + @Override + public Attribute attribute(String key) { + return () -> map.get(key); + } + + @Override + public String name() { + return "ConstraintContextFromMap"; + } + }; + } + + static ConstraintContext from(Function function) { + return new ConstraintContext() { + @Override + public Attribute attribute(String key) { + return () -> function.apply(key); + } + + @Override + public String name() { + return "ConstraintContextFromFunction"; + } + }; + } + + @FunctionalInterface + interface Attribute { + /** + * Returns the attribute value. null is returned if the attribute + * does not exist. + * @return value + */ + @Nullable + Object value(); + + /** + * Returns the typed attribute value + * @param clazz the type of the attribute value + * @return value + */ + @Nullable + default T value(Class clazz) { + return clazz.cast(this.value()); + } + + /** + * Returns whether the attribute value exists + * @return whether the attribute value exists + */ + default boolean exists() { + return this.value() != null; + } + + /** + * Return whether the attribute value is equal to the given value. + * @param value value + * @return whether the attribute value is equal to the given value. + */ + default boolean isEqualTo(Object value) { + return Objects.equals(this.value(), value); + } + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintGroup.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintGroup.java new file mode 100644 index 0000000..1c54a5d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintGroup.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Objects; + +/** + * ConstraintGroup is a specialized ConstraintContext with only + * the name, attributes is empty. + */ +@FunctionalInterface +public interface ConstraintGroup extends ConstraintContext { + ConstraintGroup DEFAULT = ConstraintGroup.of("DEFAULT"); + + static ConstraintGroup of(String name) { + return new ConstraintGroup() { + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ConstraintGroup)) { + return false; + } + ConstraintGroup cg = (ConstraintGroup) obj; + return Objects.equals(this.name(), cg.name()); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.name()); + } + + @Override + public String name() { + return name; + } + }; + } + + @Override + default Attribute attribute(String key) { + return () -> null; + } + + default ConstraintCondition toCondition() { + return (target, group) -> Objects.equals(name(), group.name()); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintPredicate.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintPredicate.java new file mode 100644 index 0000000..f833698 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintPredicate.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class ConstraintPredicate { + private final Supplier args; + + private final String defaultMessageFormat; + + private final String messageKey; + + private final NullAs nullAs; + + private final Predicate predicate; + + private ConstraintPredicate(Predicate predicate, ViolationMessage violationMessage, + Supplier args, NullAs nullAs) { + this(predicate, violationMessage.messageKey(), + violationMessage.defaultMessageFormat(), args, nullAs); + } + + private ConstraintPredicate(Predicate predicate, String messageKey, + String defaultMessageFormat, Supplier args, NullAs nullAs) { + this.predicate = predicate; + this.messageKey = messageKey; + this.defaultMessageFormat = defaultMessageFormat; + this.args = args; + this.nullAs = nullAs; + } + + public static ConstraintPredicate of(Predicate predicate, + ViolationMessage violationMessage, Supplier args, NullAs nullAs) { + return new ConstraintPredicate<>(predicate, violationMessage, args, nullAs); + } + + public static ConstraintPredicate withViolatedValue( + Function> violatedValue, + ViolationMessage violationMessage, Supplier args, NullAs nullAs) { + return new ConstraintPredicate(v -> !violatedValue.apply(v).isPresent(), + violationMessage, args, nullAs) { + @Override + public Optional violatedValue(@Nullable V target) { + return violatedValue.apply(target); + } + }; + } + + public Supplier args() { + return this.args; + } + + public final String defaultMessageFormat() { + return this.defaultMessageFormat; + } + + public String messageKey() { + return this.messageKey; + } + + public final NullAs nullValidity() { + return this.nullAs; + } + + public ConstraintPredicate overrideMessage(ViolationMessage message) { + return new ConstraintPredicate<>(this.predicate, message, this.args, this.nullAs); + } + + public ConstraintPredicate overrideMessage(String message) { + return new ConstraintPredicate<>(this.predicate, this.messageKey, message, + this.args, this.nullAs); + } + + public final Predicate predicate() { + return this.predicate; + } + + public Optional violatedValue(@Nullable V target) { + Predicate predicate = this.predicate(); + if (predicate.test(target)) { + return Optional.empty(); + } + else { + // violated + return Optional.of(new ViolatedValue(target)); + } + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintPredicates.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintPredicates.java new file mode 100644 index 0000000..f340499 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintPredicates.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Deque; +import java.util.function.Function; + +public class ConstraintPredicates { + private final String name; + + private final Deque> predicates; + + private final Function toValue; + + public ConstraintPredicates(Function toValue, String name, + Deque> predicates) { + this.toValue = toValue; + this.name = name; + this.predicates = predicates; + } + + public final String name() { + return this.name; + } + + public final Deque> predicates() { + return this.predicates; + } + + public final Function toValue() { + return this.toValue; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolation.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolation.java new file mode 100644 index 0000000..b559066 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolation.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Arrays; +import java.util.Locale; +import java.util.function.Function; + +import org.xbib.datastructures.validation.message.MessageFormatter; + +public class ConstraintViolation { + private final Object[] args; + + private final String defaultMessageFormat; + + private final Locale locale; + + private final MessageFormatter messageFormatter; + + private final String messageKey; + + private final String name; + + public ConstraintViolation(String name, String messageKey, + String defaultMessageFormat, Object[] args, MessageFormatter messageFormatter, + Locale locale) { + this.name = name; + this.messageKey = messageKey; + this.defaultMessageFormat = defaultMessageFormat; + this.args = args; + this.messageFormatter = messageFormatter; + this.locale = locale; + } + + public Object[] args() { + return this.args; + } + + public String defaultMessageFormat() { + return this.defaultMessageFormat; + } + + public ViolationDetail detail() { + return new ViolationDetail(this.messageKey, this.args, this.message()); + } + + public Locale locale() { + return this.locale; + } + + public String message() { + return this.messageFormatter.format(this.messageKey, this.defaultMessageFormat, + this.args, this.locale); + } + + public String messageKey() { + return this.messageKey; + } + + public String name() { + return this.name; + } + + @Override + public String toString() { + return "ConstraintViolation{" + "name='" + name + '\'' + ", messageKey='" + + messageKey + '\'' + ", defaultMessageFormat='" + defaultMessageFormat + + '\'' + ", args=" + Arrays.toString(args) + '}'; + } + + public Object violatedValue() { + return this.args[this.args.length - 1]; + } + + /** + * @since 0.7.0 + */ + public ConstraintViolation rename(Function rename) { + final String newName = rename.apply(name); + final Object[] newArgs = this.args().clone(); + if (newArgs.length > 0) { + newArgs[0] = newName; + } + return new ConstraintViolation(newName, this.messageKey, + this.defaultMessageFormat, newArgs, this.messageFormatter, this.locale); + } + + /** + * @since 0.7.0 + */ + public ConstraintViolation indexed(int index) { + return this.rename(name -> name + "[" + index + "]"); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolations.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolations.java new file mode 100644 index 0000000..15b9623 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolations.java @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ConstraintViolations implements List { + private final List delegate; + + /** + * Constructs with the constraintViolations to delegate. If the given value is + * ConstraintViolations itself, cast and return it. + * + * @param delegate constraintViolations to delegate + * @since 0.6.0 + */ + public static ConstraintViolations of(List delegate) { + if (delegate instanceof ConstraintViolations) { + return (ConstraintViolations) delegate; + } + return new ConstraintViolations(delegate); + } + + /** + * Constructs an empty list with an initial capacity of ten. + */ + public ConstraintViolations() { + this.delegate = new ArrayList<>(); + } + + /** + * Constructs with the constraintViolations to delegate + * @param delegate constraintViolations to delegate + * @since 0.6.0 + */ + public ConstraintViolations(List delegate) { + this.delegate = delegate; + } + + /** + * Appends the specified element to the end of this list (optional operation). + * + *

+ * Lists that support this operation may place limitations on what elements may be + * added to this list. In particular, some lists will refuse to add null elements, and + * others will impose restrictions on the type of elements that may be added. List + * classes should clearly specify in their documentation any restrictions on what + * elements may be added. + * + * @param constraintViolation element to be appended to this list + * @return true (as specified by {@link Collection#add}) + * @throws UnsupportedOperationException if the add operation is not + * supported by this list + * @throws ClassCastException if the class of the specified element prevents it from + * being added to this list + * @throws NullPointerException if the specified element is null and this list does + * not permit null elements + * @throws IllegalArgumentException if some property of this element prevents it from + * being added to this list + */ + @Override + public final boolean add(ConstraintViolation constraintViolation) { + return this.delegate.add(constraintViolation); + } + + /** + * Inserts the specified element at the specified position in this list (optional + * operation). Shifts the element currently at that position (if any) and any + * subsequent elements to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted + * @param element element to be inserted + * @throws UnsupportedOperationException if the add operation is not + * supported by this list + * @throws ClassCastException if the class of the specified element prevents it from + * being added to this list + * @throws NullPointerException if the specified element is null and this list does + * not permit null elements + * @throws IllegalArgumentException if some property of the specified element prevents + * it from being added to this list + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > size()) + */ + @Override + public final void add(int index, ConstraintViolation element) { + this.delegate.add(index, element); + } + + /** + * Appends all of the elements in the specified collection to the end of this list, in + * the order that they are returned by the specified collection's iterator (optional + * operation). The behavior of this operation is undefined if the specified collection + * is modified while the operation is in progress. (Note that this will occur if the + * specified collection is this list, and it's nonempty.) + * + * @param c collection containing elements to be added to this list + * @return true if this list changed as a result of the call + * @throws UnsupportedOperationException if the addAll operation is not + * supported by this list + * @throws ClassCastException if the class of an element of the specified collection + * prevents it from being added to this list + * @throws NullPointerException if the specified collection contains one or more null + * elements and this list does not permit null elements, or if the specified + * collection is null + * @throws IllegalArgumentException if some property of an element of the specified + * collection prevents it from being added to this list + * @see #add(Object) + */ + @Override + public final boolean addAll(Collection c) { + return this.delegate.addAll(c); + } + + /** + * Inserts all of the elements in the specified collection into this list at the + * specified position (optional operation). Shifts the element currently at that + * position (if any) and any subsequent elements to the right (increases their + * indices). The new elements will appear in this list in the order that they are + * returned by the specified collection's iterator. The behavior of this operation is + * undefined if the specified collection is modified while the operation is in + * progress. (Note that this will occur if the specified collection is this list, and + * it's nonempty.) + * + * @param index index at which to insert the first element from the specified + * collection + * @param c collection containing elements to be added to this list + * @return true if this list changed as a result of the call + * @throws UnsupportedOperationException if the addAll operation is not + * supported by this list + * @throws ClassCastException if the class of an element of the specified collection + * prevents it from being added to this list + * @throws NullPointerException if the specified collection contains one or more null + * elements and this list does not permit null elements, or if the specified + * collection is null + * @throws IllegalArgumentException if some property of an element of the specified + * collection prevents it from being added to this list + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > size()) + */ + @Override + public final boolean addAll(int index, Collection c) { + return this.delegate.addAll(c); + } + + /** + * This method is intended to be used with Spring MVC + * + *

sample

+ * + *
+	 * @PostMapping("users")
+	 * public String createUser(Model model, UserForm userForm, BindingResult bindingResult) {
+	 *     return validator.validateToEither(userForm)
+	 *         .fold(violations -> {
+	 *             violations.apply(bindingResult::rejectValue);
+	 *             return "userForm";
+	 *         }, form -> {
+	 *             // ...
+	 *             return "redirect:/";
+	 *         });
+	 * }
+	 * 
+ * + * @param callback + */ + public ConstraintViolations apply(Callback callback) { + this.forEach( + v -> callback.apply(v.name(), v.messageKey(), v.args(), v.message())); + return this; + } + + /** + * Removes all of the elements from this list (optional operation). The list will be + * empty after this call returns. + * + * @throws UnsupportedOperationException if the clear operation is not + * supported by this list + */ + @Override + public final void clear() { + this.delegate.clear(); + } + + /** + * Returns true if this list contains the specified element. More formally, + * returns true if and only if this list contains at least one element + * e such that + * (o==null ? e==null : o.equals(e)). + * + * @param o element whose presence in this list is to be tested + * @return true if this list contains the specified element + * @throws ClassCastException if the type of the specified element is incompatible + * with this list (optional) + * @throws NullPointerException if the specified element is null and this list does + * not permit null elements + * (optional) + */ + @Override + public final boolean contains(Object o) { + return this.delegate.contains(o); + } + + /** + * Returns true if this list contains all of the elements of the specified + * collection. + * + * @param c collection to be checked for containment in this list + * @return true if this list contains all of the elements of the specified + * collection + * @throws ClassCastException if the types of one or more elements in the specified + * collection are incompatible with this list + * (optional) + * @throws NullPointerException if the specified collection contains one or more null + * elements and this list does not permit null elements + * (optional), or if the + * specified collection is null + * @see #contains(Object) + */ + @Override + public final boolean containsAll(Collection c) { + return this.delegate.containsAll(c); + } + + public List details() { + return this.delegate.stream().map(ConstraintViolation::detail) + .collect(Collectors.toList()); + } + + /** + * Returns the element at the specified position in this list. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= size()) + */ + @Override + public final ConstraintViolation get(int index) { + return this.delegate.get(index); + } + + /** + * Returns the index of the first occurrence of the specified element in this list, or + * -1 if this list does not contain the element. More formally, returns the lowest + * index i such that + * (o==null ? get(i)==null : o.equals(get(i))), or -1 if + * there is no such index. + * + * @param o element to search for + * @return the index of the first occurrence of the specified element in this list, or + * -1 if this list does not contain the element + * @throws ClassCastException if the type of the specified element is incompatible + * with this list (optional) + * @throws NullPointerException if the specified element is null and this list does + * not permit null elements + * (optional) + */ + @Override + public final int indexOf(Object o) { + return this.delegate.indexOf(o); + } + + /** + * Returns true if this list contains no elements. + * + * @return true if this list contains no elements + */ + @Override + public final boolean isEmpty() { + return this.delegate.isEmpty(); + } + + public final boolean isValid() { + return this.delegate.isEmpty(); + } + + /** + * Returns an iterator over the elements in this list in proper sequence. + * + * @return an iterator over the elements in this list in proper sequence + */ + @Override + public final Iterator iterator() { + return this.delegate.iterator(); + } + + /** + * Returns the index of the last occurrence of the specified element in this list, or + * -1 if this list does not contain the element. More formally, returns the highest + * index i such that + * (o==null ? get(i)==null : o.equals(get(i))), or -1 if + * there is no such index. + * + * @param o element to search for + * @return the index of the last occurrence of the specified element in this list, or + * -1 if this list does not contain the element + * @throws ClassCastException if the type of the specified element is incompatible + * with this list (optional) + * @throws NullPointerException if the specified element is null and this list does + * not permit null elements + * (optional) + */ + @Override + public final int lastIndexOf(Object o) { + return this.delegate.lastIndexOf(o); + } + + /** + * Returns a list iterator over the elements in this list (in proper sequence). + * + * @return a list iterator over the elements in this list (in proper sequence) + */ + @Override + public final ListIterator listIterator() { + return this.delegate.listIterator(); + } + + /** + * Returns a list iterator over the elements in this list (in proper sequence), + * starting at the specified position in the list. The specified index indicates the + * first element that would be returned by an initial call to {@link ListIterator#next + * next}. An initial call to {@link ListIterator#previous previous} would return the + * element with the specified index minus one. + * + * @param index index of the first element to be returned from the list iterator (by a + * call to {@link ListIterator#next next}) + * @return a list iterator over the elements in this list (in proper sequence), + * starting at the specified position in the list + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index > size()}) + */ + @Override + public final ListIterator listIterator(int index) { + return this.delegate.listIterator(index); + } + + /** + * Removes the first occurrence of the specified element from this list, if it is + * present (optional operation). If this list does not contain the element, it is + * unchanged. More formally, removes the element with the lowest index i such + * that (o==null ? get(i)==null : o.equals(get(i))) (if + * such an element exists). Returns true if this list contained the specified + * element (or equivalently, if this list changed as a result of the call). + * + * @param o element to be removed from this list, if present + * @return true if this list contained the specified element + * @throws ClassCastException if the type of the specified element is incompatible + * with this list (optional) + * @throws NullPointerException if the specified element is null and this list does + * not permit null elements + * (optional) + * @throws UnsupportedOperationException if the remove operation is not + * supported by this list + */ + @Override + public final boolean remove(Object o) { + return this.delegate.remove(o); + } + + /** + * Removes the element at the specified position in this list (optional operation). + * Shifts any subsequent elements to the left (subtracts one from their indices). + * Returns the element that was removed from the list. + * + * @param index the index of the element to be removed + * @return the element previously at the specified position + * @throws UnsupportedOperationException if the remove operation is not + * supported by this list + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= size()) + */ + @Override + public final ConstraintViolation remove(int index) { + return this.delegate.remove(index); + } + + /** + * Removes from this list all of its elements that are contained in the specified + * collection (optional operation). + * + * @param c collection containing elements to be removed from this list + * @return true if this list changed as a result of the call + * @throws UnsupportedOperationException if the removeAll operation is not + * supported by this list + * @throws ClassCastException if the class of an element of this list is incompatible + * with the specified collection + * (optional) + * @throws NullPointerException if this list contains a null element and the specified + * collection does not permit null elements + * (optional), or if the + * specified collection is null + * @see #remove(Object) + * @see #contains(Object) + */ + @Override + public final boolean removeAll(Collection c) { + return this.delegate.removeAll(c); + } + + /** + * Retains only the elements in this list that are contained in the specified + * collection (optional operation). In other words, removes from this list all of its + * elements that are not contained in the specified collection. + * + * @param c collection containing elements to be retained in this list + * @return true if this list changed as a result of the call + * @throws UnsupportedOperationException if the retainAll operation is not + * supported by this list + * @throws ClassCastException if the class of an element of this list is incompatible + * with the specified collection + * (optional) + * @throws NullPointerException if this list contains a null element and the specified + * collection does not permit null elements + * (optional), or if the + * specified collection is null + * @see #remove(Object) + * @see #contains(Object) + */ + @Override + public final boolean retainAll(Collection c) { + return this.delegate.retainAll(c); + } + + /** + * Replaces the element at the specified position in this list with the specified + * element (optional operation). + * + * @param index index of the element to replace + * @param element element to be stored at the specified position + * @return the element previously at the specified position + * @throws UnsupportedOperationException if the set operation is not + * supported by this list + * @throws ClassCastException if the class of the specified element prevents it from + * being added to this list + * @throws NullPointerException if the specified element is null and this list does + * not permit null elements + * @throws IllegalArgumentException if some property of the specified element prevents + * it from being added to this list + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= size()) + */ + @Override + public final ConstraintViolation set(int index, ConstraintViolation element) { + return this.delegate.set(index, element); + } + + /** + * Returns the number of elements in this list. If this list contains more than + * Integer.MAX_VALUE elements, returns Integer.MAX_VALUE. + * + * @return the number of elements in this list + */ + @Override + public final int size() { + return this.delegate.size(); + } + + /** + * Returns a view of the portion of this list between the specified + * fromIndex, inclusive, and toIndex, exclusive. (If + * fromIndex and toIndex are equal, the returned list is empty.) The + * returned list is backed by this list, so non-structural changes in the returned + * list are reflected in this list, and vice-versa. The returned list supports all of + * the optional list operations supported by this list. + *

+ *

+ * This method eliminates the need for explicit range operations (of the sort that + * commonly exist for arrays). Any operation that expects a list can be used as a + * range operation by passing a subList view instead of a whole list. For example, the + * following idiom removes a range of elements from a list: + * + *

+	 * {@code
+	 *      list.subList(from, to).clear();
+	 * }
+	 * 
+ * + * Similar idioms may be constructed for indexOf and lastIndexOf, + * and all of the algorithms in the Collections class can be applied to a + * subList. + *

+ *

+ * The semantics of the list returned by this method become undefined if the backing + * list (i.e., this list) is structurally modified in any way other than via + * the returned list. (Structural modifications are those that change the size of this + * list, or otherwise perturb it in such a fashion that iterations in progress may + * yield incorrect results.) + * + * @param fromIndex low endpoint (inclusive) of the subList + * @param toIndex high endpoint (exclusive) of the subList + * @return a view of the specified range within this list + * @throws IndexOutOfBoundsException for an illegal endpoint index value + * (fromIndex < 0 || toIndex > size || + * fromIndex > toIndex) + */ + @Override + public final List subList(int fromIndex, int toIndex) { + return this.delegate.subList(fromIndex, toIndex); + } + + public final void throwIfInvalid( + Function toException) throws E { + if (!isValid()) { + throw toException.apply(this); + } + } + + /** + * Returns an array containing all of the elements in this list in proper sequence + * (from first to last element). + * + *

+ * The returned array will be "safe" in that no references to it are maintained by + * this list. (In other words, this method must allocate a new array even if this list + * is backed by an array). The caller is thus free to modify the returned array. + * + *

+ * This method acts as bridge between array-based and collection-based APIs. + * + * @return an array containing all of the elements in this list in proper sequence + * @see Arrays#asList(Object[]) + */ + @Override + public final Object[] toArray() { + return this.delegate.toArray(); + } + + /** + * Returns an array containing all of the elements in this list in proper sequence + * (from first to last element); the runtime type of the returned array is that of the + * specified array. If the list fits in the specified array, it is returned therein. + * Otherwise, a new array is allocated with the runtime type of the specified array + * and the size of this list. + * + *

+ * If the list fits in the specified array with room to spare (i.e., the array has + * more elements than the list), the element in the array immediately following the + * end of the list is set to null. (This is useful in determining the length + * of the list only if the caller knows that the list does not contain any null + * elements.) + * + *

+ * Like the {@link #toArray()} method, this method acts as bridge between array-based + * and collection-based APIs. Further, this method allows precise control over the + * runtime type of the output array, and may, under certain circumstances, be used to + * save allocation costs. + * + *

+ * Suppose x is a list known to contain only strings. The following code can + * be used to dump the list into a newly allocated array of String: + * + *

+	 * {
+	 * 	@code
+	 * 	String[] y = x.toArray(new String[0]);
+	 * }
+	 * 
+ *

+ * Note that toArray(new Object[0]) is identical in function to + * toArray(). + * + * @param a the array into which the elements of this list are to be stored, if it is + * big enough; otherwise, a new array of the same runtime type is allocated for + * this purpose. + * @return an array containing the elements of this list + * @throws ArrayStoreException if the runtime type of the specified array is not a + * supertype of the runtime type of every element in this list + * @throws NullPointerException if the specified array is null + */ + @Override + public final T[] toArray(T[] a) { + return this.delegate.toArray(a); + } + + @Override + public String toString() { + return this.delegate.toString(); + } + + public final List violations() { + return Collections.unmodifiableList(this.delegate); + } + + @FunctionalInterface + public interface Callback { + void apply(String name, String messageKey, Object[] args, String defaultMessage); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolationsException.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolationsException.java new file mode 100644 index 0000000..1f621cc --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ConstraintViolationsException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @since 0.3.0 + */ +public class ConstraintViolationsException extends RuntimeException { + private final ConstraintViolations violations; + + public ConstraintViolationsException(String message, + List violations) { + super(message); + this.violations = ConstraintViolations.of(violations); + } + + public ConstraintViolationsException(List violations) { + this("Constraint violations found!" + System.lineSeparator() + + ConstraintViolations.of(violations).violations().stream() + .map(ConstraintViolation::message).map(s -> "* " + s) + .collect(Collectors.joining(System.lineSeparator())), + violations); + } + + public ConstraintViolations violations() { + return violations; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/CustomConstraint.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/CustomConstraint.java new file mode 100644 index 0000000..4fc767d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/CustomConstraint.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Objects; +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +public interface CustomConstraint + extends Predicate, ViolationMessage, ViolatedArguments { + + Objects[] EMPTY_ARRAY = new Objects[0]; + + @Override + default Object[] arguments(@Nullable V violatedValue) { + return EMPTY_ARRAY; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedCollectionValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedCollectionValidator.java new file mode 100644 index 0000000..63e8272 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedCollectionValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Collection; +import java.util.function.Function; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class NestedCollectionValidator, E, N> + extends CollectionValidator { + + private final Function nested; + + public NestedCollectionValidator(Function toCollection, String name, + Validator validator, Function nested) { + super(toCollection, name, validator); + this.nested = nested; + } + + @Nullable + public N nestedCollection(T target) { + return nested.apply(target); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedConstraintCondition.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedConstraintCondition.java new file mode 100644 index 0000000..7e89c8e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedConstraintCondition.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.function.Function; + +public class NestedConstraintCondition implements ConstraintCondition { + + private final Function nested; + + private final ConstraintCondition constraintCondition; + + public NestedConstraintCondition(Function nested, + ConstraintCondition constraintCondition) { + this.nested = nested; + this.constraintCondition = constraintCondition; + } + + @Override + public boolean test(T t, ConstraintContext constraintContext) { + final N n = this.nested.apply(t); + if (n == null) { + return false; + } + + return this.constraintCondition.test(n, constraintContext); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedConstraintPredicates.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedConstraintPredicates.java new file mode 100644 index 0000000..36db64f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedConstraintPredicates.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Deque; +import java.util.function.Function; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class NestedConstraintPredicates extends ConstraintPredicates { + private final Function nested; + + public NestedConstraintPredicates(Function toValue, String name, + Deque> constraintPredicates, Function nested) { + super(toValue, name, constraintPredicates); + this.nested = nested; + } + + @Nullable + public N nestedValue(T target) { + return nested.apply(target); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedValidator.java new file mode 100644 index 0000000..fbb78c9 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NestedValidator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Locale; +import java.util.function.Function; + +public class NestedValidator implements Validatable { + private final Function nested; + + private final Validatable validator; + + private final String prefix; + + public NestedValidator(Function nested, Validatable validator, + String prefix) { + this.nested = nested; + this.prefix = prefix; + this.validator = prefixedValidatorIfNeeded(validator, prefix); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Validatable prefixedValidatorIfNeeded(Validatable validator, + String prefix) { + if (validator instanceof NestedValidator) { + final NestedValidator nestedValidator = (NestedValidator) validator; + return new NestedValidator(nestedValidator.nested, nestedValidator.validator, + prefix); + } + return (validator instanceof Validator) + ? ((Validator) validator).prefixed(prefix) + : validator; + } + + @Override + public ConstraintViolations validate(T target, Locale locale, + ConstraintContext constraintContext) { + final N n = this.nested.apply(target); + if (n != null) { + return this.validator.validate(n, locale, constraintContext); + } + else { + return new ConstraintViolations(); + } + } + + public String getPrefix() { + return prefix; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NullAs.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NullAs.java new file mode 100644 index 0000000..297d8df --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/NullAs.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +public enum NullAs { + INVALID(false), VALID(true); + + private final boolean skipNull; + + NullAs(boolean skipNull) { + this.skipNull = skipNull; + } + + public boolean skipNull() { + return this.skipNull; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validatable.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validatable.java new file mode 100644 index 0000000..4d817f9 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validatable.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Locale; + +import org.xbib.datastructures.validation.fn.Validation; + +@FunctionalInterface +public interface Validatable { + /** + * Validates all constraints on {@code target}. + * + * @param target target to validate + * @param locale the locale targeted for the violation messages. + * @param constraintContext constraint context to validate + * @return constraint violations + * @throws IllegalArgumentException if target is {@code null} + */ + ConstraintViolations validate(T target, Locale locale, + ConstraintContext constraintContext); + + /** + * Validates all constraints on {@code target}.
+ * {@code Locale.getDefault()} is used to locate the violation messages. + * {@code ConstraintGroup.DEFAULT} is used as a constraint context. + * + * @param target target to validate + * @return constraint violations + * @throws IllegalArgumentException if target is {@code null} + */ + default ConstraintViolations validate(T target) { + return this.validate(target, Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + /** + * Validates all constraints on {@code target}.
+ * {@code ConstraintGroup.DEFAULT} is used as a constraint context. + * + * @param target target to validate + * @param locale the locale targeted for the violation messages. + * @return constraint violations + * @throws IllegalArgumentException if target is {@code null} + */ + default ConstraintViolations validate(T target, Locale locale) { + return this.validate(target, locale, ConstraintGroup.DEFAULT); + } + + /** + * Validates all constraints on {@code target}.
+ * {@code Locale.getDefault()} is used to locate the violation messages. + * + * @param target target to validate + * @param constraintContext constraint context to validate + * @return constraint violations + * @throws IllegalArgumentException if target is {@code null} + */ + default ConstraintViolations validate(T target, ConstraintContext constraintContext) { + return this.validate(target, Locale.getDefault(), constraintContext); + } + + /** + * Returns the corresponding applicative validator + * @return applicative validator + * @since 0.6.0 + */ + default ApplicativeValidator applicative() { + return (target, locale, constraintContext) -> { + final ConstraintViolations violations = Validatable.this.validate(target, + locale, constraintContext); + if (violations.isValid()) { + return Validated.of(Validation.success(target)); + } + else { + return Validated.of(Validation.failure(violations)); + } + }; + } + + /** + * Converts given applicative validator to a regular validator. + * @param applicative applicative validator to convert + * @return regular validator + * @since 0.11.0 + */ + static Validatable from( + ValueValidator applicative) { + return (target, locale, constraintGroup) -> applicative + .validate(target, locale, constraintGroup) + .fold(ConstraintViolations::of, result -> new ConstraintViolations()); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validated.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validated.java new file mode 100644 index 0000000..e764b90 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validated.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.fn.Validation; +import org.xbib.datastructures.validation.fn.Validations; +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * a specialized {@code Validation} type that regards {@code List} as + * {@code ConstraintViolations} + * @param value type in the case of success + * @since 0.6.0 + */ +public class Validated extends Validation { + private final Validation delegate; + + @SuppressWarnings("unchecked") + public static Validated of( + Validation delegate) { + if (delegate instanceof Validated) { + return ((Validated) delegate); + } + return new Validated<>(delegate); + } + + Validated(Validation delegate) { + this.delegate = delegate; + } + + @Override + public boolean isValid() { + return this.delegate.isValid(); + } + + @Override + public T value() { + return this.delegate.value(); + } + + @Override + public T valueNullable() { + return this.delegate.valueNullable(); + } + + @Override + public ConstraintViolations errors() { + return ConstraintViolations.of(this.delegate.errors()); + } + + @Override + @SuppressWarnings("unchecked") + protected > V yieldSuccess( + @Nullable U value) { + return (V) Validated.of(Validation.success(value)); + } + + @Override + @SuppressWarnings("unchecked") + protected > V yieldFailure( + List errors) { + return (V) Validated.of(Validation.failure(errors)); + } + + @Override + public Validated peek(Consumer consumer) { + return Validated.of(super.peek(consumer)); + } + + @Override + public Validated peekErrors(Consumer> consumer) { + return Validated.of(super.peekErrors(consumer)); + } + + /** + * @since 0.7.0 + */ + public Validated indexed(int index) { + return Validated.of(this.mapError(violation -> violation.indexed(index))); + } + + /** + * @since 0.7.0 + */ + public static Validated> sequence( + Iterable> values) { + return Validated.of(Validations.sequence(values)); + } + + /** + * @since 0.7.0 + */ + public static Validated> traverse(Iterable values, + Function> mapper) { + return Validated.of(Validations.traverse(values, mapper)); + } + + /** + * @since 0.7.0 + */ + public static Validated> traverseIndexed(Iterable values, + Validations.IndexedTraverser> mapper) { + return Validated.of(Validations.traverseIndexed(values, mapper)); + } + + /** + * @since 0.8.0 + */ + public static > Validated traverseIndexed( + Iterable values, + Validations.IndexedTraverser> mapper, + Supplier factory) { + return Validated.of(Validations.traverseIndexed(values, mapper, factory)); + } + + /** + * @since 0.8.0 + */ + public static Validated> traverseOptional(Optional value, + Function> mapper) { + return Validated.of(Validations.traverseOptional(value, mapper)); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validator.java new file mode 100644 index 0000000..3018880 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/Validator.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.fn.Pair; +import org.xbib.datastructures.validation.message.MessageFormatter; + +/** + * Validates the target instances. + * + * A Validator instance is immutable and can be used as a singleton. + * + * @param the type of the instance to validate + * @author Toshiaki Maki + */ +public class Validator implements Validatable { + private final List> collectionValidators; + + private final List, Validatable>> conditionalValidators; + + private final MessageFormatter messageFormatter; + + private final String messageKeySeparator; + + private final List> predicatesList; + + private final String prefix; + + private final boolean failFast; + + private final ApplicativeValidator applicativeValidator = Validatable.super.applicative(); + + public Validator(String messageKeySeparator, + List> predicatesList, + List> collectionValidators, + List, Validatable>> conditionalValidators, + MessageFormatter messageFormatter) { + this(messageKeySeparator, predicatesList, collectionValidators, + conditionalValidators, messageFormatter, false); + } + + /** + * @since 0.8.0 + */ + public Validator(String messageKeySeparator, + List> predicatesList, + List> collectionValidators, + List, Validatable>> conditionalValidators, + MessageFormatter messageFormatter, boolean failFast) { + this(messageKeySeparator, predicatesList, collectionValidators, + conditionalValidators, messageFormatter, failFast, ""); + } + + private Validator(String messageKeySeparator, + List> predicatesList, + List> collectionValidators, + List, Validatable>> conditionalValidators, + MessageFormatter messageFormatter, boolean failFast, String prefix) { + this.messageKeySeparator = messageKeySeparator; + this.predicatesList = Collections.unmodifiableList(predicatesList); + this.collectionValidators = Collections.unmodifiableList(collectionValidators); + this.conditionalValidators = Collections.unmodifiableList(conditionalValidators); + this.messageFormatter = messageFormatter; + this.failFast = failFast; + this.prefix = (prefix == null || prefix.isEmpty() + || prefix.endsWith(this.messageKeySeparator)) ? prefix + : prefix + this.messageKeySeparator; + } + + public Validator prefixed(String prefix) { + return new Validator<>(this.messageKeySeparator, this.predicatesList, + this.collectionValidators, this.conditionalValidators, + this.messageFormatter, this.failFast, prefix); + } + + /** + * Set whether to enable fail fast mode. If enabled, Validator returns from the + * current validation as soon as the first constraint violation occurs. + * + * @param failFast whether to enable fail fast mode + * @since 0.8.0 + */ + public Validator failFast(boolean failFast) { + return new Validator<>(this.messageKeySeparator, this.predicatesList, + this.collectionValidators, this.conditionalValidators, + this.messageFormatter, failFast, this.prefix); + } + + /** + * This method is supposed to be used only internally. + * + * @param action callback per ConstraintPredicates. + */ + public void forEachPredicates(Consumer> action) { + this.predicatesList.forEach(action); + } + + /** + * This method is supposed to be used only internally. + * + * @param action callback per CollectionValidator. + */ + public void forEachCollectionValidator( + Consumer> action) { + this.collectionValidators.forEach(action); + } + + /** + * This method is supposed to be used only internally. + * + * @param action callback per Pair, Validator>. + */ + public void forEachConditionalValidator( + Consumer, Validatable>> action) { + this.conditionalValidators.forEach(action); + } + + @Override + public ConstraintViolations validate(T target, Locale locale, + ConstraintContext constraintContext) { + return this.validate(target, "", -1, locale, constraintContext); + } + + @Override + public ApplicativeValidator applicative() { + return this.applicativeValidator; + } + + private String indexedName(String name, String collectionName, int index) { + if (index < 0) { + return name; + } + if (name.isEmpty()) { + return collectionName + "[" + index + "]"; + } + return collectionName + "[" + index + "]" + this.messageKeySeparator + name; + } + + private Object[] pad(String name, Object[] args, ViolatedValue violatedValue) { + Object[] pad = new Object[args.length + 2]; + pad[0] = name; + System.arraycopy(args, 0, pad, 1, args.length); + pad[pad.length - 1] = violatedValue.value(); + return pad; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private ConstraintViolations validate(T target, String collectionName, int index, + Locale locale, ConstraintContext constraintContext) { + if (target == null) { + throw new IllegalArgumentException("target must not be null"); + } + final ConstraintViolations violations = new ConstraintViolations(); + for (ConstraintPredicates predicates : this.predicatesList) { + if (predicates instanceof NestedConstraintPredicates) { + final NestedConstraintPredicates nested = (NestedConstraintPredicates) predicates; + final Object nestedValue = nested.nestedValue(target); + if (nestedValue == null) { + continue; + } + } + for (ConstraintPredicate constraintPredicate : predicates.predicates()) { + final Object v = predicates.toValue().apply(target); + if (v == null && constraintPredicate.nullValidity().skipNull()) { + continue; + } + final Optional violated = ((ConstraintPredicate) constraintPredicate) + .violatedValue(v); + if (violated.isPresent()) { + final ViolatedValue violatedValue = violated.get(); + final String name = this.prefix + + this.indexedName(predicates.name(), collectionName, index); + final Supplier argsSupplier = constraintPredicate.args(); + final Object[] args = (argsSupplier instanceof ViolatedArguments) + ? ((ViolatedArguments) argsSupplier) + .arguments(violatedValue.value()) + : argsSupplier.get(); + violations.add(new ConstraintViolation(name, + constraintPredicate.messageKey(), + constraintPredicate.defaultMessageFormat(), + pad(name, args, violatedValue), this.messageFormatter, + locale)); + if (this.failFast) { + return violations; + } + } + } + } + for (CollectionValidator collectionValidator : this.collectionValidators) { + final Collection collection = collectionValidator.toCollection() + .apply(target); + if (collection != null) { + final Validator validator = collectionValidator.validator() + .failFast(this.failFast); + int i = 0; + for (Object element : collection) { + if (element != null) { + final String nestedName = this.indexedName( + collectionValidator.name(), collectionName, index); + final ConstraintViolations v = validator.validate(element, + nestedName, i++, locale, constraintContext); + violations.addAll(v); + if (this.failFast) { + return violations; + } + } + } + } + } + for (Pair, Validatable> pair : this.conditionalValidators) { + final ConstraintCondition condition = pair.first(); + if (condition.test(target, constraintContext)) { + final Validatable validator = pair.second(); + final ConstraintViolations constraintViolations = validator + .validate(target, locale, constraintContext); + for (ConstraintViolation violation : constraintViolations) { + final ConstraintViolation renamed = violation + .rename(name -> this.prefix + + this.indexedName(name, collectionName, index)); + violations.add(renamed); + if (this.failFast) { + return violations; + } + } + } + } + return violations; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ValueValidator.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ValueValidator.java new file mode 100644 index 0000000..a091cff --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ValueValidator.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.fn.Validation; + +/** + * @since 0.8.0 + */ +@FunctionalInterface +public interface ValueValidator { + + Validated validate(T t, Locale locale, ConstraintContext constraintContext); + + /** + * Return {@link ValueValidator} instance that always successes without validation. + * + * @param target class + * @return value validator that always successes without validation + */ + static ValueValidator passThrough() { + return (x, locale, constraintContext) -> Validated.of(Validation.success(x)); + } + + default ValueValidator andThen(Function mapper) { + return (t, locale, constraintContext) -> ValueValidator.this + .validate(t, locale, constraintContext).map(mapper); + } + + /** + * @since 0.11.0 + */ + default ValueValidator andThen(ValueValidator validator) { + return (t, locale, constraintContext) -> ValueValidator.this + .validate(t, locale, constraintContext) + .flatMap(v -> validator.validate(v, locale, constraintContext)); + } + + default ValueValidator compose(Function mapper) { + return (a, locale, constraintContext) -> ValueValidator.this + .validate(mapper.apply(a), locale, constraintContext); + } + + default Validated validate(T t) { + return this.validate(t, Locale.getDefault(), ConstraintGroup.DEFAULT); + } + + default Validated validate(T t, ConstraintContext constraintContext) { + return this.validate(t, Locale.getDefault(), constraintContext); + } + + default Validated validate(T t, Locale locale) { + return this.validate(t, locale, ConstraintGroup.DEFAULT); + } + + default X validated(T t) throws ConstraintViolationsException { + return this.validate(t).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(T t, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(t, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default X validated(T t, Locale locale) throws ConstraintViolationsException { + return this.validate(t, locale).orElseThrow(ConstraintViolationsException::new); + } + + default X validated(T t, Locale locale, ConstraintContext constraintContext) + throws ConstraintViolationsException { + return this.validate(t, locale, constraintContext) + .orElseThrow(ConstraintViolationsException::new); + } + + default ValueValidator indexed(int index) { + return (t, locale, constraintContext) -> ValueValidator.this + .validate(t, locale, constraintContext).indexed(index); + } + + default > ValueValidator, C> liftCollection( + Supplier factory) { + return (values, locale, constraintContext) -> Validated.traverseIndexed(values, + (v, index) -> this.indexed(index).validate(v, locale, constraintContext), + factory); + } + + default ValueValidator, List> liftList() { + return ValueValidator.liftList(this); + } + + default ValueValidator, Set> liftSet() { + return ValueValidator.liftSet(this); + } + + default ValueValidator, Optional> liftOptional() { + return ValueValidator.liftOptional(this); + } + + /** + * @since 0.8.1 + */ + static > ValueValidator, C> liftCollection( + ValueValidator validator, Supplier factory) { + return (values, locale, constraintContext) -> Validated.traverseIndexed(values, + (v, index) -> validator.indexed(index).validate(v, locale, + constraintContext), + factory); + } + + /** + * @since 0.8.1 + */ + static ValueValidator, List> liftList( + ValueValidator validator) { + return ValueValidator.liftCollection(validator, ArrayList::new); + + } + + /** + * @since 0.8.1 + */ + static ValueValidator, Set> liftSet( + ValueValidator validator) { + // Since Index is attached to the name of ConstraintViolation, + // we need a consistent order. This is why we use LinkedHashSet. + return ValueValidator.liftCollection(validator, LinkedHashSet::new); + } + + /** + * @since 0.8.1 + */ + static ValueValidator, Optional> liftOptional( + ValueValidator validator) { + return (value, locale, constraintContext) -> Validated.traverseOptional(value, + v -> validator.validate(v, locale, constraintContext)); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolatedArguments.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolatedArguments.java new file mode 100644 index 0000000..d2b4134 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolatedArguments.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +@FunctionalInterface +public interface ViolatedArguments extends Supplier { + + /** + * returns arguments that can be used to build the violation message
+ * The argument can be access by {1}, {2} , ....
+ * Note that {0} is reserved for the property name and the last index in + * reserved for the actual value.
+ * The implementer don't need to include the property name and the actual value. + * @param violatedValue the violated value + * @return the array of arguments + */ + Object[] arguments(@Nullable T violatedValue); + + @Override + default Object[] get() { + return this.arguments(null); + } + + @SuppressWarnings("unchecked") + static Supplier supplyArguments(Predicate predicate) { + return (predicate instanceof ViolatedArguments) ? (ViolatedArguments) predicate + : () -> new Object[] {}; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolatedValue.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolatedValue.java new file mode 100644 index 0000000..c46746f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolatedValue.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Objects; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +public class ViolatedValue { + private final Object value; + + public ViolatedValue(@Nullable Object value) { + this.value = value; + } + + @Override + public String toString() { + return Objects.toString(this.value, ""); + } + + @Nullable + public Object value() { + return this.value; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolationDetail.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolationDetail.java new file mode 100644 index 0000000..fa18e44 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolationDetail.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Arrays; + +/** + * This class is intended to be used for the JSON serialization (such as Jackson) + */ +public class ViolationDetail { + private final Object[] args; + + private final String defaultMessage; + + private final String key; + + public ViolationDetail(String key, Object[] args, String defaultMessage) { + this.key = key; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public Object[] getArgs() { + return args; + } + + public String getDefaultMessage() { + return defaultMessage; + } + + public String getKey() { + return key; + } + + @Override + public String toString() { + return "ViolationDetail{" + "key='" + key + '\'' + ", args=" + + Arrays.toString(args) + ", defaultMessage='" + defaultMessage + '\'' + + '}'; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolationMessage.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolationMessage.java new file mode 100644 index 0000000..6debb44 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/core/ViolationMessage.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +public interface ViolationMessage { + static ViolationMessage of(String messageKey, String defaultMessageFormat) { + return new ViolationMessage() { + @Override + public String defaultMessageFormat() { + return defaultMessageFormat; + } + + @Override + public String messageKey() { + return messageKey; + } + }; + } + + String defaultMessageFormat(); + + String messageKey(); + + enum Default implements ViolationMessage { + OBJECT_NOT_NULL("object.notNull", "\"{0}\" must not be null"), // + OBJECT_IS_NULL("object.isNull", "\"{0}\" must be null"), // + OBJECT_EQUAL_TO("object.equalTo", "\"{0}\" must be equal to {1}"), // + OBJECT_ONE_OF("object.oneOf", "\"{0}\" must be one of the following values: {1}"), // + CONTAINER_NOT_EMPTY("container.notEmpty", "\"{0}\" must not be empty"), // + CONTAINER_LESS_THAN("container.lessThan", + "The size of \"{0}\" must be less than {1}. The given size is {2}"), // + CONTAINER_LESS_THAN_OR_EQUAL("container.lessThanOrEqual", + "The size of \"{0}\" must be less than or equal to {1}. The given size is {2}"), // + CONTAINER_GREATER_THAN("container.greaterThan", + "The size of \"{0}\" must be greater than {1}. The given size is {2}"), // + CONTAINER_GREATER_THAN_OR_EQUAL("container.greaterThanOrEqual", + "The size of \"{0}\" must be greater than or equal to {1}. The given size is {2}"), // + CONTAINER_FIXED_SIZE("container.fixedSize", + "The size of \"{0}\" must be {1}. The given size is {2}"), // + NUMERIC_GREATER_THAN("numeric.greaterThan", "\"{0}\" must be greater than {1}"), // + NUMERIC_GREATER_THAN_OR_EQUAL("numeric.greaterThanOrEqual", + "\"{0}\" must be greater than or equal to {1}"), // + NUMERIC_LESS_THAN("numeric.lessThan", "\"{0}\" must be less than {1}"), // + NUMERIC_LESS_THAN_OR_EQUAL("numeric.lessThanOrEqual", + "\"{0}\" must be less than or equal to {1}"), // + NUMERIC_POSITIVE("numeric.positive", "\"{0}\" must be positive"), // + NUMERIC_POSITIVE_OR_ZERO("numeric.positiveOrZero", + "\"{0}\" must be positive or zero"), // + NUMERIC_NEGATIVE("numeric.negative", "\"{0}\" must be negative"), // + NUMERIC_NEGATIVE_OR_ZERO("numeric.negativeOrZero", + "\"{0}\" must be negative or zero"), // + BOOLEAN_IS_TRUE("boolean.isTrue", "\"{0}\" must be true"), // + BOOLEAN_IS_FALSE("boolean.isFalse", "\"{0}\" must be false"), // + CHAR_SEQUENCE_NOT_BLANK("charSequence.notBlank", "\"{0}\" must not be blank"), // + CHAR_SEQUENCE_CONTAINS("charSequence.contains", "\"{0}\" must contain {1}"), // + CHAR_SEQUENCE_STARTSWITH("charSequence.startsWith", + "\"{0}\" must start with \"{1}\""), // + CHAR_SEQUENCE_ENDSWITH("charSequence.endsWith", "\"{0}\" must end with \"{1}\""), // + CHAR_SEQUENCE_EMAIL("charSequence.email", + "\"{0}\" must be a valid email address"), // + CHAR_SEQUENCE_IPV4("charSequence.ipv4", "\"{0}\" must be a valid IPv4"), // + CHAR_SEQUENCE_IPV6("charSequence.ipv6", "\"{0}\" must be a valid IPv6"), // + CHAR_SEQUENCE_URL("charSequence.url", "\"{0}\" must be a valid URL"), // + CHAR_SEQUENCE_UUID("charSequence.uuid", "\"{0}\" must be a valid UUID"), // + CHAR_SEQUENCE_PATTERN("charSequence.pattern", "\"{0}\" must match {1}"), // + CHAR_SEQUENCE_LUHN("charSequence.luhn", + "the check digit for \"{0}\" is invalid, Luhn checksum failed"), // + CHAR_SEQUENCE_BOOLEAN("charSequence.boolean", + "\"{0}\" must be a valid representation of a boolean"), // + CHAR_SEQUENCE_BYTE("charSequence.byte", + "\"{0}\" must be a valid representation of a byte"), // + CHAR_SEQUENCE_SHORT("charSequence.short", + "\"{0}\" must be a valid representation of a short"), // + CHAR_SEQUENCE_INTEGER("charSequence.integer", + "\"{0}\" must be a valid representation of an integer"), // + CHAR_SEQUENCE_LONG("charSequence.long", + "\"{0}\" must be a valid representation of a long"), // + CHAR_SEQUENCE_FLOAT("charSequence.float", + "\"{0}\" must be a valid representation of a float"), // + CHAR_SEQUENCE_DOUBLE("charSequence.double", + "\"{0}\" must be a valid representation of a double"), // + CHAR_SEQUENCE_BIGINTEGER("charSequence.biginteger", + "\"{0}\" must be a valid representation of a big integer"), // + CHAR_SEQUENCE_BIGDECIMAL("charSequence.bigdecimal", + "\"{0}\" must be a valid representation of a big decimal"), // + BYTE_SIZE_LESS_THAN("byteSize.lessThan", + "The byte size of \"{0}\" must be less than {1}. The given size is {2}"), // + BYTE_SIZE_LESS_THAN_OR_EQUAL("byteSize.lessThanOrEqual", + "The byte size of \"{0}\" must be less than or equal to {1}. The given size is {2}"), // + BYTE_SIZE_GREATER_THAN("byteSize.greaterThan", + "The byte size of \"{0}\" must be greater than {1}. The given size is {2}"), // + BYTE_SIZE_GREATER_THAN_OR_EQUAL("byteSize.greaterThanOrEqual", + "The byte size of \"{0}\" must be greater than or equal to {1}. The given size is {2}"), // + BYTE_SIZE_FIXED_SIZE("byteSize.fixedSize", + "The byte size of \"{0}\" must be {1}. The given size is {2}"), // + COLLECTION_CONTAINS("collection.contains", "\"{0}\" must contain {1}"), // + COLLECTION_UNIQUE("collection.unique", + "\"{0}\" must be unique. {1} is/are duplicated."), // + MAP_CONTAINS_VALUE("map.containsValue", "\"{0}\" must contain value {1}"), // + MAP_CONTAINS_KEY("map.containsKey", "\"{0}\" must contain key {1}"), // + ARRAY_CONTAINS("array.contains", "\"{0}\" must contain {1}"), // + CODE_POINTS_ALL_INCLUDED("codePoints.asWhiteList", + "\"{1}\" is/are not allowed for \"{0}\""), // + CODE_POINTS_NOT_INCLUDED("codePoints.asBlackList", + "\"{1}\" is/are not allowed for \"{0}\""), // + PASSWORD_REQUIRED("password.required", "\"{0}\" must meet {1} policy"), // + PASSWORD_OPTIONAL("password.optional", + "\"{0}\" must meet at least {1} policies from {2}"), // + TEMPORAL_PAST("temporal.past", "\"{0}\" must be a past date"), // + TEMPORAL_PAST_OR_PRESENT("temporal.pastOrPresent", + "\"{0}\" must be a date in the past or in the present"), // + TEMPORAL_FUTURE("temporal.future", "\"{0}\" must be a future date"), // + TEMPORAL_FUTURE_OR_PRESENT("temporal.futureOrPresent", + "\"{0}\" must be a date in the present or in the future"), // + TEMPORAL_BEFORE("temporal.before", "\"{0}\" has to be before {1}"), // + TEMPORAL_BEFORE_OR_EQUAL("temporal.beforeOrEqual", + "\"{0}\" has to be before or equals to {1}"), // + TEMPORAL_AFTER("temporal.after", "\"{0}\" has to be after {1}"), // + TEMPORAL_AFTER_OR_EQUAL("temporal.afterOrEqual", + "\"{0}\" has to be after or equals to {1}"), // + TEMPORAL_BETWEEN("temporal.between", "\"{0}\" has to be between {1} and {2}"), // + TEMPORAL_FIELD("temporal.field", "The {1} of \"{0}\" is invalid") // + ; + + private final String defaultMessageFormat; + + private final String messageKey; + + Default(String messageKey, String defaultMessageFormat) { + this.messageKey = messageKey; + this.defaultMessageFormat = defaultMessageFormat; + } + + @Override + public String defaultMessageFormat() { + return this.defaultMessageFormat; + } + + @Override + public String messageKey() { + return this.messageKey; + } + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/BiValidatorFactory.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/BiValidatorFactory.java new file mode 100644 index 0000000..a5fc100 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/BiValidatorFactory.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.factory; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.core.BiValidator; +import org.xbib.datastructures.validation.core.BiValidator.ErrorHandler; +import org.xbib.datastructures.validation.jsr305.Nullable; +import org.xbib.datastructures.validation.message.MessageFormatter; + +/** + * A factory class of BiValidator. It can be used to manage the common + * configurations of BiValidator in IoC container etc.
+ * + * In case of Spring Framework, you can define BiValidatorFactory as follows: + * + *

+ *{@literal @Bean}
+ * public BiValidatorFactory<Errors> biValidatorFactory(MessageSource messageSource) {
+ *   MessageFormatter messageFormatter = new MessageSourceMessageFormatter(messageSource::getMessage);
+ *   return new BiValidatorFactory<>(null, messageFormatter, Errors::rejectValues);
+ * }
+ * 
+ * + * A component can create a validator like following: + * + *
+ *{@literal @RestController}
+ * public class OrderController {
+ *     private final BiValidator<CartItem, Errors> validator;
+ *
+ *     public OrderController(BiValidatorFactory<Errors> factory) {
+ *         this.validator = factory.validator(builder -> builder.constraint(...));
+ *     }
+ * }
+ * 
+ * + * @param the type of the errors object + * @author Toshiaki Maki + * @since 0.5.0 + */ +public class BiValidatorFactory extends ValidatorFactorySupport { + private final ErrorHandler errorHandler; + + public BiValidatorFactory(@Nullable String messageKeySeparator, + @Nullable MessageFormatter messageFormatter, + @Nullable ErrorHandler errorHandler) { + super(messageKeySeparator, messageFormatter); + this.errorHandler = errorHandler; + } + + public BiValidatorFactory(@Nullable ErrorHandler errorHandler) { + this(null, null, errorHandler); + } + + public BiValidator validator(Function, ValidatorBuilder> constraints) { + if (this.errorHandler == null) { + throw new IllegalArgumentException("'errorHandler' must not be null."); + } + final ValidatorBuilder builder = super.initBuilder(); + return constraints.apply(builder).build(this.errorHandler); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/ValidatorFactory.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/ValidatorFactory.java new file mode 100644 index 0000000..481848e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/ValidatorFactory.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.factory; + +import java.util.function.Function; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.jsr305.Nullable; +import org.xbib.datastructures.validation.message.MessageFormatter; + +/** + * A factory class of Validator. It can be used to manage the common + * configurations of Validator in IoC container etc.
+ * + * In case of Spring Framework, you can define BiValidatorFactory as follows: + * + *
+ *{@literal @Bean}
+ * public ValidatorFactory validatorFactory(MessageSource messageSource) {
+ *   MessageFormatter messageFormatter = new MessageSourceMessageFormatter(messageSource::getMessage);
+ *   return new ValidatorFactory(null, messageFormatter);
+ * }
+ * 
+ * + * A component can create a validator like following: + * + *
+ *{@literal @RestController}
+ * public class OrderController {
+ *     private final Validator<CartItem> validator;
+ *
+ *     public OrderController(ValidatorFactory factory) {
+ *         this.validator = factory.validator(builder -> builder.constraint(...));
+ *     }
+ * }
+ * 
+ * + * @author Toshiaki Maki + * @since 0.5.0 + */ +public class ValidatorFactory extends ValidatorFactorySupport { + + public ValidatorFactory() { + this(null, null); + } + + public ValidatorFactory(@Nullable String messageKeySeparator, + @Nullable MessageFormatter messageFormatter) { + super(messageKeySeparator, messageFormatter); + } + + public Validator validator( + Function, ValidatorBuilder> constraints) { + final ValidatorBuilder builder = super.initBuilder(); + return constraints.apply(builder).build(); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/ValidatorFactorySupport.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/ValidatorFactorySupport.java new file mode 100644 index 0000000..9bfd222 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/factory/ValidatorFactorySupport.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.factory; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.jsr305.Nullable; +import org.xbib.datastructures.validation.message.MessageFormatter; + +abstract class ValidatorFactorySupport { + protected final String messageKeySeparator; + + protected final MessageFormatter messageFormatter; + + protected ValidatorFactorySupport(@Nullable String messageKeySeparator, + @Nullable MessageFormatter messageFormatter) { + this.messageKeySeparator = messageKeySeparator; + this.messageFormatter = messageFormatter; + } + + protected ValidatorBuilder initBuilder() { + final ValidatorBuilder builder = this.messageKeySeparator == null + ? new ValidatorBuilder<>() + : new ValidatorBuilder<>(this.messageKeySeparator); + if (this.messageFormatter != null) { + builder.messageFormatter(messageFormatter); + } + return builder; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining1.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining1.java new file mode 100644 index 0000000..a32e4bb --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining1.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining1 { + protected final Validation v1; + + public Combining1(Validation v1) { + this.v1 = v1; + } + + public > V apply(Function1 f) { + return v1.apply(Validation.success(Functions.curry(f))); + } + + public Combining2 combine(Validation v2) { + return new Combining2<>(v1, v2); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining10.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining10.java new file mode 100644 index 0000000..27d83c8 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining10.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining10 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + protected final Validation v9; + + protected final Validation v10; + + public Combining10(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + } + + public > V apply( + Function10 f) { + final Validation>>>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>>>> apply4 = v4 + .apply(apply3); + final Validation>>>>> apply5 = v5 + .apply(apply4); + final Validation>>>> apply6 = v6 + .apply(apply5); + final Validation>>> apply7 = v7 + .apply(apply6); + final Validation>> apply8 = v8.apply(apply7); + final Validation> apply9 = v9.apply(apply8); + return v10.apply(apply9); + } + + public Combining11 combine( + Validation v11) { + return new Combining11<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining11.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining11.java new file mode 100644 index 0000000..dcd3dc0 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining11.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining11 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + protected final Validation v9; + + protected final Validation v10; + + protected final Validation v11; + + public Combining11(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + } + + public > V apply( + Function11 f) { + final Validation>>>>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>>>>> apply4 = v4 + .apply(apply3); + final Validation>>>>>> apply5 = v5 + .apply(apply4); + final Validation>>>>> apply6 = v6 + .apply(apply5); + final Validation>>>> apply7 = v7 + .apply(apply6); + final Validation>>> apply8 = v8 + .apply(apply7); + final Validation>> apply9 = v9.apply(apply8); + final Validation> apply10 = v10.apply(apply9); + return v11.apply(apply10); + } + + public Combining12 combine( + Validation v12) { + return new Combining12<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining12.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining12.java new file mode 100644 index 0000000..9780db2 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining12.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining12 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + protected final Validation v9; + + protected final Validation v10; + + protected final Validation v11; + + protected final Validation v12; + + public Combining12(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + } + + public > V apply( + Function12 f) { + final Validation>>>>>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>>>>>> apply4 = v4 + .apply(apply3); + final Validation>>>>>>> apply5 = v5 + .apply(apply4); + final Validation>>>>>> apply6 = v6 + .apply(apply5); + final Validation>>>>> apply7 = v7 + .apply(apply6); + final Validation>>>> apply8 = v8 + .apply(apply7); + final Validation>>> apply9 = v9 + .apply(apply8); + final Validation>> apply10 = v10 + .apply(apply9); + final Validation> apply11 = v11.apply(apply10); + return v12.apply(apply11); + } + + public Combining13 combine( + Validation v13) { + return new Combining13<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining13.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining13.java new file mode 100644 index 0000000..b53dabd --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining13.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining13 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + protected final Validation v9; + + protected final Validation v10; + + protected final Validation v11; + + protected final Validation v12; + + protected final Validation v13; + + public Combining13(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + } + + public > V apply( + Function13 f) { + final Validation>>>>>>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>>>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>>>>>>> apply4 = v4 + .apply(apply3); + final Validation>>>>>>>> apply5 = v5 + .apply(apply4); + final Validation>>>>>>> apply6 = v6 + .apply(apply5); + final Validation>>>>>> apply7 = v7 + .apply(apply6); + final Validation>>>>> apply8 = v8 + .apply(apply7); + final Validation>>>> apply9 = v9 + .apply(apply8); + final Validation>>> apply10 = v10 + .apply(apply9); + final Validation>> apply11 = v11 + .apply(apply10); + final Validation> apply12 = v12.apply(apply11); + return v13.apply(apply12); + } + + public Combining14 combine( + Validation v14) { + return new Combining14<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining14.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining14.java new file mode 100644 index 0000000..74fac5e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining14.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining14 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + protected final Validation v9; + + protected final Validation v10; + + protected final Validation v11; + + protected final Validation v12; + + protected final Validation v13; + + protected final Validation v14; + + public Combining14(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + } + + public > V apply( + Function14 f) { + final Validation>>>>>>>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>>>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>>>>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>>>>>>>> apply4 = v4 + .apply(apply3); + final Validation>>>>>>>>> apply5 = v5 + .apply(apply4); + final Validation>>>>>>>> apply6 = v6 + .apply(apply5); + final Validation>>>>>>> apply7 = v7 + .apply(apply6); + final Validation>>>>>> apply8 = v8 + .apply(apply7); + final Validation>>>>> apply9 = v9 + .apply(apply8); + final Validation>>>> apply10 = v10 + .apply(apply9); + final Validation>>> apply11 = v11 + .apply(apply10); + final Validation>> apply12 = v12 + .apply(apply11); + final Validation> apply13 = v13.apply(apply12); + return v14.apply(apply13); + } + + public Combining15 combine( + Validation v15) { + return new Combining15<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining15.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining15.java new file mode 100644 index 0000000..370281d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining15.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining15 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + protected final Validation v9; + + protected final Validation v10; + + protected final Validation v11; + + protected final Validation v12; + + protected final Validation v13; + + protected final Validation v14; + + protected final Validation v15; + + public Combining15(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14, Validation v15) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + this.v15 = v15; + } + + public > V apply( + Function15 f) { + final Validation>>>>>>>>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>>>>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>>>>>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>>>>>>>>> apply4 = v4 + .apply(apply3); + final Validation>>>>>>>>>> apply5 = v5 + .apply(apply4); + final Validation>>>>>>>>> apply6 = v6 + .apply(apply5); + final Validation>>>>>>>> apply7 = v7 + .apply(apply6); + final Validation>>>>>>> apply8 = v8 + .apply(apply7); + final Validation>>>>>> apply9 = v9 + .apply(apply8); + final Validation>>>>> apply10 = v10 + .apply(apply9); + final Validation>>>> apply11 = v11 + .apply(apply10); + final Validation>>> apply12 = v12 + .apply(apply11); + final Validation>> apply13 = v13 + .apply(apply12); + final Validation> apply14 = v14.apply(apply13); + return v15.apply(apply14); + } + + public Combining16 combine( + Validation v16) { + return new Combining16<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining16.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining16.java new file mode 100644 index 0000000..927447d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining16.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining16 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + protected final Validation v9; + + protected final Validation v10; + + protected final Validation v11; + + protected final Validation v12; + + protected final Validation v13; + + protected final Validation v14; + + protected final Validation v15; + + protected final Validation v16; + + public Combining16(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14, Validation v15, + Validation v16) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + this.v10 = v10; + this.v11 = v11; + this.v12 = v12; + this.v13 = v13; + this.v14 = v14; + this.v15 = v15; + this.v16 = v16; + } + + public > V apply( + Function16 f) { + final Validation>>>>>>>>>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>>>>>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>>>>>>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>>>>>>>>>> apply4 = v4 + .apply(apply3); + final Validation>>>>>>>>>>> apply5 = v5 + .apply(apply4); + final Validation>>>>>>>>>> apply6 = v6 + .apply(apply5); + final Validation>>>>>>>>> apply7 = v7 + .apply(apply6); + final Validation>>>>>>>> apply8 = v8 + .apply(apply7); + final Validation>>>>>>> apply9 = v9 + .apply(apply8); + final Validation>>>>>> apply10 = v10 + .apply(apply9); + final Validation>>>>> apply11 = v11 + .apply(apply10); + final Validation>>>> apply12 = v12 + .apply(apply11); + final Validation>>> apply13 = v13 + .apply(apply12); + final Validation>> apply14 = v14 + .apply(apply13); + final Validation> apply15 = v15.apply(apply14); + return v16.apply(apply15); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining2.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining2.java new file mode 100644 index 0000000..367d540 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining2.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining2 { + protected final Validation v1; + + protected final Validation v2; + + public Combining2(Validation v1, Validation v2) { + this.v1 = v1; + this.v2 = v2; + } + + public > V apply(Function2 f) { + final Validation> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + return v2.apply(apply1); + } + + public Combining3 combine(Validation v3) { + return new Combining3<>(v1, v2, v3); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining3.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining3.java new file mode 100644 index 0000000..93458a4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining3.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining3 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + public Combining3(Validation v1, Validation v2, Validation v3) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public > V apply(Function3 f) { + final Validation>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation> apply2 = v2.apply(apply1); + return v3.apply(apply2); + } + + public Combining4 combine(Validation v4) { + return new Combining4<>(v1, v2, v3, v4); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining4.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining4.java new file mode 100644 index 0000000..eadb970 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining4.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining4 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + public Combining4(Validation v1, Validation v2, Validation v3, + Validation v4) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + } + + public > V apply(Function4 f) { + final Validation>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>> apply2 = v2.apply(apply1); + final Validation> apply3 = v3.apply(apply2); + return v4.apply(apply3); + } + + public Combining5 combine(Validation v5) { + return new Combining5<>(v1, v2, v3, v4, v5); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining5.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining5.java new file mode 100644 index 0000000..fe9a871 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining5.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining5 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + public Combining5(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + } + + public > V apply(Function5 f) { + final Validation>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>> apply2 = v2 + .apply(apply1); + final Validation>> apply3 = v3.apply(apply2); + final Validation> apply4 = v4.apply(apply3); + return v5.apply(apply4); + } + + public Combining6 combine(Validation v6) { + return new Combining6<>(v1, v2, v3, v4, v5, v6); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining6.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining6.java new file mode 100644 index 0000000..4cecfaa --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining6.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining6 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + public Combining6(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + } + + public > V apply( + Function6 f) { + final Validation>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>> apply2 = v2 + .apply(apply1); + final Validation>>> apply3 = v3 + .apply(apply2); + final Validation>> apply4 = v4.apply(apply3); + final Validation> apply5 = v5.apply(apply4); + return v6.apply(apply5); + } + + public Combining7 combine(Validation v7) { + return new Combining7<>(v1, v2, v3, v4, v5, v6, v7); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining7.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining7.java new file mode 100644 index 0000000..b2678de --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining7.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining7 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + public Combining7(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + } + + public > V apply( + Function7 f) { + final Validation>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>> apply3 = v3 + .apply(apply2); + final Validation>>> apply4 = v4 + .apply(apply3); + final Validation>> apply5 = v5.apply(apply4); + final Validation> apply6 = v6.apply(apply5); + return v7.apply(apply6); + } + + public Combining8 combine( + Validation v8) { + return new Combining8<>(v1, v2, v3, v4, v5, v6, v7, v8); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining8.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining8.java new file mode 100644 index 0000000..77245b1 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining8.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining8 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + public Combining8(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + } + + public > V apply( + Function8 f) { + final Validation>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>> apply4 = v4 + .apply(apply3); + final Validation>>> apply5 = v5 + .apply(apply4); + final Validation>> apply6 = v6.apply(apply5); + final Validation> apply7 = v7.apply(apply6); + return v8.apply(apply7); + } + + public Combining9 combine( + Validation v9) { + return new Combining9<>(v1, v2, v3, v4, v5, v6, v7, v8, v9); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining9.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining9.java new file mode 100644 index 0000000..a295499 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Combining9.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Combining9 { + protected final Validation v1; + + protected final Validation v2; + + protected final Validation v3; + + protected final Validation v4; + + protected final Validation v5; + + protected final Validation v6; + + protected final Validation v7; + + protected final Validation v8; + + protected final Validation v9; + + public Combining9(Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9) { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + this.v8 = v8; + this.v9 = v9; + } + + public > V apply( + Function9 f) { + final Validation>>>>>>>> apply1 = v1 + .apply(Validation.success(Functions.curry(f))); + final Validation>>>>>>> apply2 = v2 + .apply(apply1); + final Validation>>>>>> apply3 = v3 + .apply(apply2); + final Validation>>>>> apply4 = v4 + .apply(apply3); + final Validation>>>> apply5 = v5 + .apply(apply4); + final Validation>>> apply6 = v6 + .apply(apply5); + final Validation>> apply7 = v7.apply(apply6); + final Validation> apply8 = v8.apply(apply7); + return v9.apply(apply8); + } + + public Combining10 combine( + Validation v10) { + return new Combining10<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function1.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function1.java new file mode 100644 index 0000000..aaa5e2b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function1.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function1 { + + R apply(@Nullable T1 t1); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function10.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function10.java new file mode 100644 index 0000000..9ff033b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function10.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function10 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8, + @Nullable T9 t9, @Nullable T10 t10); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function11.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function11.java new file mode 100644 index 0000000..7dcc2ce --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function11.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function11 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8, + @Nullable T9 t9, @Nullable T10 t10, @Nullable T11 t11); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function12.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function12.java new file mode 100644 index 0000000..6155191 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function12.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function12 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8, + @Nullable T9 t9, @Nullable T10 t10, @Nullable T11 t11, @Nullable T12 t12); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function13.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function13.java new file mode 100644 index 0000000..baa15bd --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function13.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function13 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8, + @Nullable T9 t9, @Nullable T10 t10, @Nullable T11 t11, @Nullable T12 t12, + @Nullable T13 t13); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function14.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function14.java new file mode 100644 index 0000000..fdb2648 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function14.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function14 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8, + @Nullable T9 t9, @Nullable T10 t10, @Nullable T11 t11, @Nullable T12 t12, + @Nullable T13 t13, @Nullable T14 t14); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function15.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function15.java new file mode 100644 index 0000000..6d17734 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function15.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function15 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8, + @Nullable T9 t9, @Nullable T10 t10, @Nullable T11 t11, @Nullable T12 t12, + @Nullable T13 t13, @Nullable T14 t14, @Nullable T15 t15); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function16.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function16.java new file mode 100644 index 0000000..423758a --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function16.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function16 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8, + @Nullable T9 t9, @Nullable T10 t10, @Nullable T11 t11, @Nullable T12 t12, + @Nullable T13 t13, @Nullable T14 t14, @Nullable T15 t15, @Nullable T16 t16); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function2.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function2.java new file mode 100644 index 0000000..963646c --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function2.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function2 { + + R apply(@Nullable T1 t1, @Nullable T2 t2); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function3.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function3.java new file mode 100644 index 0000000..f8f7b5c --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function3.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function3 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function4.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function4.java new file mode 100644 index 0000000..1c23787 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function4.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function4 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function5.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function5.java new file mode 100644 index 0000000..8ef0701 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function5.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function5 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function6.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function6.java new file mode 100644 index 0000000..fb66690 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function6.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function6 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function7.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function7.java new file mode 100644 index 0000000..bd9021b --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function7.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function7 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function8.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function8.java new file mode 100644 index 0000000..75fd0cc --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function8.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function8 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function9.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function9.java new file mode 100644 index 0000000..56571e5 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Function9.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-function.sh + * + * @since 0.6.0 + */ +@FunctionalInterface +public interface Function9 { + + R apply(@Nullable T1 t1, @Nullable T2 t2, @Nullable T3 t3, @Nullable T4 t4, + @Nullable T5 t5, @Nullable T6 t6, @Nullable T7 t7, @Nullable T8 t8, + @Nullable T9 t9); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Functions.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Functions.java new file mode 100644 index 0000000..4efa06a --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Functions.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Functions { + public static Function1 curry(Function1 f) { + return t1 -> f.apply(t1); + } + + public static Function1> curry( + Function2 f) { + return t1 -> t2 -> f.apply(t1, t2); + } + + public static Function1>> curry( + Function3 f) { + return t1 -> t2 -> t3 -> f.apply(t1, t2, t3); + } + + public static Function1>>> curry( + Function4 f) { + return t1 -> t2 -> t3 -> t4 -> f.apply(t1, t2, t3, t4); + } + + public static Function1>>>> curry( + Function5 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> f.apply(t1, t2, t3, t4, t5); + } + + public static Function1>>>>> curry( + Function6 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> f.apply(t1, t2, t3, t4, t5, t6); + } + + public static Function1>>>>>> curry( + Function7 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> f.apply(t1, t2, t3, t4, t5, t6, + t7); + } + + public static Function1>>>>>>> curry( + Function8 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> f.apply(t1, t2, t3, t4, t5, + t6, t7, t8); + } + + public static Function1>>>>>>>> curry( + Function9 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> t9 -> f.apply(t1, t2, t3, + t4, t5, t6, t7, t8, t9); + } + + public static Function1>>>>>>>>> curry( + Function10 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> t9 -> t10 -> f.apply(t1, + t2, t3, t4, t5, t6, t7, t8, t9, t10); + } + + public static Function1>>>>>>>>>> curry( + Function11 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> t9 -> t10 -> t11 -> f + .apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11); + } + + public static Function1>>>>>>>>>>> curry( + Function12 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> t9 -> t10 -> t11 -> t12 -> f + .apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12); + } + + public static Function1>>>>>>>>>>>> curry( + Function13 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> t9 -> t10 -> t11 -> t12 -> t13 -> f + .apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13); + } + + public static Function1>>>>>>>>>>>>> curry( + Function14 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> t9 -> t10 -> t11 -> t12 -> t13 -> t14 -> f + .apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14); + } + + public static Function1>>>>>>>>>>>>>> curry( + Function15 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> t9 -> t10 -> t11 -> t12 -> t13 -> t14 -> t15 -> f + .apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15); + } + + public static Function1>>>>>>>>>>>>>>> curry( + Function16 f) { + return t1 -> t2 -> t3 -> t4 -> t5 -> t6 -> t7 -> t8 -> t9 -> t10 -> t11 -> t12 -> t13 -> t14 -> t15 -> t16 -> f + .apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, + t16); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Pair.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Pair.java new file mode 100644 index 0000000..8e74fa2 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Pair.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import java.util.Objects; + +public final class Pair { + private final F first; + private final S second; + + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Pair pair = (Pair) o; + return Objects.equals(first, pair.first) && Objects.equals(second, pair.second); + } + + public F first() { + return this.first; + } + + @Override + public int hashCode() { + return Objects.hash(first, second); + } + + public S second() { + return this.second; + } + + @Override + public String toString() { + return "Pair{" + "first=" + first + ", second=" + second + '}'; + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Validation.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Validation.java new file mode 100644 index 0000000..5430aae --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Validation.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +import static java.util.stream.Collectors.toList; + +/** + * An implementation similar to Vavr's + *
Validation control.
+ * The Validation control is an applicative functor and facilitates accumulating errors. + * + * @param value type in the case of failure + * @param value type in the case of success + * @since 0.6.0 + */ +public abstract class Validation implements Serializable { + private static final long serialVersionUID = 1L; + + public abstract boolean isValid(); + + /** + * Returns the value of this {@code Validation} if is a {@code Success} or throws if + * this is an {@code Failure}. + * + * @return The value of this {@code Validation} + * @throws NoSuchElementException if this is an {@code Failure} or the value is + * {@code null} + */ + public abstract T value(); + + /** + * Returns the value of this {@code Validation} if is a {@code Success} or throws if + * this is an {@code Failure}. {@code null} can be returned. + * + * @return The value of this {@code Validation} + * @throws NoSuchElementException if this is an {@code Failure}. + */ + @Nullable + public abstract T valueNullable(); + + /** + * Returns the errors of this {@code Validation} if it is an {@code Failure} or throws + * if this is a {@code Success}. + * + * @return The errors, if present + * @throws NoSuchElementException if this is a {@code Success} + */ + public abstract List errors(); + + @SuppressWarnings("unchecked") + public > V map( + Function mapper) { + return isValid() ? this.yieldSuccess(mapper.apply(value())) : (V) this; + } + + @SuppressWarnings("unchecked") + public > V flatMap(Function mapper) { + return isValid() ? mapper.apply(value()) : (V) this; + } + + public Validation peek(Consumer consumer) { + if (isValid()) { + consumer.accept(value()); + } + return this; + } + + @SuppressWarnings("unchecked") + public Validation mapErrors( + Function, ? extends List> errorsMapper) { + return isValid() ? (Validation) this + : Validation.failure(errorsMapper.apply(errors())); + } + + /** + * @since 0.8.2 + */ + @SuppressWarnings("unchecked") + public Validation mapError( + Function errorMapper) { + return isValid() ? (Validation) this + : Validation + .failure(errors().stream().map(errorMapper).collect(toList())); + } + + public Validation bimap( + Function, ? extends List> errorsMapper, + Function mapper) { + return isValid() ? Validation.success(mapper.apply(value())) + : Validation.failure(errorsMapper.apply(errors())); + } + + public Validation peekErrors(Consumer> consumer) { + if (!isValid()) { + consumer.accept(errors()); + } + return this; + } + + public T orElseThrow( + Function, ? extends X> exceptionMapper) throws X { + if (isValid()) { + return value(); + } + else { + throw exceptionMapper.apply(errors()); + } + } + + /** + * @since 0.10.0 + */ + public void throwIfInvalid( + Function, ? extends X> exceptionMapper) throws X { + if (!isValid()) { + throw exceptionMapper.apply(errors()); + } + } + + public T orElseGet(Function, ? extends T> other) { + if (isValid()) { + return value(); + } + else { + return other.apply(errors()); + } + } + + public U fold(Function, ? extends U> errorsMapper, + Function mapper) { + return isValid() ? mapper.apply(value()) : errorsMapper.apply(errors()); + } + + @SuppressWarnings("unchecked") + protected > V yieldSuccess(@Nullable U value) { + return (V) Validation.success(value); + } + + @SuppressWarnings("unchecked") + protected > V yieldFailure(List errors) { + return (V) Validation.failure(errors); + } + + public > V apply( + Validation> validation) { + if (isValid()) { + if (validation.isValid()) { + final Function1 f = validation.valueNullable(); + final U u = f == null ? null : f.apply(this.valueNullable()); + return this.yieldSuccess(u); + } + else { + final List errors = validation.errors(); + return this.yieldFailure(errors); + } + } + else { + final List errors = this.errors(); + if (validation.isValid()) { + return this.yieldFailure(errors); + } + else { + final List errorsList = new ArrayList<>(validation.errors()); + errorsList.addAll(errors); + return this.yieldFailure(errorsList); + } + } + } + + public Combining2 combine(Validation validation) { + return new Combining2<>(this, validation); + } + + public static Validation success(@Nullable T value) { + return new Success<>(value); + } + + public static Validation failure(List errors) { + return new Failure<>(errors); + } + + @SuppressWarnings("unchecked") + public static Validation failure(E... errors) { + return new Failure<>(Arrays.asList(errors)); + } + + public static class Success extends Validation { + private static final long serialVersionUID = 1L; + + public final T value; + + Success(@Nullable T value) { + this.value = value; + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public T value() { + if (this.value == null) { + throw new NoSuchElementException("'value' is null."); + } + return this.value; + } + + @Override + public T valueNullable() { + return this.value; + } + + @Override + public List errors() throws RuntimeException { + throw new NoSuchElementException( + "errors of 'Success' Validation does not exist."); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Success success = (Success) o; + return Objects.equals(value, success.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } + + public static class Failure extends Validation { + private static final long serialVersionUID = 1L; + + private final List errors; + + Failure(List errors) { + if (errors == null) { + throw new IllegalArgumentException("'errors' must not be null."); + } + if (errors.isEmpty()) { + throw new IllegalArgumentException("'errors' must not be empty."); + } + this.errors = Collections.unmodifiableList(errors); + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public T value() { + throw new NoSuchElementException( + "value of 'Failure' Validation does not exists. Errors=" + errors); + } + + @Override + public T valueNullable() { + return this.value(); + } + + @Override + public List errors() { + return this.errors; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Failure failure = (Failure) o; + return errors.equals(failure.errors); + } + + @Override + public int hashCode() { + return Objects.hash(errors); + } + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Validations.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Validations.java new file mode 100644 index 0000000..4c13918 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/fn/Validations.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; + +import static java.util.function.Function.identity; + +/** + * Generated by + * https://github.com/making/yavi/blob/develop/scripts/generate-applicative.sh + * + * @since 0.6.0 + */ +public class Validations { + public static Combining1 combine(Validation v1) { + return new Combining1<>(v1); + } + + public static Combining2 combine(Validation v1, + Validation v2) { + return new Combining2<>(v1, v2); + } + + public static Combining3 combine(Validation v1, + Validation v2, Validation v3) { + return new Combining3<>(v1, v2, v3); + } + + public static Combining4 combine( + Validation v1, Validation v2, Validation v3, + Validation v4) { + return new Combining4<>(v1, v2, v3, v4); + } + + public static Combining5 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5) { + return new Combining5<>(v1, v2, v3, v4, v5); + } + + public static Combining6 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6) { + return new Combining6<>(v1, v2, v3, v4, v5, v6); + } + + public static Combining7 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7) { + return new Combining7<>(v1, v2, v3, v4, v5, v6, v7); + } + + public static Combining8 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8) { + return new Combining8<>(v1, v2, v3, v4, v5, v6, v7, v8); + } + + public static Combining9 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9) { + return new Combining9<>(v1, v2, v3, v4, v5, v6, v7, v8, v9); + } + + public static Combining10 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10) { + return new Combining10<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); + } + + public static Combining11 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11) { + return new Combining11<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); + } + + public static Combining12 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12) { + return new Combining12<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); + } + + public static Combining13 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13) { + return new Combining13<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13); + } + + public static Combining14 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14) { + return new Combining14<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14); + } + + public static Combining15 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14, Validation v15) { + return new Combining15<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15); + } + + public static Combining16 combine( + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14, Validation v15, + Validation v16) { + return new Combining16<>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16); + } + + public static > V apply(Function1 f, + Validation v1) { + return combine(v1).apply(f); + } + + public static > V apply( + Function2 f, Validation v1, Validation v2) { + return combine(v1, v2).apply(f); + } + + public static > V apply( + Function3 f, Validation v1, Validation v2, + Validation v3) { + return combine(v1, v2, v3).apply(f); + } + + public static > V apply( + Function4 f, Validation v1, Validation v2, + Validation v3, Validation v4) { + return combine(v1, v2, v3, v4).apply(f); + } + + public static > V apply( + Function5 f, Validation v1, + Validation v2, Validation v3, Validation v4, + Validation v5) { + return combine(v1, v2, v3, v4, v5).apply(f); + } + + public static > V apply( + Function6 f, Validation v1, + Validation v2, Validation v3, Validation v4, + Validation v5, Validation v6) { + return combine(v1, v2, v3, v4, v5, v6).apply(f); + } + + public static > V apply( + Function7 f, Validation v1, + Validation v2, Validation v3, Validation v4, + Validation v5, Validation v6, Validation v7) { + return combine(v1, v2, v3, v4, v5, v6, v7).apply(f); + } + + public static > V apply( + Function8 f, Validation v1, + Validation v2, Validation v3, Validation v4, + Validation v5, Validation v6, Validation v7, + Validation v8) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8).apply(f); + } + + public static > V apply( + Function9 f, Validation v1, + Validation v2, Validation v3, Validation v4, + Validation v5, Validation v6, Validation v7, + Validation v8, Validation v9) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8, v9).apply(f); + } + + public static > V apply( + Function10 f, + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10).apply(f); + } + + public static > V apply( + Function11 f, + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11).apply(f); + } + + public static > V apply( + Function12 f, + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12).apply(f); + } + + public static > V apply( + Function13 f, + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13).apply(f); + } + + public static > V apply( + Function14 f, + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) + .apply(f); + } + + public static > V apply( + Function15 f, + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14, Validation v15) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) + .apply(f); + } + + public static > V apply( + Function16 f, + Validation v1, Validation v2, Validation v3, + Validation v4, Validation v5, Validation v6, + Validation v7, Validation v8, Validation v9, + Validation v10, Validation v11, Validation v12, + Validation v13, Validation v14, Validation v15, + Validation v16) { + return combine(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16).apply(f); + } + + public static Validation> sequence( + Iterable> validations) { + return traverse(validations, identity()); + } + + public static Validation> traverse(Iterable values, + Function> mapper) { + return traverseIndexed(values, IndexedTraverser.ignoreIndex(mapper)); + } + + /** + * @since 0.7.0 + */ + @FunctionalInterface + public interface IndexedTraverser { + R apply(A a, int index); + + static IndexedTraverser ignoreIndex( + Function f) { + return (a, index) -> f.apply(a); + } + } + + /** + * @since 0.7.0 + */ + public static Validation> traverseIndexed(Iterable values, + IndexedTraverser> traverser) { + return traverseIndexed(values, traverser, ArrayList::new); + } + + /** + * @since 0.8.0 + */ + public static > Validation traverseIndexed( + Iterable values, + IndexedTraverser> traverser, + Supplier factory) { + final List errors = new ArrayList<>(); + final C results = factory.get(); + int index = 0; + for (T value : values) { + traverser.apply(value, index++).fold(errors::addAll, results::add); + } + return errors.isEmpty() ? Validation.success(results) + : Validation.failure(errors); + } + + /** + * @since 0.8.0 + */ + @SuppressWarnings("unchecked") + public static Validation> traverseOptional(Optional value, + Function> mapper) { + return value.map( + t -> mapper.apply(t).bimap(es -> (List) es, u -> Optional.of((U) u))) // + .orElse(Validation.success(Optional.empty())); + } + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/NonNull.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/NonNull.java new file mode 100644 index 0000000..d18c1da --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/NonNull.java @@ -0,0 +1,35 @@ +package org.xbib.datastructures.validation.jsr305; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.xbib.annotation.Nonnull; +import org.xbib.annotation.meta.TypeQualifierNickname; + +/** + * A common annotation (similar to Spring ones) to declare that annotated elements + * cannot be {@code null}. Leverages JSR 305 meta-annotations to indicate nullability in + * Java to common tools with JSR 305 support. + * + *

+ * Should be used at parameter, return value, and field level. Methods overrides should + * repeat parent {@code @NonNull} annotations unless they behave differently. + * + *

+ * Use {@code @NonNullApi} (scope = parameters + return values) to set the default + * behavior to non-nullable in order to avoid annotating your whole codebase with + * {@code @NonNull}. + * + * @see NonNullApi + * @see Nullable + */ +@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Nonnull +@TypeQualifierNickname +public @interface NonNull { +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/NonNullApi.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/NonNullApi.java new file mode 100644 index 0000000..15579ca --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/NonNullApi.java @@ -0,0 +1,31 @@ +package org.xbib.datastructures.validation.jsr305; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.xbib.annotation.Nonnull; +import org.xbib.annotation.meta.TypeQualifierDefault; + +/** + * A common annotation (similar to Spring one) to declare that parameters and return + * values are to be considered as non-nullable by default for a given package. Leverages + * JSR 305 meta-annotations to indicate nullability in Java to common tools with JSR 305 + * support. + * + *

+ * Should be used at package level in association with {@link Nullable} annotations at + * parameter and return value level. + * + * @see Nullable + * @see NonNull + */ +@Target(ElementType.PACKAGE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Nonnull +@TypeQualifierDefault({ ElementType.METHOD, ElementType.PARAMETER }) +public @interface NonNullApi { +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/Nullable.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/Nullable.java new file mode 100644 index 0000000..48c8cb5 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/jsr305/Nullable.java @@ -0,0 +1,35 @@ +package org.xbib.datastructures.validation.jsr305; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.xbib.annotation.Nonnull; +import org.xbib.annotation.meta.TypeQualifierNickname; +import org.xbib.annotation.meta.When; + +/** + * A common annotation (similar to Spring ones) to declare that annotated elements + * can be {@code null} under some circumstance. Leverages JSR 305 meta-annotations to + * indicate nullability in Java to common tools with JSR 305 support. + * + *

+ * Should be used at parameter, return value, and field level. Methods overrides should + * repeat parent {@code @Nullable} annotations unless they behave differently. + * + *

+ * Can be used in association with {@code NonNullApi} to override the default non-nullable + * semantic to nullable. + * + * @see NonNullApi + * @see NonNull + */ +@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Nonnull(when = When.MAYBE) +@TypeQualifierNickname +public @interface Nullable { +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/MessageFormatter.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/MessageFormatter.java new file mode 100644 index 0000000..8d68261 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/MessageFormatter.java @@ -0,0 +1,9 @@ +package org.xbib.datastructures.validation.message; + +import java.util.Locale; + +@FunctionalInterface +public interface MessageFormatter { + String format(String messageKey, String defaultMessageFormat, Object[] args, + Locale locale); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/MessageSourceMessageFormatter.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/MessageSourceMessageFormatter.java new file mode 100644 index 0000000..f1142f2 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/MessageSourceMessageFormatter.java @@ -0,0 +1,52 @@ +package org.xbib.datastructures.validation.message; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Objects; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +/** + * MessageFormatter implementation that delegates formatting to + * MessageSource.
+ * This can adopt Spring Framework's MessageSource as follows: + * + *

+ * 
+ * org.springframework.context.MessageSource messageSource = ...;
+ * Validator<CartItem> validator = ValidatorBuilder.<CartItem> of()
+ *                        .messageFormatter(new MessageSourceMessageFormatter(messageSource::getMessage))
+ *                        .constraint(CartItem::getQuantity, "quantity", c -> c.greaterThan(0))
+ *                        .constraint(...)
+ *                        .build();
+ * 
+ * 
+ * + */ +public class MessageSourceMessageFormatter implements MessageFormatter { + private final MessageSource messageSource; + + public MessageSourceMessageFormatter(MessageSource messageSource) { + this.messageSource = messageSource; + } + + @Override + public String format(String messageKey, String defaultMessageFormat, Object[] args, + Locale locale) { + final String defaultMessage = new MessageFormat(defaultMessageFormat, locale) + .format(args); + final String message = this.messageSource.getMessage(messageKey, args, + defaultMessage, locale); + return Objects.requireNonNull(message, defaultMessage); + } + + /** + * A compatible interface of Spring Framework's MessageSource. + */ + @FunctionalInterface + public interface MessageSource { + @Nullable + String getMessage(String code, Object[] args, String defaultMessage, + Locale locale); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/SimpleMessageFormatter.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/SimpleMessageFormatter.java new file mode 100644 index 0000000..9861523 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/message/SimpleMessageFormatter.java @@ -0,0 +1,12 @@ +package org.xbib.datastructures.validation.message; + +import java.text.MessageFormat; +import java.util.Locale; + +public class SimpleMessageFormatter implements MessageFormatter { + @Override + public String format(String messageKey, String defaultMessageFormat, Object[] args, + Locale locale) { + return new MessageFormat(defaultMessageFormat, locale).format(args); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BigDecimalConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BigDecimalConstraintMeta.java new file mode 100644 index 0000000..8253b0d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BigDecimalConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.math.BigDecimal; + +/** + * @since 0.4.0 + */ +public interface BigDecimalConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BigIntegerConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BigIntegerConstraintMeta.java new file mode 100644 index 0000000..65cea95 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BigIntegerConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.math.BigInteger; + +/** + * @since 0.4.0 + */ +public interface BigIntegerConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BooleanConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BooleanConstraintMeta.java new file mode 100644 index 0000000..4727ac9 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/BooleanConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface BooleanConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ByteConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ByteConstraintMeta.java new file mode 100644 index 0000000..b628a40 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ByteConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface ByteConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/CharacterConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/CharacterConstraintMeta.java new file mode 100644 index 0000000..5b73fd3 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/CharacterConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface CharacterConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintArguments.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintArguments.java new file mode 100644 index 0000000..710f4b7 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintArguments.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @since 0.4.0 + */ +@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD }) +@Retention(RetentionPolicy.SOURCE) +public @interface ConstraintArguments { +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintMeta.java new file mode 100644 index 0000000..0c2bc43 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintMeta.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.util.function.Function; + +/** + * @since 0.4.0 + */ +public interface ConstraintMeta { + String name(); + + Function toValue(); +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintTarget.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintTarget.java new file mode 100644 index 0000000..269e5a4 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ConstraintTarget.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @since 0.4.0 + */ +@Target({ ElementType.METHOD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.SOURCE) +public @interface ConstraintTarget { + boolean getter() default true; + + boolean field() default false; +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/DoubleConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/DoubleConstraintMeta.java new file mode 100644 index 0000000..fd44035 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/DoubleConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface DoubleConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/FloatConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/FloatConstraintMeta.java new file mode 100644 index 0000000..4109f14 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/FloatConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface FloatConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/InstantConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/InstantConstraintMeta.java new file mode 100644 index 0000000..9a062b0 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/InstantConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.time.Instant; + +/** + * @since 0.10.0 + */ +public interface InstantConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/IntegerConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/IntegerConstraintMeta.java new file mode 100644 index 0000000..c12ee96 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/IntegerConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface IntegerConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalDateConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalDateConstraintMeta.java new file mode 100644 index 0000000..8804eab --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalDateConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.time.LocalDate; + +/** + * @since 0.10.0 + */ +public interface LocalDateConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalDateTimeConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalDateTimeConstraintMeta.java new file mode 100644 index 0000000..cd8af1d --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalDateTimeConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.time.LocalDateTime; + +/** + * @since 0.10.0 + */ +public interface LocalDateTimeConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalTimeConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalTimeConstraintMeta.java new file mode 100644 index 0000000..258a3b3 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LocalTimeConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.time.LocalTime; + +/** + * @since 0.10.0 + */ +public interface LocalTimeConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LongConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LongConstraintMeta.java new file mode 100644 index 0000000..490219e --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/LongConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface LongConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ObjectConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ObjectConstraintMeta.java new file mode 100644 index 0000000..754b7f9 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ObjectConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface ObjectConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/OffsetDateTimeConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/OffsetDateTimeConstraintMeta.java new file mode 100644 index 0000000..769f043 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/OffsetDateTimeConstraintMeta.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.time.OffsetDateTime; + +/** + * @since 0.10.0 + */ +public interface OffsetDateTimeConstraintMeta + extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ShortConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ShortConstraintMeta.java new file mode 100644 index 0000000..32f2366 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ShortConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface ShortConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/StringConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/StringConstraintMeta.java new file mode 100644 index 0000000..f8e6bbd --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/StringConstraintMeta.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +/** + * @since 0.4.0 + */ +public interface StringConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/YearConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/YearConstraintMeta.java new file mode 100644 index 0000000..22ce508 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/YearConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.time.Year; + +/** + * @since 0.11.0 + */ +public interface YearConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/YearMonthConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/YearMonthConstraintMeta.java new file mode 100644 index 0000000..618dedd --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/YearMonthConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.time.YearMonth; + +/** + * @since 0.11.0 + */ +public interface YearMonthConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ZonedDateTimeConstraintMeta.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ZonedDateTimeConstraintMeta.java new file mode 100644 index 0000000..78d9ef9 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/meta/ZonedDateTimeConstraintMeta.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.time.ZonedDateTime; + +/** + * @since 0.10.0 + */ +public interface ZonedDateTimeConstraintMeta extends ConstraintMeta { + +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/ConstraintMetaProcessor.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/ConstraintMetaProcessor.java new file mode 100644 index 0000000..9ed07cf --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/ConstraintMetaProcessor.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.processor; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.tools.JavaFileObject; + +import static org.xbib.datastructures.validation.processor.ConstraintMetaTemplate.template; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; +import static javax.lang.model.element.ElementKind.CLASS; +import static javax.lang.model.element.ElementKind.METHOD; +import static javax.lang.model.element.ElementKind.PARAMETER; +import static javax.lang.model.type.TypeKind.BOOLEAN; +import static javax.lang.model.type.TypeKind.BYTE; +import static javax.lang.model.type.TypeKind.CHAR; +import static javax.lang.model.type.TypeKind.DOUBLE; +import static javax.lang.model.type.TypeKind.FLOAT; +import static javax.lang.model.type.TypeKind.INT; +import static javax.lang.model.type.TypeKind.LONG; +import static javax.lang.model.type.TypeKind.SHORT; + +import org.xbib.datastructures.validation.fn.Pair; +import org.xbib.datastructures.validation.meta.ConstraintArguments; +import org.xbib.datastructures.validation.meta.ConstraintTarget; + +@SupportedAnnotationTypes({ "org.xbib.datastructures.validation.meta.ConstraintTarget", + "org.xbib.datastructures.validation.meta.ConstraintArguments" }) +public class ConstraintMetaProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public boolean process(Set annotations, + RoundEnvironment roundEnv) { + for (TypeElement typeElement : annotations) { + final Name qualifiedName = typeElement.getQualifiedName(); + if (qualifiedName.contentEquals(ConstraintTarget.class.getName())) { + this.processConstraintTarget(typeElement, roundEnv); + } + if (qualifiedName.contentEquals(ConstraintArguments.class.getName())) { + this.processConstraintArguments(typeElement, roundEnv); + } + } + return true; + } + + private void processConstraintTarget(TypeElement typeElement, + RoundEnvironment roundEnv) { + final Set elementsAnnotatedWith = roundEnv + .getElementsAnnotatedWith(typeElement); + final Map>> elementsMap = elementsAnnotatedWith + .stream() + .filter(x -> !(x instanceof ExecutableElement) + || ((ExecutableElement) x).getTypeParameters().isEmpty()) + .map(x -> new Pair(x, -1)).collect(groupingBy(k -> { + String className = "****"; + final Element element = k.first(); + if (element instanceof VariableElement) { + className = element.getEnclosingElement().getEnclosingElement() + .toString(); + } + else if (element instanceof ExecutableElement) { + className = element.getEnclosingElement().toString(); + } + return className; + }, toList())); + if (elementsMap.isEmpty()) { + return; + } + elementsMap.forEach(this::writeConstraintMetaFile); + } + + private void processConstraintArguments(TypeElement typeElement, + RoundEnvironment roundEnv) { + final Set elementsAnnotatedWith = roundEnv + .getElementsAnnotatedWith(typeElement); + + for (Element element : elementsAnnotatedWith) { + final List parameters = new ArrayList<>( + ((ExecutableElement) element).getParameters()); + String className = element.getEnclosingElement().toString(); + if (element.getKind() == METHOD) { + parameters.add(0, element.getEnclosingElement()); + className = className + + beanUpperCamel(element.getSimpleName().toString()); + } + final String argumentsClass = "org.xbib.datastructures.validation.arguments.Arguments" + + parameters.size() + "<" + parameters.stream() + .map(x -> type(x.asType())).collect(Collectors.joining(", ")) + + ">"; + final List> pairs = parameters.stream() + .map(x -> new Pair<>(x, parameters.indexOf(x))).collect(toList()); + this.writeConstraintArgumentMetaFile(className + "Arguments", argumentsClass, + pairs); + } + } + + private void writeConstraintMetaFile(String className, + List> elements) { + this.writeMetaFile(className, elements, (pair, metas) -> { + final Element element = pair.first(); + final ConstraintTarget constraintTarget = element + .getAnnotation(ConstraintTarget.class); + final ElementKind kind = element.getKind(); + final String name = element.getSimpleName().toString(); + if (kind == METHOD) { + final TypeMirror type = ((ExecutableElement) element).getReturnType(); + final String target = beanLowerCamel(constraintTarget.getter() + ? name.replaceFirst("^" + getterPrefix(type), "") + : name); + metas.put(target, template(className, type(type), target, name, + constraintTarget.field())); + } + else if (kind == PARAMETER) { + final TypeMirror type = element.asType(); + final String method = (constraintTarget.getter() + ? getterPrefix(type) + beanUpperCamel(name) + : name); + metas.put(name, template(className, type(type), name, method, + constraintTarget.field())); + } + }); + } + + private void writeConstraintArgumentMetaFile(String className, String argumentsClass, + List> elements) { + this.writeMetaFile(className, elements, (pair, metas) -> { + final Element element = pair.first(); + final TypeMirror type = element.asType(); + final int position = pair.second() + 1; + final String name = element.getSimpleName().toString(); + metas.put(name, + ConstraintMetaTemplate.templateArgument(argumentsClass, type(type), + element.getKind() == CLASS ? beanLowerCamel(name) : name, + position)); + }); + } + + private void writeMetaFile(String className, List> elements, + BiConsumer, Map> processElement) { + final Pair pair = splitClassName(className); + final String packageName = pair.first(); + final String simpleClassName = pair.second(); + final String metaSimpleClassName = "_" + simpleClassName.replace('.', '_') + + "Meta"; + final String metaClassName = packageName + "." + metaSimpleClassName; + try { + final JavaFileObject builderFile = super.processingEnv.getFiler() + .createSourceFile(metaClassName); + try (final PrintWriter out = new PrintWriter(builderFile.openWriter())) { + + if (!packageName.isEmpty()) { + out.print("package "); + out.print(packageName); + out.println(";"); + out.println(); + } + + out.println("// Generated at " + OffsetDateTime.now()); + out.print("public class "); + out.print(metaSimpleClassName); + out.println(" {"); + + final Map metas = new LinkedHashMap<>(); + for (Pair element : elements) { + processElement.accept(element, metas); + } + metas.forEach((k, v) -> out.println(" " + v + ";")); + out.println("}"); + } + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + static Pair splitClassName(String className) { + String packageName = ""; + final int p = firstUpperPosition(className); + if (p > 0) { + packageName = className.substring(0, p - 1); + } + final String simpleClassName = className.substring(p); + return new Pair<>(packageName, simpleClassName); + } + + static int firstUpperPosition(String s) { + final String lower = s.toLowerCase(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) != lower.charAt(i)) { + return i; + } + } + return -1; + } + + static String getterPrefix(TypeMirror type) { + return type.getKind() == BOOLEAN ? "is" : "get"; + } + + static String type(final TypeMirror typeMirror) { + final TypeKind kind = typeMirror.getKind(); + if (kind.isPrimitive()) { + if (kind == BOOLEAN) { + return Boolean.class.getName(); + } + else if (kind == BYTE) { + return Byte.class.getName(); + } + else if (kind == SHORT) { + return Short.class.getName(); + } + else if (kind == INT) { + return Integer.class.getName(); + } + else if (kind == LONG) { + return Long.class.getName(); + } + else if (kind == CHAR) { + return Character.class.getName(); + } + else if (kind == FLOAT) { + return Float.class.getName(); + } + else if (kind == DOUBLE) { + return Double.class.getName(); + } + } + return typeMirror.toString(); + } + + static String beanLowerCamel(String s) { + if (s.length() >= 2) { + final String firstTwo = s.substring(0, 2); + if (firstTwo.equals(firstTwo.toUpperCase())) { + return s; + } + } + return s.substring(0, 1).toLowerCase() + s.substring(1); + } + + static String beanUpperCamel(String s) { + if (s.length() >= 2) { + final String firstTwo = s.substring(0, 2); + if (firstTwo.equals(firstTwo.toUpperCase())) { + return s; + } + } + return s.substring(0, 1).toUpperCase() + s.substring(1); + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/ConstraintMetaTemplate.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/ConstraintMetaTemplate.java new file mode 100644 index 0000000..a623f2f --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/ConstraintMetaTemplate.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.processor; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.HashSet; +import java.util.Set; + +/** + * @since 0.4.0 + */ +class ConstraintMetaTemplate { + + private static final Set supportedTypes = new HashSet() { + { + add(BigDecimal.class.getName()); + add(BigInteger.class.getName()); + add(Boolean.class.getName()); + add(Byte.class.getName()); + add(Character.class.getName()); + add(Double.class.getName()); + add(Float.class.getName()); + add(Integer.class.getName()); + add(Long.class.getName()); + add(Short.class.getName()); + add(String.class.getName()); + add(LocalDate.class.getName()); + add(LocalTime.class.getName()); + add(LocalDateTime.class.getName()); + add(OffsetDateTime.class.getName()); + add(ZonedDateTime.class.getName()); + add(Instant.class.getName()); + } + }; + + static String template(String className, String type, String target, String method, + boolean useField) { + final String simpleType = simpleType(type); + final String metaType = "Object".equals(simpleType) ? className + ", " + type + : className; + return String.format("\n" + // + "\tpublic static final org.xbib.datastructures.validation.meta.%sConstraintMeta<%s> %s = new org.xbib.datastructures.validation.meta.%sConstraintMeta<%s>() {\n" + + "\n" + // + "\t\t@Override\n" + // + "\t\tpublic String name() {\n" + // + "\t\t\treturn \"%s\";\n" + // + "\t\t}\n" + // + "\n" + // + "\t\t@Override\n" // + + "\t\tpublic java.util.function.Function<%s, %s> toValue() {\n" // + + "\t\t\treturn %s;\n" + // + "\t\t}\n" + // + "\t}", simpleType, metaType, target.toUpperCase(), simpleType, metaType, + target, className, type, + useField ? "x -> x." + target : className + "::" + method); + } + + static String templateArgument(String className, String type, String target, + int position) { + final String simpleType = simpleType(type); + final String metaType = "Object".equals(simpleType) ? className + ", " + type + : className; + return String.format("\n" + // + "\tpublic static final org.xbib.datastructures.validation.meta.%sConstraintMeta<%s> %s = new org.xbib.datastructures.validation.meta.%sConstraintMeta<%s>() {\n" + + "\n" + // + "\t\t@Override\n" + // + "\t\tpublic String name() {\n" + // + "\t\t\treturn \"%s\";\n" + // + "\t\t}\n" + // + "\n" + // + "\t\t@Override\n" // + + "\t\tpublic java.util.function.Function<%s, %s> toValue() {\n" // + + "\t\t\treturn %s;\n" + // + "\t\t}\n" + // + "\t}", simpleType, metaType, target.toUpperCase(), simpleType, metaType, + target, className, type, + "org.xbib.datastructures.validation.arguments.Arguments" + position + "::arg" + position); + } + + private static String simpleType(String type) { + if (supportedTypes.contains(type)) { + final int lastDot = type.lastIndexOf('.'); + return type.substring(lastDot + 1); + } + else { + return "Object"; + } + } +} diff --git a/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/package-info.java b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/package-info.java new file mode 100644 index 0000000..5ff8ee6 --- /dev/null +++ b/datastructures-validation/src/main/java/org/xbib/datastructures/validation/processor/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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. + */ +@NonNullApi +package org.xbib.datastructures.validation.processor; + +import org.xbib.datastructures.validation.jsr305.NonNullApi; diff --git a/datastructures-validation/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/datastructures-validation/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..432197c --- /dev/null +++ b/datastructures-validation/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.xbib.datastructures.validation.processor.ConstraintMetaProcessor \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Address.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Address.java new file mode 100644 index 0000000..3bec3e0 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Address.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +public class Address { + private final Country country; + + private final PhoneNumber phoneNumber; + + private final String street; + + public Address(Country country, String street, PhoneNumber phoneNumber) { + this.country = country; + this.street = street; + this.phoneNumber = phoneNumber; + } + + public Country country() { + return this.country; + } + + public PhoneNumber phoneNumber() { + return this.phoneNumber; + } + + public String street() { + return this.street; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Book.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Book.java new file mode 100644 index 0000000..c9751f4 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Book.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +public class Book { + private final String isbn; + + public Book(String isbn) { + this.isbn = isbn; + } + + public String isbn() { + return this.isbn; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalDate.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalDate.java new file mode 100644 index 0000000..a392b70 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalDate.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import java.time.LocalDate; + +public class CalendarEntryLocalDate { + private String title; + private LocalDate date; + + public CalendarEntryLocalDate(String title, LocalDate timepoint) { + this.title = title; + this.date = timepoint; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalDateTime.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalDateTime.java new file mode 100644 index 0000000..31e9e56 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalDateTime.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import java.time.LocalDateTime; + +public class CalendarEntryLocalDateTime { + private String title; + private LocalDateTime dateTime; + + public CalendarEntryLocalDateTime(String title, LocalDateTime timepoint) { + this.title = title; + this.dateTime = timepoint; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public void setDateTime(LocalDateTime dateTime) { + this.dateTime = dateTime; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalTime.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalTime.java new file mode 100644 index 0000000..0a5eeec --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/CalendarEntryLocalTime.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import java.time.LocalTime; + +public class CalendarEntryLocalTime { + private String title; + private LocalTime time; + + public CalendarEntryLocalTime(String title, LocalTime timepoint) { + this.title = title; + this.time = timepoint; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public LocalTime getTime() { + return time; + } + + public void setTime(LocalTime time) { + this.time = time; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Color.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Color.java new file mode 100644 index 0000000..8a582a3 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Color.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +public enum Color { + RED, BLUE, GREEN +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/ConstraintViolationsException.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/ConstraintViolationsException.java new file mode 100644 index 0000000..2a55e6d --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/ConstraintViolationsException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import org.xbib.datastructures.validation.core.ConstraintViolations; + +public class ConstraintViolationsException extends RuntimeException { + private final ConstraintViolations violations; + + public ConstraintViolationsException(ConstraintViolations violations) { + this.violations = violations; + } + + public ConstraintViolations getViolations() { + return this.violations; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Country.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Country.java new file mode 100644 index 0000000..e087d66 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Country.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.core.ApplicativeValidator; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; + +public class Country { + private final String name; + + public Country(String name) { + this.name = name; + } + + static final ApplicativeValidator applicativeValidator = validator() + .prefixed("country").applicative(); + + public static Validator validator() { + return ValidatorBuilder. of() + .constraint(Country::name, "name", c -> c.notBlank() // + .greaterThanOrEqual(2)) + .build(); + } + + public String name() { + return this.name; + } + + public static Validated of(String name) { + return applicativeValidator.validate(new Country(name)); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithArray.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithArray.java new file mode 100644 index 0000000..c19b4cc --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithArray.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +public class FormWithArray { + private final Address[] addresses; + + public FormWithArray(Address[] addresses) { + this.addresses = addresses; + } + + public Address[] getAddresses() { + return addresses; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithCollection.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithCollection.java new file mode 100644 index 0000000..6147703 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithCollection.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import java.util.List; + +public class FormWithCollection { + private final List
addresses; + + public FormWithCollection(List
addresses) { + this.addresses = addresses; + } + + public List
getAddresses() { + return addresses; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithMap.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithMap.java new file mode 100644 index 0000000..b8300db --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/FormWithMap.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import java.util.Map; + +public class FormWithMap { + private final Map addresses; + + public FormWithMap(Map addresses) { + this.addresses = addresses; + } + + public Map getAddresses() { + return addresses; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Message.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Message.java new file mode 100644 index 0000000..5b55b60 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Message.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +public class Message { + private final String text; + private final Color color; + + public Message(String text, Color color) { + this.text = text; + this.color = color; + } + + public String getText() { + return text; + } + + public Color getColor() { + return color; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/NestedFormWithCollection.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/NestedFormWithCollection.java new file mode 100644 index 0000000..9d772e2 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/NestedFormWithCollection.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import java.util.List; + +public class NestedFormWithCollection { + private final List forms; + + public NestedFormWithCollection(List forms) { + this.forms = forms; + } + + public List getForms() { + return forms; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/PhoneNumber.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/PhoneNumber.java new file mode 100644 index 0000000..906c8c3 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/PhoneNumber.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.core.ApplicativeValidator; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.Validator; + +import java.util.Objects; + +public class PhoneNumber { + private final String value; + + static final ApplicativeValidator applicativeValidator = validator() + .prefixed("phoneNumber").applicative(); + + public PhoneNumber(String value) { + this.value = value; + } + + public static Validator validator() { + return ValidatorBuilder. of() + .constraint((PhoneNumber p) -> p.value, "value", + c -> c.notBlank().greaterThanOrEqual(8).lessThanOrEqual(16)) + .build(); + } + + public String value() { + return this.value; + } + + public static Validated of(String value) { + return applicativeValidator.validate(new PhoneNumber(value)); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + PhoneNumber that = (PhoneNumber) o; + return value.equals(that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Range.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Range.java new file mode 100644 index 0000000..70e1625 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/Range.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +public class Range { + private final int from; + private final int to; + + public Range(int from, int to) { + this.from = from; + this.to = to; + } + + public int getFrom() { + return from; + } + + public int getTo() { + return to; + } + + public boolean isToGreaterThanFrom() { + return this.to > this.from; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/User.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/User.java new file mode 100644 index 0000000..0809462 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/User.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation; + +public class User { + private Integer age; + + private String email; + + private boolean enabled = true; + + private String name; + + public User(String name, String email, Integer age) { + this.name = name; + this.email = email; + this.age = age; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ArgumentsValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ArgumentsValidatorTest.java new file mode 100644 index 0000000..301a29c --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ArgumentsValidatorTest.java @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.Range; +import org.xbib.datastructures.validation.User; +import org.xbib.datastructures.validation.builder.ArgumentsValidatorBuilder; +import org.xbib.datastructures.validation.builder.StringValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.xbib.datastructures.validation.core.ViolationMessage; +import org.junit.jupiter.api.Test; + +import static java.util.function.Function.identity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ArgumentsValidatorTest { + + final Arguments1Validator arguments1Validator = ArgumentsValidatorBuilder + .of(Country::new) // + .builder(b -> b // + ._string(Arguments1::arg1, "name", c -> c.greaterThanOrEqual(2))) + .build(); + + final Arguments2Validator arguments2Validator = ArgumentsValidatorBuilder + .of(Range::new) + .builder(b -> b + ._integer(Arguments1::arg1, "from", + c -> c.greaterThanOrEqual(0).lessThanOrEqual(9)) + ._integer(Arguments2::arg2, "to", + c -> c.greaterThanOrEqual(0).lessThanOrEqual(9)) + .constraintOnTarget(a -> a.arg1() < a.arg2(), "range", + ViolationMessage.of("to.isGreaterThanFrom", + "\"to\" must be greater than \"from\"."))) + .build(); + + final Arguments3Validator arguments3Validator = ArgumentsValidatorBuilder + .of(User::new) + .builder(b -> b + ._string(Arguments1::arg1, "name", + c -> c.notNull().greaterThanOrEqual(1).lessThanOrEqual(20)) + ._string(Arguments2::arg2, "email", + c -> c.notNull().greaterThanOrEqual(5).lessThanOrEqual(50) + .email()) + ._integer(Arguments3::arg3, "age", + c -> c.notNull().greaterThanOrEqual(0).lessThanOrEqual(200))) + .build(); + + final Arguments1Validator, User> mapValidator = arguments3Validator + .compose(m -> Arguments.of((String) m.get("name"), (String) m.get("email"), + (Integer) m.get("age"))); + + static final Arguments1Validator phoneNumberValidator = StringValidatorBuilder + .of("phoneNumber", + c -> c.notBlank().greaterThanOrEqual(8).lessThanOrEqual(16)) + .build(PhoneNumber::new).compose(identity()); + + @Test + void testArg2_allInvalid() { + assertThatThrownBy(() -> arguments2Validator.validated(-1, -3)) + .isInstanceOfSatisfying(ConstraintViolationsException.class, + e -> assertThat(e.getMessage()).isEqualTo( + "Constraint violations found!" + System.lineSeparator() + + "* \"from\" must be greater than or equal to 0" + + System.lineSeparator() + + "* \"to\" must be greater than or equal to 0" + + System.lineSeparator() + + "* \"to\" must be greater than \"from\".")); + } + + @Test + void testArg2_valid() { + final Range range = arguments2Validator.validated(3, 5); + assertThat(range.getFrom()).isEqualTo(3); + assertThat(range.getTo()).isEqualTo(5); + } + + @Test + void testArg3_allInvalid() { + assertThatThrownBy(() -> arguments3Validator.validated("", "example.com", 300)) + .isInstanceOfSatisfying(ConstraintViolationsException.class, e -> { + final ConstraintViolations violations = e.violations(); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).message()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(violations.get(1).messageKey()) + .isEqualTo("charSequence.email"); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" must be less than or equal to 200"); + assertThat(violations.get(2).messageKey()) + .isEqualTo("numeric.lessThanOrEqual"); + }); + } + + @Test + void testArg3_either_allInvalid() { + final Validated either = arguments3Validator.validate("", "example.com", + 300); + assertThat(either.isValid()).isFalse(); + final ConstraintViolations violations = either.errors(); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).message()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.email"); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" must be less than or equal to 200"); + assertThat(violations.get(2).messageKey()).isEqualTo("numeric.lessThanOrEqual"); + } + + @Test + void testArg3_either_valid() { + final Validated either = arguments3Validator.validate("foo", + "foo@example.com", 30); + assertThat(either.isValid()).isTrue(); + final User user = either.value(); + assertThat(user.getName()).isEqualTo("foo"); + assertThat(user.getEmail()).isEqualTo("foo@example.com"); + assertThat(user.getAge()).isEqualTo(30); + } + + @Test + void testArg3_valid() { + final User user = arguments3Validator.validated("foo", "foo@example.com", 30); + assertThat(user.getName()).isEqualTo("foo"); + assertThat(user.getEmail()).isEqualTo("foo@example.com"); + assertThat(user.getAge()).isEqualTo(30); + } + + @Test + void testArg_invalid() { + assertThatThrownBy(() -> arguments1Validator.validated("J")) + .isInstanceOfSatisfying(ConstraintViolationsException.class, + e -> assertThat(e.getMessage()).isEqualTo( + "Constraint violations found!" + System.lineSeparator() + + "* The size of \"name\" must be greater than or equal to 2. The given size is 1")); + } + + @Test + void testArg_valid() { + final Country country = arguments1Validator.validated("JP"); + assertThat(country.name()).isEqualTo("JP"); + } + + @Test + void testValidateOnly_valid() { + final Product product = new Product("foo", 100); + assertThat(product).isNotNull(); + } + + @Test + void testValidateOnly_invalid() { + assertThatThrownBy(() -> new Product("", 0)) // + .isInstanceOfSatisfying(ConstraintViolationsException.class, + e -> assertThat(e.getMessage()).isEqualTo( + "Constraint violations found!" + System.lineSeparator() + + "* \"name\" must not be empty" + + System.lineSeparator() + + "* \"price\" must be greater than 0")); + } + + @Test + void contramap_allInvalid() { + final Map map = new HashMap<>(); + map.put("name", ""); + map.put("email", "example.com"); + map.put("age", 300); + final Validated validated = mapValidator.validate(map); + assertThat(validated.isValid()).isFalse(); + final ConstraintViolations violations = validated.errors(); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).message()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.email"); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" must be less than or equal to 200"); + assertThat(violations.get(2).messageKey()).isEqualTo("numeric.lessThanOrEqual"); + } + + @Test + void contramap_valid() { + final Map map = new HashMap<>(); + map.put("name", "foo"); + map.put("email", "foo@example.com"); + map.put("age", 30); + final Validated validated = mapValidator.validate(map); + assertThat(validated.isValid()).isTrue(); + final User user = validated.value(); + assertThat(user.getName()).isEqualTo("foo"); + assertThat(user.getEmail()).isEqualTo("foo@example.com"); + assertThat(user.getAge()).isEqualTo(30); + } + + @Test + void fromValidatorSubset_valid() { + final Arguments1Validator countryValidator = Arguments1Validator + .from(Country.validator().prefixed("country")); + final Validated countryValidated = countryValidator + .validate(new Country("JP")); + assertThat(countryValidated.isValid()).isTrue(); + assertThat(countryValidated.value().name()).isEqualTo("JP"); + } + + @Test + void fromValidatorSubset_invalid() { + final Arguments1Validator countryValidator = Arguments1Validator + .from(Country.validator().prefixed("country")); + final Validated countryValidated = countryValidator + .validate(new Country("J")); + assertThat(countryValidated.isValid()).isFalse(); + final ConstraintViolations violations = countryValidated.errors(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(0).name()).isEqualTo("country.name"); + } + + @Test + void fromValueValidator_valid() { + final Arguments1Validator countryValidator = Arguments1Validator + .from(Country.validator().prefixed("country").applicative()); + final Validated countryValidated = countryValidator + .validate(new Country("JP")); + assertThat(countryValidated.isValid()).isTrue(); + assertThat(countryValidated.value().name()).isEqualTo("JP"); + } + + @Test + void fromValueValidator_invalid() { + final Arguments1Validator countryValidator = Arguments1Validator + .from(Country.validator().prefixed("country").applicative()); + final Validated countryValidated = countryValidator + .validate(new Country("J")); + assertThat(countryValidated.isValid()).isFalse(); + final ConstraintViolations violations = countryValidated.errors(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(0).name()).isEqualTo("country.name"); + } + + @Test + void liftListValid() { + Arguments1Validator, List> phoneNumberListValidator = phoneNumberValidator + .liftList(); + List input = Arrays.asList("012012345678", "012012348765", + "012012345679"); + Validated> actual = phoneNumberListValidator.validate(input); + + assertThat(actual.isValid()).isTrue(); + assertThat(actual.value()).isEqualTo(Arrays.asList( + new PhoneNumber("012012345678"), new PhoneNumber("012012348765"), + new PhoneNumber("012012345679"))); + } + + @Test + void liftListInvalid() { + Arguments1Validator, List> phoneNumberListValidator = phoneNumberValidator + .liftList(); + List input = Arrays.asList("012012345678", "", "012"); + Validated> actual = phoneNumberListValidator.validate(input); + + assertThat(actual.isValid()).isFalse(); + assertThat(actual.errors()).hasSize(3); + assertThat(actual.errors().get(0).name()).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(0).messageKey()) + .isEqualTo("charSequence.notBlank"); + assertThat(actual.errors().get(0).args()[0]).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(1).name()).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(1).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(1).args()[0]).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(2).name()).isEqualTo("phoneNumber[2]"); + assertThat(actual.errors().get(2).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(2).args()[0]).isEqualTo("phoneNumber[2]"); + } + + @Test + void liftSetValid() { + Arguments1Validator, Set> phoneNumberSetValidator = phoneNumberValidator + .liftSet(); + List input = Arrays.asList("012012345678", "012012348765", "012012345679", + "012012345678"); + Validated> actual = phoneNumberSetValidator.validate(input); + + assertThat(actual.isValid()).isTrue(); + assertThat(new ArrayList<>(actual.value())).isEqualTo(Arrays.asList( + new PhoneNumber("012012345678"), new PhoneNumber("012012348765"), + new PhoneNumber("012012345679"))); + } + + @Test + void liftSetInvalid() { + Arguments1Validator, Set> phoneNumberSetValidator = phoneNumberValidator + .liftSet(); + List input = Arrays.asList("012012345678", "", "012", "012012345678"); + Validated> actual = phoneNumberSetValidator.validate(input); + + assertThat(actual.isValid()).isFalse(); + assertThat(actual.errors()).hasSize(3); + assertThat(actual.errors().get(0).name()).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(0).messageKey()) + .isEqualTo("charSequence.notBlank"); + assertThat(actual.errors().get(0).args()[0]).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(1).name()).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(1).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(1).args()[0]).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(2).name()).isEqualTo("phoneNumber[2]"); + assertThat(actual.errors().get(2).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(2).args()[0]).isEqualTo("phoneNumber[2]"); + } + + @Test + void liftOptionalValid() { + Arguments1Validator, Optional> phoneNumberOptionalValidator = phoneNumberValidator + .liftOptional(); + + Validated> actual = phoneNumberOptionalValidator + .validate(Optional.of("012012345678")); + assertThat(actual.isValid()).isTrue(); + assertThat(actual.value()) + .isEqualTo(Optional.of(new PhoneNumber("012012345678"))); + + Validated> actual2 = phoneNumberOptionalValidator + .validate(Optional.empty()); + assertThat(actual2.isValid()).isTrue(); + assertThat(actual2.value()).isEqualTo(Optional.empty()); + } + + @Test + void liftOptionalInvalid() { + Arguments1Validator, Optional> phoneNumberOptionalValidator = phoneNumberValidator + .liftOptional(); + + Validated> actual = phoneNumberOptionalValidator + .validate(Optional.of("")); + assertThat(actual.isValid()).isFalse(); + assertThat(actual.errors()).hasSize(2); + assertThat(actual.errors().get(0).name()).isEqualTo("phoneNumber"); + assertThat(actual.errors().get(0).messageKey()) + .isEqualTo("charSequence.notBlank"); + assertThat(actual.errors().get(0).args()[0]).isEqualTo("phoneNumber"); + assertThat(actual.errors().get(1).name()).isEqualTo("phoneNumber"); + assertThat(actual.errors().get(1).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(1).args()[0]).isEqualTo("phoneNumber"); + } + + @Test + void nullArgValid() { + final Arguments1Validator validator = ArgumentsValidatorBuilder + .of(Country::new).builder(b -> b._string(Arguments1::arg1, "country", + c -> c.greaterThan(1))) + .build(); + final Validated validated = validator.validate(null); + assertThat(validated.isValid()).isTrue(); + assertThat(validated.value()).isNotNull(); + assertThat(validated.value().name()).isNull(); + } + + @Test + void nullArgInValid() { + final Arguments1Validator validator = ArgumentsValidatorBuilder + .of(Country::new).builder(b -> b._string(Arguments1::arg1, "country", + c -> c.notNull().greaterThan(1))) + .build(); + final Validated validated = validator.validate(null); + assertThat(validated.isValid()).isFalse(); + assertThat(validated.errors()).hasSize(1); + assertThat(validated.errors().get(0).name()).isEqualTo("country"); + assertThat(validated.errors().get(0).messageKey()).isEqualTo("object.notNull"); + assertThat(validated.errors().get(0).args()[0]).isEqualTo("country"); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ArgumentsValidatorsTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ArgumentsValidatorsTest.java new file mode 100644 index 0000000..3108a86 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ArgumentsValidatorsTest.java @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.StringValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class ArgumentsValidatorsTest { + static final StringValidator countryValidator = StringValidatorBuilder + .of("country", c -> c.notBlank().greaterThanOrEqual(2)).build(Country::new); + + static final StringValidator streetValidator = StringValidatorBuilder + .of("street", c -> c.notBlank()).build(); + + static final StringValidator phoneNumberValidator = StringValidatorBuilder + .of("phoneNumber", + c -> c.notBlank().greaterThanOrEqual(8).lessThanOrEqual(16)) + .build(PhoneNumber::new); + + static final Arguments1Validator, Country> mapCountryValidator = countryValidator + .compose(map -> map.get("country")); + + static final Arguments1Validator, String> mapStreetValidator = streetValidator + .compose(map -> map.get("street")); + + static final Arguments1Validator, PhoneNumber> mapPhoneNumberValidator = phoneNumberValidator + .compose(map -> map.get("phoneNumber")); + + static final StringValidator v1 = StringValidatorBuilder + .of("s1", c -> c.notBlank()).build(); + + static final StringValidator v2 = StringValidatorBuilder + .of("s2", c -> c.notBlank()).build(); + + static final StringValidator v3 = StringValidatorBuilder + .of("s3", c -> c.notBlank()).build(); + + static final StringValidator v4 = StringValidatorBuilder + .of("s4", c -> c.notBlank()).build(); + + static final StringValidator v5 = StringValidatorBuilder + .of("s5", c -> c.notBlank()).build(); + + static final StringValidator v6 = StringValidatorBuilder + .of("s6", c -> c.notBlank()).build(); + + static final StringValidator v7 = StringValidatorBuilder + .of("s7", c -> c.notBlank()).build(); + + static final StringValidator v8 = StringValidatorBuilder + .of("s8", c -> c.notBlank()).build(); + + static final StringValidator v9 = StringValidatorBuilder + .of("s9", c -> c.notBlank()).build(); + + static final StringValidator v10 = StringValidatorBuilder + .of("s10", c -> c.notBlank()).build(); + + static final Arguments10Validator> arguments10Validator = v1 + .split(v2).split(v3).split(v4).split(v5).split(v6).split(v7).split(v8) + .split(v9).split(v10).apply(Arrays::asList); + + final Arguments1Validator, String> mapV1 = v1 + .compose(map -> map.get("s1")); + + final Arguments1Validator, String> mapV2 = v2 + .compose(map -> map.get("s2")); + + final Arguments1Validator, String> mapV3 = v3 + .compose(map -> map.get("s3")); + + final Arguments1Validator, String> mapV4 = v4 + .compose(map -> map.get("s4")); + + final Arguments1Validator, String> mapV5 = v5 + .compose(map -> map.get("s5")); + + final Arguments1Validator, String> mapV6 = v6 + .compose(map -> map.get("s6")); + + final Arguments1Validator, String> mapV7 = v7 + .compose(map -> map.get("s7")); + + final Arguments1Validator, String> mapV8 = v8 + .compose(map -> map.get("s8")); + + final Arguments1Validator, String> mapV9 = v9 + .compose(map -> map.get("s9")); + + final Arguments1Validator, String> mapV10 = v10 + .compose(map -> map.get("s10")); + + final Arguments1Validator, List> map10V = mapV1 + .combine(mapV2).combine(mapV3).combine(mapV4).combine(mapV5).combine(mapV6) + .combine(mapV7).combine(mapV8).combine(mapV9).combine(mapV10) + .apply(Arrays::asList); + + @ParameterizedTest + @MethodSource("splitValidators") + void splitValid(Arguments3Validator validator) { + + final Validated
validated = validator.validate("JP", "XYZ Avenue", + "123-456-789"); + assertThat(validated.isValid()).isTrue(); + final Address address = validated.value(); + assertThat(address.country().name()).isEqualTo("JP"); + assertThat(address.street()).isEqualTo("XYZ Avenue"); + assertThat(address.phoneNumber().value()).isEqualTo("123-456-789"); + } + + @ParameterizedTest + @MethodSource("splitValidators") + void splitInvalid(Arguments3Validator validator) { + final Validated
validated = validator.validate("J", " ", "1234567"); + assertThat(validated.isValid()).isFalse(); + final ConstraintViolations violations = validated.errors(); + assertThat(violations).hasSize(3); + assertThat(violations.get(0).name()).isEqualTo("country"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).name()).isEqualTo("street"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).name()).isEqualTo("phoneNumber"); + assertThat(violations.get(2).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void split10Valid() { + final Validated> validated = arguments10Validator.validate("1", "2", + "3", "4", "5", "6", "7", "8", "9", "10"); + assertThat(validated.isValid()).isTrue(); + final List list = validated.value(); + assertThat(list).containsExactly("1", "2", "3", "4", "5", "6", "7", "8", "9", + "10"); + } + + @Test + void split10Invalid() { + final Validated> validated = arguments10Validator.validate(" ", " ", + " ", " ", " ", " ", " ", " ", " ", " "); + assertThat(validated.isValid()).isFalse(); + final ConstraintViolations violations = validated.errors(); + assertThat(violations).hasSize(10); + for (int i = 0; i < 10; i++) { + assertThat(violations.get(i).name()).isEqualTo("s" + (i + 1)); + assertThat(violations.get(i).messageKey()).isEqualTo("charSequence.notBlank"); + } + } + + @ParameterizedTest + @MethodSource("combineValidators") + void combineValid(Arguments1Validator, Address> validator) { + final Validated
validated = validator + .validate(new HashMap() { + { + put("country", "JP"); + put("street", "XYZ Avenue"); + put("phoneNumber", "123-456-789"); + } + }); + assertThat(validated.isValid()).isTrue(); + final Address address = validated.value(); + assertThat(address.country().name()).isEqualTo("JP"); + assertThat(address.street()).isEqualTo("XYZ Avenue"); + assertThat(address.phoneNumber().value()).isEqualTo("123-456-789"); + } + + @ParameterizedTest + @MethodSource("combineValidators") + void combineInvalid(Arguments1Validator, Address> validator) { + final Validated
validated = validator + .validate(new HashMap() { + { + put("country", "J"); + put("street", " "); + put("phoneNumber", "1234567"); + } + }); + assertThat(validated.isValid()).isFalse(); + final ConstraintViolations violations = validated.errors(); + assertThat(violations).hasSize(3); + assertThat(violations.get(0).name()).isEqualTo("country"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).name()).isEqualTo("street"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).name()).isEqualTo("phoneNumber"); + assertThat(violations.get(2).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void combine10Valid() { + final Validated> validated = map10V + .validate(new HashMap() { + { + for (int i = 1; i <= 10; i++) { + put("s" + i, String.valueOf(i)); + } + } + }); + assertThat(validated.isValid()).isTrue(); + final List list = validated.value(); + assertThat(list).containsExactly("1", "2", "3", "4", "5", "6", "7", "8", "9", + "10"); + } + + @Test + void combine10Invalid() { + final Validated> validated = map10V + .validate(new HashMap() { + { + for (int i = 1; i <= 10; i++) { + put("s" + i, " "); + } + } + }); + assertThat(validated.isValid()).isFalse(); + final ConstraintViolations violations = validated.errors(); + assertThat(violations).hasSize(10); + for (int i = 0; i < 10; i++) { + assertThat(violations.get(i).name()).isEqualTo("s" + (i + 1)); + assertThat(violations.get(i).messageKey()).isEqualTo("charSequence.notBlank"); + } + } + + static Stream> splitValidators() { + return Stream.of( + ArgumentsValidators + .split(countryValidator, streetValidator, phoneNumberValidator) + .apply(Address::new), + countryValidator.split(streetValidator).split(phoneNumberValidator) + .apply(Address::new)); + } + + static Stream, Address>> combineValidators() { + return Stream.of( + ArgumentsValidators.combine(mapCountryValidator, mapStreetValidator, + mapPhoneNumberValidator).apply(Address::new), + mapCountryValidator.combine(mapStreetValidator) + .combine(mapPhoneNumberValidator).apply(Address::new)); + } + + @Test + void sequence1Valid() { + final Arguments1Validator, List> sequenced = ArgumentsValidators + .sequence1(Arrays.asList(mapV1, mapV2, mapV3, mapV4, mapV5, mapV6, mapV7, + mapV8, mapV9, mapV10)); + + final Validated> validated = sequenced + .validate(new HashMap() { + { + for (int i = 1; i <= 10; i++) { + put("s" + i, String.valueOf(i)); + } + } + }); + assertThat(validated.isValid()).isTrue(); + final List list = validated.value(); + assertThat(list).containsExactly("1", "2", "3", "4", "5", "6", "7", "8", "9", + "10"); + } + + @Test + void sequence1Invalid() { + final Arguments1Validator, List> sequenced = ArgumentsValidators + .sequence1(Arrays.asList(mapV1, mapV2, mapV3, mapV4, mapV5, mapV6, mapV7, + mapV8, mapV9, mapV10)); + + final Validated> validated = sequenced + .validate(new HashMap() { + { + for (int i = 1; i <= 10; i++) { + put("s" + i, " "); + } + } + }); + assertThat(validated.isValid()).isFalse(); + final ConstraintViolations violations = validated.errors(); + assertThat(violations).hasSize(10); + for (int i = 0; i < 10; i++) { + assertThat(violations.get(i).name()).isEqualTo("s" + (i + 1)); + assertThat(violations.get(i).messageKey()).isEqualTo("charSequence.notBlank"); + } + } + + static Stream, List>> phoneNumberListValidators() { + return Stream.of(ArgumentsValidators.liftList(phoneNumberValidator), + phoneNumberValidator.liftList()); + } + + @ParameterizedTest + @MethodSource("phoneNumberListValidators") + void liftListValid( + Arguments1Validator, List> phoneNumberListValidator) { + List input = Arrays.asList("012012345678", "012012348765", + "012012345679"); + Validated> actual = phoneNumberListValidator.validate(input); + + assertThat(actual.isValid()).isTrue(); + assertThat(actual.value()).isEqualTo(Arrays.asList( + new PhoneNumber("012012345678"), new PhoneNumber("012012348765"), + new PhoneNumber("012012345679"))); + } + + @ParameterizedTest + @MethodSource("phoneNumberListValidators") + void liftListInvalid( + Arguments1Validator, List> phoneNumberListValidator) { + List input = Arrays.asList("012012345678", "", "012"); + Validated> actual = phoneNumberListValidator.validate(input); + + assertThat(actual.isValid()).isFalse(); + assertThat(actual.errors()).hasSize(3); + assertThat(actual.errors().get(0).name()).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(0).messageKey()) + .isEqualTo("charSequence.notBlank"); + assertThat(actual.errors().get(0).args()[0]).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(1).name()).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(1).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(1).args()[0]).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(2).name()).isEqualTo("phoneNumber[2]"); + assertThat(actual.errors().get(2).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(2).args()[0]).isEqualTo("phoneNumber[2]"); + } + + static Stream, Set>> phoneNumberSetValidators() { + return Stream.of(ArgumentsValidators.liftSet(phoneNumberValidator), + phoneNumberValidator.liftSet()); + } + + @ParameterizedTest + @MethodSource("phoneNumberSetValidators") + void liftSetValid( + Arguments1Validator, Set> phoneNumberSetValidator) { + List input = Arrays.asList("012012345678", "012012348765", "012012345679", + "012012345678"); + Validated> actual = phoneNumberSetValidator.validate(input); + + assertThat(actual.isValid()).isTrue(); + assertThat(new ArrayList<>(actual.value())).isEqualTo(Arrays.asList( + new PhoneNumber("012012345678"), new PhoneNumber("012012348765"), + new PhoneNumber("012012345679"))); + } + + @ParameterizedTest + @MethodSource("phoneNumberSetValidators") + void liftSetInvalid( + Arguments1Validator, Set> phoneNumberSetValidator) { + List input = Arrays.asList("012012345678", "", "012", "012012345678"); + Validated> actual = phoneNumberSetValidator.validate(input); + + assertThat(actual.isValid()).isFalse(); + assertThat(actual.errors()).hasSize(3); + assertThat(actual.errors().get(0).name()).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(0).messageKey()) + .isEqualTo("charSequence.notBlank"); + assertThat(actual.errors().get(0).args()[0]).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(1).name()).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(1).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(1).args()[0]).isEqualTo("phoneNumber[1]"); + assertThat(actual.errors().get(2).name()).isEqualTo("phoneNumber[2]"); + assertThat(actual.errors().get(2).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(2).args()[0]).isEqualTo("phoneNumber[2]"); + } + + static Stream, Optional>> phoneNumberOptionalValidators() { + return Stream.of(ArgumentsValidators.liftOptional(phoneNumberValidator), + phoneNumberValidator.liftOptional()); + } + + @ParameterizedTest + @MethodSource("phoneNumberOptionalValidators") + void liftOptionalValid( + Arguments1Validator, Optional> phoneNumberOptionalValidator) { + Validated> actual = phoneNumberOptionalValidator + .validate(Optional.of("012012345678")); + assertThat(actual.isValid()).isTrue(); + assertThat(actual.value()) + .isEqualTo(Optional.of(new PhoneNumber("012012345678"))); + + Validated> actual2 = phoneNumberOptionalValidator + .validate(Optional.empty()); + assertThat(actual2.isValid()).isTrue(); + assertThat(actual2.value()).isEqualTo(Optional.empty()); + } + + @ParameterizedTest + @MethodSource("phoneNumberOptionalValidators") + void liftOptionalInvalid( + Arguments1Validator, Optional> phoneNumberOptionalValidator) { + Validated> actual = phoneNumberOptionalValidator + .validate(Optional.of("")); + assertThat(actual.isValid()).isFalse(); + assertThat(actual.errors()).hasSize(2); + assertThat(actual.errors().get(0).name()).isEqualTo("phoneNumber"); + assertThat(actual.errors().get(0).messageKey()) + .isEqualTo("charSequence.notBlank"); + assertThat(actual.errors().get(0).args()[0]).isEqualTo("phoneNumber"); + assertThat(actual.errors().get(1).name()).isEqualTo("phoneNumber"); + assertThat(actual.errors().get(1).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(actual.errors().get(1).args()[0]).isEqualTo("phoneNumber"); + } + +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BigDecimalValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BigDecimalValidatorTest.java new file mode 100644 index 0000000..bcfdb60 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BigDecimalValidatorTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.BigDecimalValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BigDecimalValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(BigDecimalValidator priceValidator) { + final Validated priceValidated = priceValidator + .validate(BigDecimal.valueOf(100)); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(BigDecimal.valueOf(100)); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(BigDecimalValidator priceValidator) { + final Validated priceValidated = priceValidator + .validate(BigDecimal.valueOf(-1)); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(BigDecimalValidator priceValidator) { + final Price price = priceValidator.validated(BigDecimal.valueOf(100)); + assertThat(price.value()).isEqualTo(BigDecimal.valueOf(100)); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(BigDecimalValidator priceValidator) { + assertThatThrownBy(() -> priceValidator.validated(BigDecimal.valueOf(-1))) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"price\" must be greater than or equal to 0"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(BigDecimalValidator priceValidator) { + final Map params = Collections.singletonMap("price", + BigDecimal.valueOf(100)); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(BigDecimal.valueOf(100)); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(BigDecimalValidator priceValidator) { + final Map params = Collections.singletonMap("price", + BigDecimal.valueOf(-1)); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + static Stream> validators() { + return Stream.of( + BigDecimalValidatorBuilder + .of("price", + c -> c.notNull() + .greaterThanOrEqual(BigDecimal.valueOf(0))) + .build(Price::new), + BigDecimalValidatorBuilder + .of("price", + c -> c.notNull() + .greaterThanOrEqual(BigDecimal.valueOf(0))) + .build().andThen(Price::new)); + } + + public static class Price { + private final BigDecimal value; + + public Price(BigDecimal value) { + this.value = value; + } + + public BigDecimal value() { + return value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BigIntegerValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BigIntegerValidatorTest.java new file mode 100644 index 0000000..9cfff8d --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BigIntegerValidatorTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.BigIntegerValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BigIntegerValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(BigIntegerValidator priceValidator) { + final Validated priceValidated = priceValidator + .validate(BigInteger.valueOf(100)); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(BigInteger.valueOf(100)); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(BigIntegerValidator priceValidator) { + final Validated priceValidated = priceValidator + .validate(BigInteger.valueOf(-1)); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(BigIntegerValidator priceValidator) { + final Price price = priceValidator.validated(BigInteger.valueOf(100)); + assertThat(price.value()).isEqualTo(BigInteger.valueOf(100)); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(BigIntegerValidator priceValidator) { + assertThatThrownBy(() -> priceValidator.validated(BigInteger.valueOf(-1))) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"price\" must be greater than or equal to 0"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(BigIntegerValidator priceValidator) { + final Map params = Collections.singletonMap("price", + BigInteger.valueOf(100)); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(BigInteger.valueOf(100)); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(BigIntegerValidator priceValidator) { + final Map params = Collections.singletonMap("price", + BigInteger.valueOf(-1)); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + static Stream> validators() { + return Stream.of( + BigIntegerValidatorBuilder + .of("price", + c -> c.notNull() + .greaterThanOrEqual(BigInteger.valueOf(0))) + .build(Price::new), + BigIntegerValidatorBuilder + .of("price", + c -> c.notNull() + .greaterThanOrEqual(BigInteger.valueOf(0))) + .build().andThen(Price::new)); + } + + public static class Price { + private final BigInteger value; + + public Price(BigInteger value) { + this.value = value; + } + + public BigInteger value() { + return value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BooleanValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BooleanValidatorTest.java new file mode 100644 index 0000000..77a3fa7 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/BooleanValidatorTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.BooleanValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BooleanValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(BooleanValidator checkedValidator) { + final Validated checkedValidated = checkedValidator.validate(true); + assertThat(checkedValidated.isValid()).isTrue(); + assertThat(checkedValidated.value().isChecked()).isTrue(); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(BooleanValidator checkedValidator) { + final Validated checkedValidated = checkedValidator.validate(false); + assertThat(checkedValidated.isValid()).isFalse(); + final ConstraintViolations violations = checkedValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("checked"); + assertThat(violations.get(0).messageKey()).isEqualTo("boolean.isTrue"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(BooleanValidator checkedValidator) { + final Checked checked = checkedValidator.validated(true); + assertThat(checked.isChecked()).isTrue(); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(BooleanValidator checkedValidator) { + assertThatThrownBy(() -> checkedValidator.validated(false)) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"checked\" must be true"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(BooleanValidator checkedValidator) { + final Map params = Collections.singletonMap("checked", true); + final Arguments1Validator, Checked> mapValidator = checkedValidator + .compose(map -> map.get("checked")); + final Validated checkedValidated = mapValidator.validate(params); + assertThat(checkedValidated.isValid()).isTrue(); + assertThat(checkedValidated.value().isChecked()).isTrue(); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(BooleanValidator checkedValidator) { + final Map params = Collections.singletonMap("checked", false); + final Arguments1Validator, Checked> mapValidator = checkedValidator + .compose(map -> map.get("checked")); + final Validated checkedValidated = mapValidator.validate(params); + assertThat(checkedValidated.isValid()).isFalse(); + final ConstraintViolations violations = checkedValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("checked"); + assertThat(violations.get(0).messageKey()).isEqualTo("boolean.isTrue"); + } + + static Stream> validators() { + return Stream.of( + BooleanValidatorBuilder.of("checked", c -> c.notNull().isTrue()) + .build(Checked::new), + BooleanValidatorBuilder.of("checked", c -> c.notNull().isTrue()).build() + .andThen(Checked::new)); + } + + public static class Checked { + private final boolean checked; + + public Checked(boolean checked) { + this.checked = checked; + } + + public boolean isChecked() { + return checked; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/DoubleValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/DoubleValidatorTest.java new file mode 100644 index 0000000..5ceba02 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/DoubleValidatorTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.DoubleValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DoubleValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(DoubleValidator priceValidator) { + final Validated priceValidated = priceValidator.validate((double) 100); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(DoubleValidator priceValidator) { + final Validated priceValidated = priceValidator.validate((double) -1); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(DoubleValidator priceValidator) { + final Price price = priceValidator.validated((double) 100); + assertThat(price.value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(DoubleValidator priceValidator) { + assertThatThrownBy(() -> priceValidator.validated((double) -1)) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"price\" must be greater than or equal to 0"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(DoubleValidator priceValidator) { + final Map params = Collections.singletonMap("price", + (double) 100); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(DoubleValidator priceValidator) { + final Map params = Collections.singletonMap("price", (double) -1); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + static Stream> validators() { + return Stream.of( + DoubleValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual((double) 0)) + .build(Price::new), + DoubleValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual((double) 0)) + .build().andThen(Price::new)); + } + + public static class Price { + private final double value; + + public Price(double value) { + this.value = value; + } + + public double value() { + return value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/FloatValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/FloatValidatorTest.java new file mode 100644 index 0000000..9487154 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/FloatValidatorTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.FloatValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class FloatValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(FloatValidator priceValidator) { + final Validated priceValidated = priceValidator.validate((float) 100); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo((float) 100); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(FloatValidator priceValidator) { + final Validated priceValidated = priceValidator.validate((float) -1); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(FloatValidator priceValidator) { + final Price price = priceValidator.validated((float) 100); + assertThat(price.value()).isEqualTo((float) 100); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(FloatValidator priceValidator) { + assertThatThrownBy(() -> priceValidator.validated((float) -1)) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"price\" must be greater than or equal to 0"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(FloatValidator priceValidator) { + final Map params = Collections.singletonMap("price", (float) 100); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo((float) 100); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(FloatValidator priceValidator) { + final Map params = Collections.singletonMap("price", (float) -1); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + static Stream> validators() { + return Stream.of( + FloatValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual((float) 0)) + .build(Price::new), + FloatValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual((float) 0)) + .build().andThen(Price::new)); + } + + public static class Price { + private final float value; + + public Price(float value) { + this.value = value; + } + + public float value() { + return value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/Gh156Test.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/Gh156Test.java new file mode 100644 index 0000000..cc49300 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/Gh156Test.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.builder.StringValidatorBuilder; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.jsr305.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; + +public class Gh156Test { + static class Person { + private final String firstName; + + private final String middleName; + + private final String lastName; + + public Person(String firstName, @Nullable String middleName, String lastName) { + this.firstName = firstName; + this.middleName = middleName; + this.lastName = lastName; + } + + @Override + public String toString() { + if (this.middleName == null) { + return String.format("%s %s", this.firstName, this.lastName); + } + return String.format("%s %s %s", this.firstName, this.middleName, + this.lastName); + } + } + + final StringValidator firstNameValidator = StringValidatorBuilder + .of("firstName", c -> c.notBlank().lessThanOrEqual(128)).build(); + + final StringValidator middleNameValidator = StringValidatorBuilder + .of("middleName", c -> c.lessThanOrEqual(128)).build(); + + final StringValidator lastNameValidator = StringValidatorBuilder + .of("lastName", c -> c.notBlank().lessThanOrEqual(128)).build(); + + final Arguments3Validator personValidator = ArgumentsValidators + .split(firstNameValidator, middleNameValidator, lastNameValidator) + .apply(Person::new); + + @Test + void allNonNull() { + final Validated validated = personValidator.validate("John", "Michael", + "Doe"); + assertThat(validated.isValid()).isTrue(); + assertThat(validated.value().toString()).isEqualTo("John Michael Doe"); + } + + @Test + void nullable() { + final Validated validated = personValidator.validate("John", null, "Doe"); + assertThat(validated.isValid()).isTrue(); + assertThat(validated.value().toString()).isEqualTo("John Doe"); + } + + @Test + void allNull() { + final Validated validated = personValidator.validate(null, null, null); + assertThat(validated.isValid()).isFalse(); + assertThat(validated.errors()).hasSize(2); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/IntegerValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/IntegerValidatorTest.java new file mode 100644 index 0000000..2c1e41d --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/IntegerValidatorTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.IntegerValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class IntegerValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(IntegerValidator priceValidator) { + final Validated priceValidated = priceValidator.validate(100); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(IntegerValidator priceValidator) { + final Validated priceValidated = priceValidator.validate(-1); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(IntegerValidator priceValidator) { + final Price price = priceValidator.validated(100); + assertThat(price.value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(IntegerValidator priceValidator) { + assertThatThrownBy(() -> priceValidator.validated(-1)) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"price\" must be greater than or equal to 0"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(IntegerValidator priceValidator) { + final Map params = Collections.singletonMap("price", 100); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(IntegerValidator priceValidator) { + final Map params = Collections.singletonMap("price", -1); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + static Stream> validators() { + return Stream.of(IntegerValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual(0)).build(Price::new), + IntegerValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual(0)).build() + .andThen(Price::new)); + } + + public static class Price { + private final int value; + + public Price(int value) { + this.value = value; + } + + public int value() { + return value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/LongValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/LongValidatorTest.java new file mode 100644 index 0000000..13a3582 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/LongValidatorTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.LongValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LongValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(LongValidator priceValidator) { + final Validated priceValidated = priceValidator.validate((long) 100); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(LongValidator priceValidator) { + final Validated priceValidated = priceValidator.validate((long) -1); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(LongValidator priceValidator) { + final Price price = priceValidator.validated((long) 100); + assertThat(price.value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(LongValidator priceValidator) { + assertThatThrownBy(() -> priceValidator.validated((long) -1)) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"price\" must be greater than or equal to 0"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(LongValidator priceValidator) { + final Map params = Collections.singletonMap("price", (long) 100); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo(100); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(LongValidator priceValidator) { + final Map params = Collections.singletonMap("price", (long) -1); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + static Stream> validators() { + return Stream.of( + LongValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual((long) 0)) + .build(Price::new), + LongValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual((long) 0)) + .build().andThen(Price::new)); + } + + public static class Price { + private final long value; + + public Price(long value) { + this.value = value; + } + + public long value() { + return value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/MethodInvocationTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/MethodInvocationTest.java new file mode 100644 index 0000000..c69d9a9 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/MethodInvocationTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.xbib.datastructures.validation.builder.ArgumentsValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; + +public class MethodInvocationTest { + static final Arguments3Validator validator = ArgumentsValidatorBuilder + .of(UserService::createUser) // + .builder(b -> b // + ._object(Arguments1::arg1, "userService", c -> c.notNull()) + ._string(Arguments2::arg2, "email", c -> c.email()) + ._string(Arguments3::arg3, "name", c -> c.notNull())) // + .build(); + static final UserService userService = new UserService(); + + @Test + void valid() { + final User user = validator.validated(userService, "jd@example.com", "John Doe"); + assertThat(user).isNotNull(); + } + + @Test + void invalid() { + assertThatThrownBy(() -> validator.validated(userService, "jd", null)) // + .isInstanceOfSatisfying(ConstraintViolationsException.class, + e -> assertThat(e.getMessage()).isEqualTo( + "Constraint violations found!" + System.lineSeparator() + + "* \"email\" must be a valid email address" + + System.lineSeparator() + + "* \"name\" must not be null")); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ObjectValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ObjectValidatorTest.java new file mode 100644 index 0000000..198467e --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ObjectValidatorTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.time.Instant; +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.ObjectValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.CustomConstraint; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ObjectValidatorTest { + static final CustomConstraint past = new CustomConstraint() { + + @Override + public boolean test(Instant instant) { + return instant.isBefore(Instant.now()); + } + + @Override + public String defaultMessageFormat() { + return "\"{0}\" must be past"; + } + + @Override + public String messageKey() { + return "instant.past"; + } + }; + + @ParameterizedTest + @MethodSource("validators") + void validateValid(ObjectValidator dateValidator) { + final Validated dateValidated = dateValidator + .validate(Instant.ofEpochMilli(1000L)); + assertThat(dateValidated.isValid()).isTrue(); + assertThat(dateValidated.value().getTime()).isEqualTo(1000L); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(ObjectValidator dateValidator) { + final Validated dateValidated = dateValidator + .validate(Instant.now().plusSeconds(1)); + assertThat(dateValidated.isValid()).isFalse(); + final ConstraintViolations violations = dateValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("createdAt"); + assertThat(violations.get(0).messageKey()).isEqualTo("instant.past"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(ObjectValidator dateValidator) { + final Date date = dateValidator.validated(Instant.ofEpochMilli(1000L)); + assertThat(date.getTime()).isEqualTo(1000L); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(ObjectValidator dateValidator) { + assertThatThrownBy(() -> dateValidator.validated(Instant.now().plusSeconds(1))) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"createdAt\" must be past"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(ObjectValidator dateValidator) { + final Map params = Collections.singletonMap("createdAt", + Instant.ofEpochMilli(1000L)); + final Arguments1Validator, Date> mapValidator = dateValidator + .compose(map -> map.get("createdAt")); + final Validated dateValidated = mapValidator.validate(params); + assertThat(dateValidated.isValid()).isTrue(); + assertThat(dateValidated.value().getTime()).isEqualTo(1000L); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(ObjectValidator dateValidator) { + final Map params = Collections.singletonMap("createdAt", + Instant.now().plusSeconds(1)); + final Arguments1Validator, Date> mapValidator = dateValidator + .compose(map -> map.get("createdAt")); + final Validated dateValidated = mapValidator.validate(params); + assertThat(dateValidated.isValid()).isFalse(); + final ConstraintViolations violations = dateValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("createdAt"); + assertThat(violations.get(0).messageKey()).isEqualTo("instant.past"); + } + + static Stream> validators() { + return Stream.of( + ObjectValidatorBuilder + . of("createdAt", + c -> c.notNull().predicateNullable(past)) + .build(Date::from), + ObjectValidatorBuilder + . of("createdAt", + c -> c.notNull().predicateNullable(past)) + .build().andThen(Date::from)); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/Product.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/Product.java new file mode 100644 index 0000000..b8599f1 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/Product.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import org.xbib.datastructures.validation.builder.ArgumentsValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; + +public class Product { + private final String name; + + private final int price; + + static final Arguments2Validator validator = ArgumentsValidatorBuilder + .of(Product::new) // + .builder(b -> b // + ._string(Arguments1::arg1, "name", c -> c.notEmpty()) + ._integer(Arguments2::arg2, "price", c -> c.greaterThan(0))) + .build(); + + public Product(String name, int price) { + validator.lazy().validate(name, price) + .throwIfInvalid(ConstraintViolationsException::new); + this.name = name; + this.price = price; + } + + @Override + public String toString() { + return "Product{" + "name='" + name + '\'' + ", price=" + price + '}'; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ShortValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ShortValidatorTest.java new file mode 100644 index 0000000..0c98fc8 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/ShortValidatorTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.builder.ShortValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ShortValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(ShortValidator priceValidator) { + final Validated priceValidated = priceValidator.validate((short) 100); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo((short) 100); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(ShortValidator priceValidator) { + final Validated priceValidated = priceValidator.validate((short) -1); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(ShortValidator priceValidator) { + final Price price = priceValidator.validated((short) 100); + assertThat(price.value()).isEqualTo((short) 100); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(ShortValidator priceValidator) { + assertThatThrownBy(() -> priceValidator.validated((short) -1)) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"price\" must be greater than or equal to 0"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(ShortValidator priceValidator) { + final Map params = Collections.singletonMap("price", (short) 100); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isTrue(); + assertThat(priceValidated.value().value()).isEqualTo((short) 100); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(ShortValidator priceValidator) { + final Map params = Collections.singletonMap("price", (short) -1); + final Arguments1Validator, Price> mapValidator = priceValidator + .compose(map -> map.get("price")); + final Validated priceValidated = mapValidator.validate(params); + assertThat(priceValidated.isValid()).isFalse(); + final ConstraintViolations violations = priceValidated.errors(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).name()).isEqualTo("price"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + + static Stream> validators() { + return Stream.of( + ShortValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual((short) 0)) + .build(Price::new), + ShortValidatorBuilder + .of("price", c -> c.notNull().greaterThanOrEqual((short) 0)) + .build().andThen(Price::new)); + } + + public static class Price { + private final short value; + + public Price(short value) { + this.value = value; + } + + public short value() { + return value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/StringValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/StringValidatorTest.java new file mode 100644 index 0000000..153457b --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/StringValidatorTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.builder.StringValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class StringValidatorTest { + + @ParameterizedTest + @MethodSource("validators") + void validateValid(StringValidator countryValidator) { + final Validated countryValidated = countryValidator.validate("JP"); + assertThat(countryValidated.isValid()).isTrue(); + assertThat(countryValidated.value().name()).isEqualTo("JP"); + } + + @ParameterizedTest + @MethodSource("validators") + void validateInvalid(StringValidator countryValidator) { + final Validated countryValidated = countryValidator.validate(" "); + assertThat(countryValidated.isValid()).isFalse(); + final ConstraintViolations violations = countryValidated.errors(); + assertThat(violations).hasSize(2); + assertThat(violations.get(0).name()).isEqualTo("country"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).name()).isEqualTo("country"); + assertThat(violations.get(1).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedValid(StringValidator countryValidator) { + final Country country = countryValidator.validated("JP"); + assertThat(country.name()).isEqualTo("JP"); + } + + @ParameterizedTest + @MethodSource("validators") + void validatedInvalid(StringValidator countryValidator) { + assertThatThrownBy(() -> countryValidator.validated(" ")) + .isInstanceOf(ConstraintViolationsException.class) + .hasMessageContaining("\"country\" must not be blank") + .hasMessageContaining( + "The size of \"country\" must be greater than or equal to 2. The given size is 1"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeValid(StringValidator countryValidator) { + final Map params = Collections.singletonMap("country", "JP"); + final Arguments1Validator, Country> mapValidator = countryValidator + .compose(map -> map.get("country")); + final Validated countryValidated = mapValidator.validate(params); + assertThat(countryValidated.isValid()).isTrue(); + assertThat(countryValidated.value().name()).isEqualTo("JP"); + } + + @ParameterizedTest + @MethodSource("validators") + void composeInvalid(StringValidator countryValidator) { + final Map params = Collections.singletonMap("country", " "); + final Arguments1Validator, Country> mapValidator = countryValidator + .compose(map -> map.get("country")); + final Validated countryValidated = mapValidator.validate(params); + assertThat(countryValidated.isValid()).isFalse(); + final ConstraintViolations violations = countryValidated.errors(); + assertThat(violations).hasSize(2); + assertThat(violations.get(0).name()).isEqualTo("country"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).name()).isEqualTo("country"); + assertThat(violations.get(1).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + static Stream> validators() { + return Stream.of( + StringValidatorBuilder + .of("country", c -> c.notBlank().greaterThanOrEqual(2)) + .build(Country::new), + StringValidatorBuilder + .of("country", c -> c.notBlank().greaterThanOrEqual(2)).build() + .andThen(Country::new)); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/User.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/User.java new file mode 100644 index 0000000..56fcf18 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/User.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +public class User { + private final String email; + private final String name; + + public User(String email, String name) { + this.email = email; + this.name = name; + } + + @Override + public String toString() { + return "User{" + "email='" + email + '\'' + ", name='" + name + '\'' + '}'; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/UserService.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/UserService.java new file mode 100644 index 0000000..b8f9b2c --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/arguments/UserService.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.arguments; + +public class UserService { + public User createUser(String email, String name) { + return new User(email, name); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BigDecimalConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BigDecimalConstraintTest.java new file mode 100644 index 0000000..c51d209 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BigDecimalConstraintTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.math.BigDecimal; +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class BigDecimalConstraintTest { + + @ParameterizedTest + @ValueSource(strings = { "101", "150" }) + void validGreaterThan(BigDecimal value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThan(new BigDecimal("100"))); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "100", "-50" }) + void invalidGreaterThan(BigDecimal value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThan(new BigDecimal("100"))); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "101", "100" }) + void validGreaterThanOrEqual(BigDecimal value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual(new BigDecimal("100"))); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "-50" }) + void invalidGreaterThanOrEqual(BigDecimal value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual(new BigDecimal("100"))); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "-50" }) + void validLessThan(BigDecimal value) { + Predicate predicate = retrievePredicate( + c -> c.lessThan(new BigDecimal("100"))); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "100", "150" }) + void invalidLessThan(BigDecimal value) { + Predicate predicate = retrievePredicate( + c -> c.lessThan(new BigDecimal("100"))); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100" }) + void validLessThanOrEqual(BigDecimal value) { + Predicate predicate = retrievePredicate( + c -> c.lessThanOrEqual(new BigDecimal("100"))); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "101", "150" }) + void invalidLessThanOrEqual(BigDecimal value) { + Predicate predicate = retrievePredicate( + c -> c.lessThanOrEqual(new BigDecimal("100"))); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100" }) + void validPositive(BigDecimal value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-150", "0" }) + void invalidPositive(BigDecimal value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100", "0" }) + void invalidNegative(BigDecimal value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-150" }) + void validNegative(BigDecimal value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negative); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100", "0" }) + void validPositiveOrZero(BigDecimal value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-150" }) + void invalidPositiveOrZero(BigDecimal value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100" }) + void invalidNegativeOrZero(BigDecimal value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-150", "0" }) + void validNegativeOrZero(BigDecimal value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + private static Predicate retrievePredicate( + Function, BigDecimalConstraint> constraint) { + return constraint.apply(new BigDecimalConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BigIntegerConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BigIntegerConstraintTest.java new file mode 100644 index 0000000..579db2a --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BigIntegerConstraintTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.math.BigInteger; +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class BigIntegerConstraintTest { + + @ParameterizedTest + @ValueSource(strings = { "101", "150" }) + void validGreaterThan(BigInteger value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThan(new BigInteger("100"))); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "100", "-50" }) + void invalidGreaterThan(BigInteger value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThan(new BigInteger("100"))); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "101", "100" }) + void validGreaterThanOrEqual(BigInteger value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual(new BigInteger("100"))); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "-50" }) + void invalidGreaterThanOrEqual(BigInteger value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual(new BigInteger("100"))); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "-50" }) + void validLessThan(BigInteger value) { + Predicate predicate = retrievePredicate( + c -> c.lessThan(new BigInteger("100"))); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "100", "150" }) + void invalidLessThan(BigInteger value) { + Predicate predicate = retrievePredicate( + c -> c.lessThan(new BigInteger("100"))); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100" }) + void validLessThanOrEqual(BigInteger value) { + Predicate predicate = retrievePredicate( + c -> c.lessThanOrEqual(new BigInteger("100"))); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "101", "150" }) + void invalidLessThanOrEqual(BigInteger value) { + Predicate predicate = retrievePredicate( + c -> c.lessThanOrEqual(new BigInteger("100"))); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100" }) + void validPositive(BigInteger value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-150", "0" }) + void invalidPositive(BigInteger value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100", "0" }) + void invalidNegative(BigInteger value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-150" }) + void validNegative(BigInteger value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negative); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100", "0" }) + void validPositiveOrZero(BigInteger value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-150" }) + void invalidPositiveOrZero(BigInteger value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100" }) + void invalidNegativeOrZero(BigInteger value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-150", "0" }) + void validNegativeOrZero(BigInteger value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + private static Predicate retrievePredicate( + Function, BigIntegerConstraint> constraint) { + return constraint.apply(new BigIntegerConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BooleanConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BooleanConstraintTest.java new file mode 100644 index 0000000..0a58c9f --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/BooleanConstraintTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.junit.jupiter.api.Test; + +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class BooleanConstraintTest { + private BooleanConstraint constraint = new BooleanConstraint<>(); + + @Test + void isFalse() { + Predicate predicate = constraint.isFalse().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(true)).isFalse(); + assertThat(predicate.test(false)).isTrue(); + } + + @Test + void isTrue() { + Predicate predicate = constraint.isTrue().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(true)).isTrue(); + assertThat(predicate.test(false)).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ByteConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ByteConstraintTest.java new file mode 100644 index 0000000..8e3dbf5 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ByteConstraintTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class ByteConstraintTest { + + @ParameterizedTest + @ValueSource(bytes = { 101, 120 }) + void validGreaterThan(byte value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan((byte) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(bytes = { 100, -50 }) + void invalidGreaterThan(byte value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan((byte) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(bytes = { 101, 100 }) + void validGreaterThanOrEqual(byte value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual((byte) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(bytes = { 99, -50 }) + void invalidGreaterThanOrEqual(byte value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual((byte) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(bytes = { 99, -50 }) + void validLessThan(byte value) { + Predicate predicate = retrievePredicate(c -> c.lessThan((byte) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(bytes = { 100, 120 }) + void invalidLessThan(byte value) { + Predicate predicate = retrievePredicate(c -> c.lessThan((byte) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(bytes = { 99, 100 }) + void validLessThanOrEqual(byte value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual((byte) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(bytes = { 101, 120 }) + void invalidLessThanOrEqual(byte value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual((byte) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100" }) + void validPositive(byte value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-1", "0" }) + void invalidPositive(byte value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100", "0" }) + void invalidNegative(byte value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-10" }) + void validNegative(byte value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100", "0" }) + void validPositiveOrZero(byte value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-12" }) + void invalidPositiveOrZero(byte value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "99", "100" }) + void invalidNegativeOrZero(byte value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-101", "-120", "0" }) + void validNegativeOrZero(byte value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + private static Predicate retrievePredicate( + Function, ByteConstraint> constraint) { + return constraint.apply(new ByteConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CharSequenceConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CharSequenceConstraintTest.java new file mode 100644 index 0000000..e83160a --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CharSequenceConstraintTest.java @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.charsequence.variant.IdeographicVariationSequence; +import org.xbib.datastructures.validation.constraint.charsequence.variant.MongolianFreeVariationSelector; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class CharSequenceConstraintTest { + + @ParameterizedTest + @ValueSource(strings = { "yavi", "aは小文字" }) + void validContains(String value) { + Predicate predicate = retrievePredicate(c -> c.contains("a")); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "yvi", "Aは大文字" }) + void invalidContains(String value) { + Predicate predicate = retrievePredicate(c -> c.contains("a")); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "abc@example.com", "abc@localhost", "abc@192.168.1.10", + "東京@example.com", "" }) + void validEmail(String value) { + Predicate predicate = retrievePredicate(c -> c.email()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "example.com", "abc@@example.com" }) + void invalidEmail(String value) { + Predicate predicate = retrievePredicate(c -> c.email()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "ab", "漢字" }) + void validFixedSize(String value) { + Predicate predicate = retrievePredicate(c -> c.fixedSize(2)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "a", "abc" }) + void invalidFixedSize(String value) { + Predicate predicate = retrievePredicate(c -> c.fixedSize(2)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "abcd", "朝ごはん" }) + void validGreaterThan(String value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(3)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "abc", "\uD842\uDFB7野屋" }) + void invalidGreaterThan(String value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(3)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "abcd", "abc" }) + void validGreaterThanOrEqual(String value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(3)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "ab", "\uD842\uDFB7田" }) + void invalidGreaterThanOrEqual(String value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(3)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "ᠠ᠋", "ᠰ᠌" }) + void ignoreFvsCharacter(String value) { + Predicate predicate = retrievePredicate( + c -> c.variant(opts -> opts.fvs(MongolianFreeVariationSelector.IGNORE)) + .fixedSize(1)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "\uD842\uDF9F\uDB40\uDD00", "\u908A\uDB40\uDD07" }) + void ignoreIvsCharacter(String value) { + Predicate predicate = retrievePredicate( + c -> c.variant(opts -> opts.ivs(IdeographicVariationSequence.IGNORE)) + .fixedSize(1)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "ab", "\uD842\uDFB7田" }) + void validLessThan(String value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(3)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "abc", "\uD842\uDFB7野屋" }) + void invalidLessThan(String value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(3)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "ab", "abc", "\uD842\uDFB7野屋" }) + void validLessThanOrEqual(String value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(3)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "abcd" }) + void invalidLessThanOrEqual(String value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(3)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "モジ" /* モシ\u3099 */ }) + void validNormalizeCombiningCharacter(String value) { + Predicate predicate = retrievePredicate(c -> c.fixedSize(2)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "foo", "漢字" }) + void validNotBlank(String value) { + Predicate predicate = retrievePredicate(c -> c.notBlank()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "", " ", "   " }) + void invalidNotBlank(String value) { + Predicate predicate = retrievePredicate(c -> c.notBlank()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "foo", " " }) + void validNotEmpty(String value) { + Predicate predicate = retrievePredicate(c -> c.notEmpty()); + assertThat(predicate.test(value)).isTrue(); + } + + @Test + void invalidNotEmpty() { + Predicate predicate = retrievePredicate(c -> c.notEmpty()); + assertThat(predicate.test("")).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "ᠠ᠋", "ᠰ᠌" }) + void notIgnoreFvsCharacter(String value) { + Predicate predicate = retrievePredicate(c -> c.fixedSize(2)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "\uD842\uDF9F\uDB40\uDD00", "\u908A\uDB40\uDD07" }) + void notIgnoreIvsCharacter(String value) { + Predicate predicate = retrievePredicate(c -> c.fixedSize(2)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "モジ" /* モシ\u3099 */ }) + void notNormalizeCombiningCharacter(String value) { + Predicate predicate = retrievePredicate( + c -> c.normalizer(null).fixedSize(3)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "1234", "0000" }) + void validPattern(String value) { + Predicate predicate = retrievePredicate(c -> c.pattern("[0-9]{4}")); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "134a", "abcd" }) + void invalidPattern(String value) { + Predicate predicate = retrievePredicate(c -> c.pattern("[0-9]{4}")); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "http://example.com", "https://example.com", "" }) + void validUrl(String value) { + Predicate predicate = retrievePredicate(c -> c.url()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "example.com", "htt://example.com" }) + void invalidUrl(String value) { + Predicate predicate = retrievePredicate(c -> c.url()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @MethodSource("randomUUIDs") + void validUUID(String value) { + Predicate predicate = retrievePredicate(CharSequenceConstraint::uuid); + assertThat(predicate.test(value)).isTrue(); + } + + private static Stream randomUUIDs() { + return Stream.generate(UUID::randomUUID).map(UUID::toString).limit(10); + } + + @ParameterizedTest + @ValueSource(strings = { "nonsense-nonsense-nonsense", + "12345678-1234-1234-1234-1234-12345678" }) + void invalidUUID(String value) { + Predicate predicate = retrievePredicate(CharSequenceConstraint::uuid); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-128", "0", "127" }) + void validIsByte(String value) { + Predicate predicate = retrievePredicate(c -> c.isByte()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-129", "128", "a" }) + void invalidIsByte(String value) { + Predicate predicate = retrievePredicate(c -> c.isByte()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-32768", "0", "32767" }) + void validIsShort(String value) { + Predicate predicate = retrievePredicate(c -> c.isShort()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-32769", "32768", "a" }) + void invalidIsShort(String value) { + Predicate predicate = retrievePredicate(c -> c.isShort()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-2147483648", "0", "2147483647" }) + void validIsInteger(String value) { + Predicate predicate = retrievePredicate(c -> c.isInteger()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-2147483649", "2147483648", "a" }) + void invalidIsInteger(String value) { + Predicate predicate = retrievePredicate(c -> c.isInteger()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-9223372036854775808", "0", "9223372036854775807" }) + void validIsLong(String value) { + Predicate predicate = retrievePredicate(c -> c.isLong()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "-9223372036854775809", "9223372036854775808", "a" }) + void invalidIsLong(String value) { + Predicate predicate = retrievePredicate(c -> c.isLong()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-100", "-1.0", "0", "1.0", "100" }) + void validIsFloat(String value) { + Predicate predicate = retrievePredicate(c -> c.isFloat()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "a" }) + void invalidIsFloat(String value) { + Predicate predicate = retrievePredicate(c -> c.isFloat()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-100", "-1.0", "0", "1.0", "100" }) + void validIsDouble(String value) { + Predicate predicate = retrievePredicate(c -> c.isDouble()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "a" }) + void invalidIsDouble(String value) { + Predicate predicate = retrievePredicate(c -> c.isDouble()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-9223372036854775809", "0", "9223372036854775808" }) + void validIsBigInteger(String value) { + Predicate predicate = retrievePredicate(c -> c.isBigInteger()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "a", "0.1" }) + void invalidIsBigInteger(String value) { + Predicate predicate = retrievePredicate(c -> c.isBigInteger()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "-100", "-1.0", "0", "1.0", "100" }) + void validIsBigDecimal(String value) { + Predicate predicate = retrievePredicate(c -> c.isBigDecimal()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "a" }) + void invalidIsBigDecimal(String value) { + Predicate predicate = retrievePredicate(c -> c.isBigDecimal()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "4111111111111111", "4242424242424242", "4012888888881881", + "5555555555554444", "5105105105105100", "378282246310005", "371449635398431", + "30569309025904", "38520000023237", "3530111333300000", "3566002020360505" }) + void validLuhn(String value) { + final Predicate predicate = retrievePredicate(c -> c.luhn()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "4111111111111112", "4242424242424243", "401288888888188a" }) + void invalidLuhn(String value) { + final Predicate predicate = retrievePredicate(c -> c.luhn()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "1.1.1.1", "127.0.0.1", "255.255.255.255", "0.0.0.0" }) + void validIpv4(String value) { + final Predicate predicate = retrievePredicate(c -> c.ipv4()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "1.1.1.1.1", "255.255.255.256", "a.a.a.a" }) + void invalidIpv4(String value) { + final Predicate predicate = retrievePredicate(c -> c.ipv4()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "1762:0:0:0:0:B03:1:AF18", "0:0:0:0:0:0:0:0", + "0:0:0:0:0:0:0:1", "::1", "2001:0db8:bd05:01d2:288a:1fc0:0001:10ee", + "2001:db8:20:3:1000:100:20:3", "2001:db8::1234:0:0:9abc", "2001:db8::9abc", + "::ffff:192.0.2.1", "fe80::0123:4567:89ab:cdef%4", + "fe80::0123:4567:89ab:cdef%fxp0" }) + void validIpv6(String value) { + final Predicate predicate = retrievePredicate(c -> c.ipv6()); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "1.1.1.1.1", "0:0:0:0:0:0:0:Z" }) + void invalidIpv6(String value) { + final Predicate predicate = retrievePredicate(c -> c.ipv6()); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "DiegoFooiqMu3IPpgf", "DiegoFoojQ2gFpzEmO", + "DiegoFooPcMzCARdWM", "DiegoFoohdORZ6ks8H", "DiegoFooT7UFB7ijEU", + "DiegoFooFSmlWu4XJz", "DiegoFoofwlgMXnuvW", "DiegoFooyj4MgAQeBi", + "DiegoFookcYOLkz00a", "DiegoFooiYBuRcAkXP" }) + void validStartsWith(String value) { + String prefix = "DiegoFoo"; + final Predicate predicate = retrievePredicate(c -> c.startsWith(prefix)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "DiegoFooiqMu3IPpgf", "DiegoFoojQ2gFpzEmO", + "DiegoFooPcMzCARdWM", "DiegoFoohdORZ6ks8H", "DiegoFooT7UFB7ijEU", + "DiegoFooFSmlWu4XJz", "DiegoFoofwlgMXnuvW", "DiegoFooyj4MgAQeBi", + "DiegoFookcYOLkz00a", "DiegoFooiYBuRcAkXP" }) + void validStartsWithStringBuilder(String value) { + StringBuilder prefixBuilder = new StringBuilder("Diego").append("Foo"); + final Predicate predicate = retrievePredicate( + c -> c.startsWith(prefixBuilder)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "iqMu3IPpgf", "jQ2gFpzEmO", "PcMzCARdWM", "hdORZ6ks8H", + "T7UFB7ijEU", "FSmlWu4XJz", "fwlgMXnuvW", "yj4MgAQeBi", "kcYOLkz00a", + "iYBuRcAkXP" }) + void invalidStartsWith(String value) { + String prefix = "DiegoFoo"; + final Predicate predicate = retrievePredicate(c -> c.startsWith(prefix)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "iqMu3IPpgf", "jQ2gFpzEmO", "PcMzCARdWM", "hdORZ6ks8H", + "T7UFB7ijEU", "FSmlWu4XJz", "fwlgMXnuvW", "yj4MgAQeBi", "kcYOLkz00a", + "iYBuRcAkXP" }) + void invalidStartsWithStringBuilder(String value) { + StringBuilder prefixBuilder = new StringBuilder("Diego").append("Foo"); + final Predicate predicate = retrievePredicate( + c -> c.startsWith(prefixBuilder)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "iqMu3IPpgfDiego", "jQ2gFpzEmODiego", "PcMzCARdWMDiego", + "hdORZ6ks8HDiego", "T7UFB7ijEUDiego", "FSmlWu4XJzDiego", "fwlgMXnuvWDiego", + "yj4MgAQeBiDiego", "kcYOLkz00aDiego", "iYBuRcAkXPDiego" }) + void validEndsWith(String value) { + String suffix = "Diego"; + final Predicate predicate = retrievePredicate(c -> c.endsWith(suffix)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "iqMu3IPpgfDiego1", "jQ2gFpzEmODiego1", "PcMzCARdWMDiego1", + "hdORZ6ks8HDiego1", "T7UFB7ijEUDiego1", "FSmlWu4XJzDiego1", + "fwlgMXnuvWDiego1", "yj4MgAQeBiDiego1", "kcYOLkz00aDiego1", + "iYBuRcAkXPDiego1" }) + void validEndsWithStringBuilder(String value) { + StringBuilder suffixBuilder = new StringBuilder("Diego").append(1); + final Predicate predicate = retrievePredicate( + c -> c.endsWith(suffixBuilder)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "iqMu3IPpgf", "jQ2gFpzEmO", "PcMzCARdWM", "hdORZ6ks8H", + "T7UFB7ijEU", "FSmlWu4XJz", "fwlgMXnuvW", "yj4MgAQeBi", "kcYOLkz00a", + "iYBuRcAkXP" }) + void invalidEndsWith(String value) { + String suffix = "Diego"; + final Predicate predicate = retrievePredicate(c -> c.endsWith(suffix)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "iqMu3IPpgf", "jQ2gFpzEmO", "PcMzCARdWM", "hdORZ6ks8H", + "T7UFB7ijEU", "FSmlWu4XJz", "fwlgMXnuvW", "yj4MgAQeBi", "kcYOLkz00a", + "iYBuRcAkXP" }) + void invalidEndsWithStringBuilder(String value) { + StringBuilder suffixBuilder = new StringBuilder("Diego").append(1); + final Predicate predicate = retrievePredicate( + c -> c.endsWith(suffixBuilder)); + assertThat(predicate.test(value)).isFalse(); + } + + private static Predicate retrievePredicate( + Function, CharSequenceConstraint> constraint) { + return constraint.apply(new CharSequenceConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CharacterConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CharacterConstraintTest.java new file mode 100644 index 0000000..21841ed --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CharacterConstraintTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class CharacterConstraintTest { + + @ParameterizedTest + @ValueSource(chars = { 101, 120 }) + void validGreaterThan(char value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThan((char) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(chars = { 100, 0 }) + void invalidGreaterThan(char value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThan((char) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(chars = { 101, 100 }) + void validGreaterThanOrEqual(char value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual((char) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(chars = { 99, 0 }) + void invalidGreaterThanOrEqual(char value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual((char) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(chars = { 99, 0 }) + void validLessThan(char value) { + Predicate predicate = retrievePredicate(c -> c.lessThan((char) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(chars = { 100, 120 }) + void invalidLessThan(char value) { + Predicate predicate = retrievePredicate(c -> c.lessThan((char) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(chars = { 99, 100 }) + void validLessThanOrEqual(char value) { + Predicate predicate = retrievePredicate( + c -> c.lessThanOrEqual((char) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(chars = { 101, 120 }) + void invalidLessThanOrEqual(char value) { + Predicate predicate = retrievePredicate( + c -> c.lessThanOrEqual((char) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(chars = { 99, 100 }) + void validPositive(char value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(chars = { 0 }) + void invalidPositive(char value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(chars = { 99, 100 }) + void validNegative(char value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + private static Predicate retrievePredicate( + Function, CharacterConstraint> constraint) { + return constraint.apply(new CharacterConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CollectionConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CollectionConstraintTest.java new file mode 100644 index 0000000..7e9bd27 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/CollectionConstraintTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class CollectionConstraintTest { + + @Test + void contains() { + Predicate> predicate = retrievePredicate(c -> c.contains("foo")); + assertThat(predicate.test(Arrays.asList("foo", "bar"))).isTrue(); + assertThat(predicate.test(Arrays.asList("bar", "baz"))).isFalse(); + } + + @Test + void fixedSize() { + Predicate> predicate = retrievePredicate(c -> c.fixedSize(2)); + assertThat(predicate.test(Collections.singletonList("foo"))).isFalse(); + assertThat(predicate.test(Arrays.asList("foo", "bar"))).isTrue(); + assertThat(predicate.test(Arrays.asList("foo", "bar", "baz"))).isFalse(); + } + + @Test + void greaterThan() { + Predicate> predicate = retrievePredicate(c -> c.greaterThan(2)); + assertThat(predicate.test(Arrays.asList("foo", "bar"))).isFalse(); + assertThat(predicate.test(Arrays.asList("foo", "bar", "baz"))).isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate> predicate = retrievePredicate( + c -> c.greaterThanOrEqual(2)); + assertThat(predicate.test(Collections.singletonList("foo"))).isFalse(); + assertThat(predicate.test(Arrays.asList("foo", "bar"))).isTrue(); + assertThat(predicate.test(Arrays.asList("foo", "bar", "baz"))).isTrue(); + } + + @Test + void lessThan() { + Predicate> predicate = retrievePredicate(c -> c.lessThan(2)); + assertThat(predicate.test(Collections.singletonList("foo"))).isTrue(); + assertThat(predicate.test(Arrays.asList("foo", "bar"))).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate> predicate = retrievePredicate(c -> c.lessThanOrEqual(2)); + assertThat(predicate.test(Collections.singletonList("foo"))).isTrue(); + assertThat(predicate.test(Arrays.asList("foo", "bar"))).isTrue(); + assertThat(predicate.test(Arrays.asList("foo", "bar", "baz"))).isFalse(); + } + + @Test + void notEmpty() { + Predicate> predicate = retrievePredicate(c -> c.notEmpty()); + assertThat(predicate.test(Collections.singletonList("foo"))).isTrue(); + assertThat(predicate.test(Collections.emptyList())).isFalse(); + } + + @Test + void unique() { + Predicate> predicate = retrievePredicate(c -> c.unique()); + assertThat(predicate.test(Arrays.asList("a", "b", "c", "d"))).isTrue(); + assertThat(predicate.test(Arrays.asList("a", "b", "c", "b"))).isFalse(); + } + + private static Predicate> retrievePredicate( + Function, List, String>, CollectionConstraint, List, String>> constraint) { + return constraint.apply(new CollectionConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/DoubleConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/DoubleConstraintTest.java new file mode 100644 index 0000000..a729c40 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/DoubleConstraintTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class DoubleConstraintTest { + + @ParameterizedTest + @ValueSource(doubles = { 101.0, 150.0 }) + void validGreaterThan(double value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(100.0)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(doubles = { 100.0, -50.0 }) + void invalidGreaterThan(double value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(100.0)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(doubles = { 101.0, 100.0 }) + void validGreaterThanOrEqual(double value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(100.0)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(doubles = { 99L, -50.0 }) + void invalidGreaterThanOrEqual(double value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(100.0)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(doubles = { 99L, -50.0 }) + void validLessThan(double value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(100.0)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(doubles = { 100.0, 150.0 }) + void invalidLessThan(double value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(100.0)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(doubles = { 99L, 100.0 }) + void validLessThanOrEqual(double value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(100.0)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(doubles = { 101.0, 150.0 }) + void invalidLessThanOrEqual(double value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(100.0)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(doubles = { 101.0, 150.0 }) + void validPositive(double value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(doubles = { -101.0, -150.0, 0 }) + void invalidPositive(double value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(doubles = { 99.0, 100.0, 0.0 }) + void invalidNegative(double value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(doubles = { -100.0, -10.0 }) + void validNegative(double value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(doubles = { 99.5, 100.5, 0 }) + void validPositiveOrZero(double value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(doubles = { -101, -12 }) + void invalidPositiveOrZero(double value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(doubles = { 99.0, 100 }) + void invalidNegativeOrZero(double value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(doubles = { -101, -120, 0 }) + void validNegativeOrZero(double value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + private static Predicate retrievePredicate( + Function, DoubleConstraint> constraint) { + return constraint.apply(new DoubleConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/FloatConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/FloatConstraintTest.java new file mode 100644 index 0000000..6ceb050 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/FloatConstraintTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class FloatConstraintTest { + + @ParameterizedTest + @ValueSource(floats = { 101.0f, 150.0f }) + void validGreaterThan(float value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(100.0f)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(floats = { 100.0f, -50.0f }) + void invalidGreaterThan(float value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(100.0f)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(floats = { 101.0f, 100.0f }) + void validGreaterThanOrEqual(float value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(100.0f)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(floats = { 99L, -50.0f }) + void invalidGreaterThanOrEqual(float value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(100.0f)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(floats = { 99L, -50.0f }) + void validLessThan(float value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(100.0f)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(floats = { 100.0f, 150.0f }) + void invalidLessThan(float value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(100.0f)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(floats = { 99L, 100.0f }) + void validLessThanOrEqual(float value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(100.0f)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(floats = { 101.0f, 150.0f }) + void invalidLessThanOrEqual(float value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(100.0f)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(floats = { 101.0f, 150.0f }) + void validPositive(float value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(floats = { -101.0f, -150.0f, 0f }) + void invalidPositive(float value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(floats = { 99f, 100f, 0f }) + void invalidNegative(float value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(floats = { -100f, -10f }) + void validNegative(float value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(floats = { 99.5f, 100.5f, 0f }) + void validPositiveOrZero(float value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(floats = { -101f, -12f }) + void invalidPositiveOrZero(float value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(floats = { 99.0f, 100f }) + void invalidNegativeOrZero(float value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(floats = { -101f, -120f, 0f }) + void validNegativeOrZero(float value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + private static Predicate retrievePredicate( + Function, FloatConstraint> constraint) { + return constraint.apply(new FloatConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/InstantConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/InstantConstraintTest.java new file mode 100644 index 0000000..a25de83 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/InstantConstraintTest.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class InstantConstraintTest { + + @Test + void isPastValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(Instant.now().minus(60, ChronoUnit.SECONDS))).isTrue(); + } + + @Test + void isPastInValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(Instant.now().plus(60, ChronoUnit.SECONDS))).isFalse(); + } + + @Test + void isPastExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.past(clock)); + assertThat(predicate.test(Instant.now(clock))).isFalse(); + } + + @Test + void isPastOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(Instant.now().minus(60, ChronoUnit.SECONDS))).isTrue(); + } + + @Test + void isPastOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(Instant.now().plus(60, ChronoUnit.SECONDS))).isFalse(); + } + + @Test + void isPastOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.pastOrPresent(clock)); + assertThat(predicate.test(Instant.now(clock))).isTrue(); + } + + @Test + void isFutureValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(Instant.now().plus(60, ChronoUnit.SECONDS))).isTrue(); + } + + @Test + void isFutureInValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(Instant.now().minus(60, ChronoUnit.SECONDS))).isFalse(); + } + + @Test + void isFutureExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.future(clock)); + assertThat(predicate.test(Instant.now(clock))).isFalse(); + } + + @Test + void isFutureOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(Instant.now().plus(60, ChronoUnit.SECONDS))).isTrue(); + } + + @Test + void isFutureOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(Instant.now().minus(60, ChronoUnit.SECONDS))).isFalse(); + } + + @Test + void isFutureOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.futureOrPresent(clock)); + assertThat(predicate.test(Instant.now(clock))).isTrue(); + } + + @Test + void isBeforeValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate( + c -> c.before(() -> now.plusSeconds(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeInValid() { + Instant now = Instant.now(); + Instant past = now.minusSeconds(10); + Predicate predicate = retrievePredicate(c -> c.before(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeExactInValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate(c -> c.before(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterInValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.plusSeconds(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.minusSeconds(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterExactInValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate(c -> c.after(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now.plusSeconds(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeOrEqualInValid() { + Instant now = Instant.now(); + Instant past = now.minusSeconds(10); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualExactInValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate(c -> c.beforeOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualInValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.plusSeconds(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterOrEqualValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.minusSeconds(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualExactValid() { + Instant now = Instant.now(); + Predicate predicate = retrievePredicate(c -> c.afterOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBetweenExactInValid() { + Instant now = Instant.now(); + Supplier nowSupplier = () -> now; + + Predicate predicate = retrievePredicate( + c -> c.between(nowSupplier, nowSupplier)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBetweenInValidException() { + Instant now = Instant.now(); + + Predicate predicate = retrievePredicate( + c -> c.between(() -> now.plusSeconds(1), () -> now.minusSeconds(1))); + assertThatThrownBy(() -> predicate.test(now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + + @Test + void temporalFieldValid() { + OffsetDateTime value = OffsetDateTime.of(1970, 1, 1, 0, 0, 0, 0, + ZoneOffset.ofHours(0)); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(INSTANT_SECONDS, s -> s >= 0)); + assertThat(predicate.test(value.toInstant())).isTrue(); + } + + @Test + void temporalFieldInValid() { + OffsetDateTime value = OffsetDateTime.of(1969, 12, 31, 23, 59, 59, 0, + ZoneOffset.ofHours(0)); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(INSTANT_SECONDS, s -> s >= 0)); + assertThat(predicate.test(value.toInstant())).isFalse(); + } + + private static Predicate retrievePredicate( + Function, InstantConstraint> constraint) { + return constraint.apply(new InstantConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/IntegerConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/IntegerConstraintTest.java new file mode 100644 index 0000000..1186790 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/IntegerConstraintTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class IntegerConstraintTest { + + @ParameterizedTest + @ValueSource(ints = { 101, 150 }) + void validGreaterThan(int value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(ints = { 100, -50 }) + void invalidGreaterThan(int value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(ints = { 101, 100 }) + void validGreaterThanOrEqual(int value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(ints = { 99, -50 }) + void invalidGreaterThanOrEqual(int value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(ints = { 99, -50 }) + void validLessThan(int value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(ints = { 100, 150 }) + void invalidLessThan(int value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(ints = { 99, 100 }) + void validLessThanOrEqual(int value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(ints = { 101, 150 }) + void invalidLessThanOrEqual(int value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(ints = { 101, 150 }) + void validPositive(int value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(ints = { -101, -150, 0 }) + void invalidPositive(int value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(ints = { 9, 100, 0 }) + void invalidNegative(int value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(ints = { -100, -10 }) + void validNegative(int value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(ints = { 99, 100, 0 }) + void validPositiveOrZero(int value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(ints = { -101, -12 }) + void invalidPositiveOrZero(int value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(ints = { 99, 100 }) + void invalidNegativeOrZero(int value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(ints = { -101, -120, 0 }) + void validNegativeOrZero(int value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + private static Predicate retrievePredicate( + Function, IntegerConstraint> constraint) { + return constraint.apply(new IntegerConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalDateConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalDateConstraintTest.java new file mode 100644 index 0000000..096843d --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalDateConstraintTest.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static java.time.DayOfWeek.SATURDAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LocalDateConstraintTest { + @Test + void isPastValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(LocalDate.now().minus(60, ChronoUnit.DAYS))).isTrue(); + } + + @Test + void isPastInValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(LocalDate.now().plus(60, ChronoUnit.DAYS))).isFalse(); + } + + @Test + void isPastExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.past(clock)); + assertThat(predicate.test(LocalDate.now(clock))).isFalse(); + } + + @Test + void isPastOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(LocalDate.now().minus(60, ChronoUnit.DAYS))).isTrue(); + } + + @Test + void isPastOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(LocalDate.now().plus(60, ChronoUnit.DAYS))).isFalse(); + } + + @Test + void isPastOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.pastOrPresent(clock)); + assertThat(predicate.test(LocalDate.now(clock))).isTrue(); + } + + @Test + void isFutureValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(LocalDate.now().plus(60, ChronoUnit.DAYS))).isTrue(); + } + + @Test + void isFutureInValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(LocalDate.now().minus(60, ChronoUnit.DAYS))).isFalse(); + } + + @Test + void isFutureExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.future(clock)); + assertThat(predicate.test(LocalDate.now(clock))).isFalse(); + } + + @Test + void isFutureOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(LocalDate.now().plus(60, ChronoUnit.DAYS))).isTrue(); + } + + @Test + void isFutureOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(LocalDate.now().minus(60, ChronoUnit.DAYS))).isFalse(); + } + + @Test + void isFutureOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.futureOrPresent(clock)); + assertThat(predicate.test(LocalDate.now(clock))).isTrue(); + } + + @Test + void isBeforeValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate( + c -> c.before(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeInValid() { + LocalDate now = LocalDate.now(); + LocalDate past = now.minusDays(10); + Predicate predicate = retrievePredicate(c -> c.before(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeExactInValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate(c -> c.before(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterInValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.minusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterExactInValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate(c -> c.after(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeOrEqualInValid() { + LocalDate now = LocalDate.now(); + LocalDate past = now.minusDays(10); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualExactValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualInValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterOrEqualValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.minusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualExactInValid() { + LocalDate now = LocalDate.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @ParameterizedTest + @MethodSource("validBetweenDates") + void isBetweenValid(LocalDate now, LocalDate rangeFrom, LocalDate rangeTo) { + Predicate predicate = retrievePredicate( + c -> c.between(() -> rangeFrom, () -> rangeTo)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBetweenExactInValid() { + LocalDate now = LocalDate.now(); + Supplier nowSupplier = () -> now; + + Predicate predicate = retrievePredicate( + c -> c.between(nowSupplier, nowSupplier)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBetweenInValidException() { + LocalDate now = LocalDate.now(); + + Predicate predicate = retrievePredicate( + c -> c.between(() -> now.plusDays(1), () -> now.minusDays(1))); + assertThatThrownBy(() -> predicate.test(now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + + @Test + void temporalFieldValid() { + LocalDate value = LocalDate.of(2022, 1, 1); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(DAY_OF_WEEK, week -> week == SATURDAY.getValue())); + assertThat(predicate.test(value)).isTrue(); + } + + @Test + void temporalFieldInValid() { + LocalDate value = LocalDate.of(2022, 1, 2); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(DAY_OF_WEEK, week -> week == SATURDAY.getValue())); + assertThat(predicate.test(value)).isFalse(); + } + + private static Stream validBetweenDates() { + return IntStream.rangeClosed(1, 10).boxed().map(i -> { + LocalDate now = LocalDate.now(); + return Arguments.of(now, now.minusDays(i), now.plusDays(i)); + }); + } + + private static Predicate retrievePredicate( + Function, LocalDateConstraint> constraint) { + return constraint.apply(new LocalDateConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalDateTimeConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalDateTimeConstraintTest.java new file mode 100644 index 0000000..70b868b --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalDateTimeConstraintTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.arguments.LocalDateTimeValidator; +import org.xbib.datastructures.validation.builder.LocalDateTimeValidatorBuilder; +import org.xbib.datastructures.validation.core.Validated; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static java.time.DayOfWeek.SATURDAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LocalDateTimeConstraintTest { + @Test + void isPastValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(LocalDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isPastInValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(LocalDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isPastExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.past(clock)); + assertThat(predicate.test(LocalDateTime.now(clock))).isFalse(); + } + + @Test + void isPastOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(LocalDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isPastOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(LocalDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isPastOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate( + c -> c.pastOrPresent(clock)); + assertThat(predicate.test(LocalDateTime.now(clock))).isTrue(); + } + + @Test + void isFutureValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(LocalDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isFutureInValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(LocalDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isFutureExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.future(clock)); + assertThat(predicate.test(LocalDateTime.now(clock))).isFalse(); + } + + @Test + void isFutureOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(LocalDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isFutureOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(LocalDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isFutureOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate( + c -> c.futureOrPresent(clock)); + assertThat(predicate.test(LocalDateTime.now(clock))).isTrue(); + } + + @Test + void isBeforeValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.before(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeInValid() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime past = now.minusDays(10); + Predicate predicate = retrievePredicate(c -> c.before(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeExactInValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate(c -> c.before(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterInValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.minusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterExactInValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate(c -> c.after(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeOrEqualInValid() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime past = now.minusDays(10); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualExactInValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualInValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterOrEqualValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.minusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualExactInValid() { + LocalDateTime now = LocalDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @ParameterizedTest + @MethodSource("validBetweenDates") + void isBetweenValid(LocalDateTime now, LocalDateTime rangeFrom, + LocalDateTime rangeTo) { + Predicate predicate = retrievePredicate( + c -> c.between(() -> rangeFrom, () -> rangeTo)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBetweenExactInValid() { + LocalDateTime now = LocalDateTime.now(); + Supplier nowSupplier = () -> now; + + Predicate predicate = retrievePredicate( + c -> c.between(nowSupplier, nowSupplier)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBetweenInValidException() { + LocalDateTime now = LocalDateTime.now(); + + Predicate predicate = retrievePredicate( + c -> c.between(() -> now.plusDays(1), () -> now.minusDays(1))); + assertThatThrownBy(() -> predicate.test(now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + + @Test + void temporalFieldValid() { + LocalDateTime value = LocalDateTime.of(2022, 1, 1, 0, 0, 0); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(DAY_OF_WEEK, week -> week == SATURDAY.getValue())); + assertThat(predicate.test(value)).isTrue(); + } + + @Test + void temporalFieldInValid() { + LocalDateTime value = LocalDateTime.of(2022, 1, 2, 0, 0, 0); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(DAY_OF_WEEK, week -> week == SATURDAY.getValue())); + assertThat(predicate.test(value)).isFalse(); + } + + @Test + void Message() { + final AtomicReference saved = new AtomicReference<>(); + final LocalDateTimeValidator validator = LocalDateTimeValidatorBuilder + .of("now", c -> c.after(() -> { + final LocalDateTime now = LocalDateTime.now(); + saved.set(now); + return now; + })).build(); + final Validated validated = validator + .validate(LocalDateTime.now()); + assertThat(validated.isValid()).isFalse(); + assertThat(validated.errors().get(0).message()) + .isEqualTo("\"now\" has to be after " + saved.get()); + } + + private static Stream validBetweenDates() { + return IntStream.rangeClosed(1, 10).boxed().map(i -> { + LocalDateTime now = LocalDateTime.now(); + return Arguments.of(now, now.minusHours(i), now.plusHours(i)); + }); + } + + private static Predicate retrievePredicate( + Function, LocalDateTimeConstraint> constraint) { + return constraint.apply(new LocalDateTimeConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalTimeConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalTimeConstraintTest.java new file mode 100644 index 0000000..0220623 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LocalTimeConstraintTest.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LocalTimeConstraintTest { + + private static final LocalTime BASE_TIME = LocalTime.of(12, 30); + + @Test + void isPastValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(LocalTime.now().minus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isPastInValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(LocalTime.now().plus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isPastExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.past(clock)); + assertThat(predicate.test(LocalTime.now(clock))).isFalse(); + } + + @Test + void isPastOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(LocalTime.now().minus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isPastOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(LocalTime.now().plus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isPastOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.pastOrPresent(clock)); + assertThat(predicate.test(LocalTime.now(clock))).isTrue(); + } + + @Test + void isFutureValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(LocalTime.now().plus(60, ChronoUnit.SECONDS))).isTrue(); + } + + @Test + void isFutureInValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(LocalTime.now().minus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isFutureExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.future(clock)); + assertThat(predicate.test(LocalTime.now(clock))).isFalse(); + } + + @Test + void isFutureOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(LocalTime.now().plus(60, ChronoUnit.SECONDS))).isTrue(); + } + + @Test + void isFutureOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(LocalTime.now().minus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isFutureOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.futureOrPresent(clock)); + assertThat(predicate.test(LocalTime.now(clock))).isTrue(); + } + + @Test + void isBeforeValid() { + Predicate predicate = retrievePredicate( + c -> c.before(() -> BASE_TIME.plusHours(10))); + assertThat(predicate.test(BASE_TIME)).isTrue(); + } + + @Test + void isBeforeInValid() { + LocalTime past = BASE_TIME.minusHours(10); + Predicate predicate = retrievePredicate(c -> c.before(() -> past)); + assertThat(predicate.test(BASE_TIME)).isFalse(); + } + + @Test + void isBeforeExactInValid() { + Predicate predicate = retrievePredicate( + c -> c.before(() -> BASE_TIME)); + assertThat(predicate.test(BASE_TIME)).isFalse(); + } + + @Test + void isAfterInValid() { + Predicate predicate = retrievePredicate( + c -> c.after(() -> BASE_TIME.plusHours(10))); + assertThat(predicate.test(BASE_TIME)).isFalse(); + } + + @Test + void isAfterValid() { + Predicate predicate = retrievePredicate( + c -> c.after(() -> BASE_TIME.minusHours(10))); + assertThat(predicate.test(BASE_TIME)).isTrue(); + } + + @Test + void isAfterExactInValid() { + Predicate predicate = retrievePredicate(c -> c.after(() -> BASE_TIME)); + assertThat(predicate.test(BASE_TIME)).isFalse(); + } + + @Test + void isBeforeOrEqualValid() { + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> BASE_TIME.plusHours(10))); + assertThat(predicate.test(BASE_TIME)).isTrue(); + } + + @Test + void isBeforeOrEqualInValid() { + LocalTime past = BASE_TIME.minusHours(10); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> past)); + assertThat(predicate.test(BASE_TIME)).isFalse(); + } + + @Test + void isBeforeOrEqualExactInValid() { + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> BASE_TIME)); + assertThat(predicate.test(BASE_TIME)).isTrue(); + } + + @Test + void isAfterOrEqualInValid() { + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> BASE_TIME.plusHours(10))); + assertThat(predicate.test(BASE_TIME)).isFalse(); + } + + @Test + void isAfterOrEqualValid() { + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> BASE_TIME.minusHours(10))); + assertThat(predicate.test(BASE_TIME)).isTrue(); + } + + @Test + void isAfterOrEqualExactInValid() { + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> BASE_TIME)); + assertThat(predicate.test(BASE_TIME)).isTrue(); + } + + @ParameterizedTest + @MethodSource("validBetweenDates") + void isBetweenValid(LocalTime now, LocalTime rangeFrom, LocalTime rangeTo) { + Predicate predicate = retrievePredicate( + c -> c.between(() -> rangeFrom, () -> rangeTo)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBetweenExactInValid() { + Supplier nowSupplier = () -> BASE_TIME; + + Predicate predicate = retrievePredicate( + c -> c.between(nowSupplier, nowSupplier)); + assertThat(predicate.test(BASE_TIME)).isFalse(); + } + + @Test + void isBetweenInValidException() { + Predicate predicate = retrievePredicate(c -> c + .between(() -> BASE_TIME.plusHours(1), () -> BASE_TIME.minusHours(1))); + assertThatThrownBy(() -> predicate.test(BASE_TIME)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + + @Test + void temporalFieldValid() { + LocalTime value = LocalTime.of(10, 30, 0); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(MINUTE_OF_HOUR, s -> s % 15 == 0)); + assertThat(predicate.test(value)).isTrue(); + } + + @Test + void temporalFieldInValid() { + LocalTime value = LocalTime.of(10, 40, 0); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(MINUTE_OF_HOUR, s -> s % 15 == 0)); + assertThat(predicate.test(value)).isFalse(); + } + + private static Stream validBetweenDates() { + return IntStream.rangeClosed(1, 10).boxed().map(i -> Arguments.of(BASE_TIME, + BASE_TIME.minusHours(i), BASE_TIME.plusHours(i))); + } + + private static Predicate retrievePredicate( + Function, LocalTimeConstraint> constraint) { + return constraint.apply(new LocalTimeConstraint<>()).predicates().peekFirst() + .predicate(); + } + +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LongConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LongConstraintTest.java new file mode 100644 index 0000000..4c8d949 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/LongConstraintTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class LongConstraintTest { + + @ParameterizedTest + @ValueSource(longs = { 101L, 150L }) + void validGreaterThan(long value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(100L)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(longs = { 100L, -50L }) + void invalidGreaterThan(long value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan(100L)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(longs = { 101L, 100L }) + void validGreaterThanOrEqual(long value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(100L)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(longs = { 99L, -50L }) + void invalidGreaterThanOrEqual(long value) { + Predicate predicate = retrievePredicate(c -> c.greaterThanOrEqual(100L)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(longs = { 99L, -50L }) + void validLessThan(long value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(100L)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(longs = { 100L, 150L }) + void invalidLessThan(long value) { + Predicate predicate = retrievePredicate(c -> c.lessThan(100L)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(longs = { 99L, 100L }) + void validLessThanOrEqual(long value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(100L)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(longs = { 101L, 150L }) + void invalidLessThanOrEqual(long value) { + Predicate predicate = retrievePredicate(c -> c.lessThanOrEqual(100L)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(longs = { 101L, 150L }) + void validPositive(long value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(longs = { -101L, -150L, 0L }) + void invalidPositive(long value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(longs = { 9L, 100L, 0L }) + void invalidNegative(long value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(longs = { -100L, -10L }) + void validNegative(long value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(longs = { 99, 100, 0 }) + void validPositiveOrZero(long value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(longs = { -101, -12 }) + void invalidPositiveOrZero(long value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(longs = { 99, 100 }) + void invalidNegativeOrZero(long value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(longs = { -101, -120, 0 }) + void validNegativeOrZero(long value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + private static Predicate retrievePredicate( + Function, LongConstraint> constraint) { + return constraint.apply(new LongConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/MapConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/MapConstraintTest.java new file mode 100644 index 0000000..be40bee --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/MapConstraintTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MapConstraintTest { + + @Test + void containsKey() { + Predicate> predicate = retrievePredicate( + c -> c.containsKey("foo")); + assertThat(predicate.test(Collections.singletonMap("foo", "bar"))).isTrue(); + assertThat(predicate.test(Collections.singletonMap("bar", "baz"))).isFalse(); + } + + @Test + void containsValue() { + Predicate> predicate = retrievePredicate( + c -> c.containsValue("bar")); + assertThat(predicate.test(Collections.singletonMap("foo", "bar"))).isTrue(); + assertThat(predicate.test(Collections.singletonMap("foo", "baz"))).isFalse(); + } + + @Test + void fixedSize() { + Predicate> predicate = retrievePredicate(c -> c.fixedSize(2)); + assertThat(predicate.test(Collections.singletonMap("foo", "bar"))).isFalse(); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + } + })).isTrue(); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + put("c", "d"); + } + })).isFalse(); + } + + @Test + void greaterThan() { + Predicate> predicate = retrievePredicate( + c -> c.greaterThan(2)); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + } + })).isFalse(); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + put("c", "d"); + } + })).isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate> predicate = retrievePredicate( + c -> c.greaterThanOrEqual(2)); + assertThat(predicate.test(Collections.singletonMap("foo", "bar"))).isFalse(); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + } + })).isTrue(); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + put("c", "d"); + } + })).isTrue(); + } + + @Test + void lessThan() { + Predicate> predicate = retrievePredicate(c -> c.lessThan(2)); + assertThat(predicate.test(Collections.singletonMap("foo", "bar"))).isTrue(); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + } + })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate> predicate = retrievePredicate( + c -> c.lessThanOrEqual(2)); + assertThat(predicate.test(Collections.singletonMap("foo", "bar"))).isTrue(); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + } + })).isTrue(); + assertThat(predicate.test(new HashMap() { + { + put("a", "b"); + put("b", "c"); + put("c", "d"); + } + })).isFalse(); + } + + @Test + void notEmpty() { + Predicate> predicate = retrievePredicate(c -> c.notEmpty()); + assertThat(predicate.test(Collections.singletonMap("foo", "bar"))).isTrue(); + assertThat(predicate.test(Collections.emptyMap())).isFalse(); + } + + private static Predicate> retrievePredicate( + Function, String, String>, MapConstraint, String, String>> constraint) { + return constraint.apply(new MapConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ObjectConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ObjectConstraintTest.java new file mode 100644 index 0000000..fabcaed --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ObjectConstraintTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.function.Predicate; + +import org.xbib.datastructures.validation.Color; +import org.xbib.datastructures.validation.Message; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolation; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.Validator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.jsr305.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ObjectConstraintTest { + private final ObjectConstraint constraint = new ObjectConstraint<>(); + + @Test + void isNull() { + Predicate predicate = constraint.isNull().predicates().getFirst() + .predicate(); + assertThat(predicate.test("foo")).isFalse(); + assertThat(predicate.test(null)).isTrue(); + } + + @Test + void notNull() { + Predicate predicate = constraint.notNull().predicates().getFirst() + .predicate(); + assertThat(predicate.test("foo")).isTrue(); + assertThat(predicate.test(null)).isFalse(); + } + + @Nested + class EqualTo { + @Test + void equalObjectsSucceed() { + assertThat(equalToPredicate("other").test("other")).isTrue(); + } + + @Test + void differentObjectsFail() { + assertThat(equalToPredicate("other").test("this")).isFalse(); + } + + @Test + void nullInputFails() { + assertThat(equalToPredicate("other").test(null)).isFalse(); + } + + @Test + void equalToNullFailsWithNonNullInput() { + assertThat(equalToPredicate(null).test("this")).isFalse(); + } + + @Test + void equalToNullSucceedsWithNullInput() { + assertThat(equalToPredicate(null).test(null)).isTrue(); + } + + private Predicate equalToPredicate(@Nullable String other) { + return constraint.equalTo(other).predicates().getFirst().predicate(); + } + } + + @Nested + class OneOf { + @Test + void succeedsWhenInputIsOneOfValues() { + assertThat(oneOfPredicate(Arrays.asList("this", "other")).test("this")) + .isTrue(); + } + + @Test + void failsWhenInputIsNoneOfValues() { + assertThat(oneOfPredicate(Arrays.asList("this", "other")).test("unknown")) + .isFalse(); + } + + @Test + void failsWhenInputIsNull() { + assertThat(oneOfPredicate(Arrays.asList("this", "other")).test(null)) + .isFalse(); + } + + @Test + void succeedsWhenInputIsNullAndNullIsOneOfTheValues() { + assertThat(oneOfPredicate(Arrays.asList("this", null)).test(null)).isTrue(); + } + + @Test + void nullValuesIsNotAllowed() { + assertThatThrownBy(() -> oneOfPredicate(null)) + .isExactlyInstanceOf(NullPointerException.class); + } + + @Test + void violatedOneOfArgumentsAreRenderedCorrectly() { + final Validator validator = ValidatorBuilder. of() + .constraint(Message::getText, "text", + c -> c.oneOf(Arrays.asList("a", "b"))) + ._object(Message::getColor, "color", + c -> c.oneOf(EnumSet.of(Color.RED, Color.BLUE))) + .build(); + final ConstraintViolations violations = validator + .validate(new Message("c", Color.GREEN)); + assertThat(violations).extracting(ConstraintViolation::message) + .containsExactlyInAnyOrder( + "\"text\" must be one of the following values: [a, b]", + "\"color\" must be one of the following values: [RED, BLUE]"); + } + + private Predicate oneOfPredicate(List values) { + return constraint.oneOf(values).predicates().getFirst().predicate(); + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/OffsetDateTimeConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/OffsetDateTimeConstraintTest.java new file mode 100644 index 0000000..98207f7 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/OffsetDateTimeConstraintTest.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static java.time.DayOfWeek.SATURDAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class OffsetDateTimeConstraintTest { + @Test + void isPastValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(OffsetDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isPastInValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(OffsetDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isPastExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.past(clock)); + assertThat(predicate.test(OffsetDateTime.now(clock))).isFalse(); + } + + @Test + void isPastOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(OffsetDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isPastOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(OffsetDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isPastOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate( + c -> c.pastOrPresent(clock)); + assertThat(predicate.test(OffsetDateTime.now(clock))).isTrue(); + } + + @Test + void isFutureValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(OffsetDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isFutureInValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(OffsetDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isFutureExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.future(clock)); + assertThat(predicate.test(OffsetDateTime.now(clock))).isFalse(); + } + + @Test + void isFutureOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(OffsetDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isFutureOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(OffsetDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isFutureOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate( + c -> c.futureOrPresent(clock)); + assertThat(predicate.test(OffsetDateTime.now(clock))).isTrue(); + } + + @Test + void isBeforeValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.before(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeInValid() { + OffsetDateTime now = OffsetDateTime.now(); + OffsetDateTime past = now.minusDays(10); + Predicate predicate = retrievePredicate( + c -> c.before(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeExactInValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate(c -> c.before(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterInValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.minusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterExactInValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate(c -> c.after(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeOrEqualInValid() { + OffsetDateTime now = OffsetDateTime.now(); + OffsetDateTime past = now.minusDays(10); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualExactInValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualInValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterOrEqualValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.minusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualExactInValid() { + OffsetDateTime now = OffsetDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @ParameterizedTest + @MethodSource("validBetweenDates") + void isBetweenValid(OffsetDateTime now, OffsetDateTime rangeFrom, + OffsetDateTime rangeTo) { + Predicate predicate = retrievePredicate( + c -> c.between(() -> rangeFrom, () -> rangeTo)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBetweenExactInValid() { + OffsetDateTime now = OffsetDateTime.now(); + Supplier nowSupplier = () -> now; + + Predicate predicate = retrievePredicate( + c -> c.between(nowSupplier, nowSupplier)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBetweenInValidException() { + OffsetDateTime now = OffsetDateTime.now(); + + Predicate predicate = retrievePredicate( + c -> c.between(() -> now.plusDays(1), () -> now.minusDays(1))); + assertThatThrownBy(() -> predicate.test(now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + + @Test + void temporalFieldValid() { + OffsetDateTime value = OffsetDateTime.of(2022, 1, 1, 0, 0, 0, 0, + ZoneOffset.ofHours(9)); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(DAY_OF_WEEK, week -> week == SATURDAY.getValue())); + assertThat(predicate.test(value)).isTrue(); + } + + @Test + void temporalFieldInValid() { + OffsetDateTime value = OffsetDateTime.of(2022, 1, 2, 0, 0, 0, 0, + ZoneOffset.ofHours(9)); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(DAY_OF_WEEK, week -> week == SATURDAY.getValue())); + assertThat(predicate.test(value)).isFalse(); + } + + private static Stream validBetweenDates() { + List validBetweenZones = IntStream.rangeClosed(1, 10).boxed() + .map(i -> ZoneId.SHORT_IDS.values().stream().map(ZoneId::of) + .map(OffsetDateTime::now) + .map(offsetDateTime -> Arguments.of(offsetDateTime, + offsetDateTime.minusHours(i), + offsetDateTime.plusHours(i))) + .collect(Collectors.toList())) + .flatMap(List::stream).collect(Collectors.toList()); + return validBetweenZones.stream(); + } + + private static Predicate retrievePredicate( + Function, OffsetDateTimeConstraint> constraint) { + return constraint.apply(new OffsetDateTimeConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ShortConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ShortConstraintTest.java new file mode 100644 index 0000000..a487ea6 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ShortConstraintTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class ShortConstraintTest { + + @ParameterizedTest + @ValueSource(shorts = { 101, 150 }) + void validGreaterThan(short value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan((short) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(shorts = { 100, -50 }) + void invalidGreaterThan(short value) { + Predicate predicate = retrievePredicate(c -> c.greaterThan((short) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(shorts = { 101, 100 }) + void validGreaterThanOrEqual(short value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual((short) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(shorts = { 99, -50 }) + void invalidGreaterThanOrEqual(short value) { + Predicate predicate = retrievePredicate( + c -> c.greaterThanOrEqual((short) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(shorts = { 99, -50 }) + void validLessThan(short value) { + Predicate predicate = retrievePredicate(c -> c.lessThan((short) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(shorts = { 100, 150 }) + void invalidLessThan(short value) { + Predicate predicate = retrievePredicate(c -> c.lessThan((short) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(shorts = { 99, 100 }) + void validLessThanOrEqual(short value) { + Predicate predicate = retrievePredicate( + c -> c.lessThanOrEqual((short) 100)); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(shorts = { 101, 150 }) + void invalidLessThanOrEqual(short value) { + Predicate predicate = retrievePredicate( + c -> c.lessThanOrEqual((short) 100)); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(shorts = { 101, 10 }) + void validPositive(short value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(shorts = { -101, -10, 0 }) + void invalidPositive(short value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::positive); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(shorts = { 9, 100, 0 }) + void invalidNegative(short value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(shorts = { -100, -10 }) + void validNegative(short value) { + Predicate predicate = retrievePredicate(NumericConstraintBase::negative); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(shorts = { 99, 100, 0 }) + void validPositiveOrZero(short value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + @ParameterizedTest + @ValueSource(shorts = { -101, -12 }) + void invalidPositiveOrZero(short value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::positiveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(shorts = { 99, 100 }) + void invalidNegativeOrZero(short value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isFalse(); + } + + @ParameterizedTest + @ValueSource(shorts = { -101, -120, 0 }) + void validNegativeOrZero(short value) { + Predicate predicate = retrievePredicate( + NumericConstraintBase::negaitveOrZero); + assertThat(predicate.test(value)).isTrue(); + } + + private static Predicate retrievePredicate( + Function, ShortConstraint> constraint) { + return constraint.apply(new ShortConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/YearConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/YearConstraintTest.java new file mode 100644 index 0000000..ecca349 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/YearConstraintTest.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; +import java.time.Year; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import static java.time.temporal.ChronoField.YEAR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class YearConstraintTest { + + @Test + void isPastValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(Year.now().minus(60, ChronoUnit.YEARS))).isTrue(); + } + + @Test + void isPastInValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(Year.now().plus(60, ChronoUnit.YEARS))).isFalse(); + } + + @Test + void isPastExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.past(clock)); + assertThat(predicate.test(Year.now(clock))).isFalse(); + } + + @Test + void isPastOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(Year.now().minus(60, ChronoUnit.YEARS))).isTrue(); + } + + @Test + void isPastOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(Year.now().plus(60, ChronoUnit.YEARS))).isFalse(); + } + + @Test + void isPastOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.pastOrPresent(clock)); + assertThat(predicate.test(Year.now(clock))).isTrue(); + } + + @Test + void isFutureValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(Year.now().plus(60, ChronoUnit.YEARS))).isTrue(); + } + + @Test + void isFutureInValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(Year.now().minus(60, ChronoUnit.YEARS))).isFalse(); + } + + @Test + void isFutureExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.future(clock)); + assertThat(predicate.test(Year.now(clock))).isFalse(); + } + + @Test + void isFutureOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(Year.now().plus(60, ChronoUnit.YEARS))).isTrue(); + } + + @Test + void isFutureOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(Year.now().minus(60, ChronoUnit.YEARS))).isFalse(); + } + + @Test + void isFutureOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.futureOrPresent(clock)); + assertThat(predicate.test(Year.now(clock))).isTrue(); + } + + @Test + void isBeforeValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate( + c -> c.before(() -> now.plusYears(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeInValid() { + Year now = Year.now(); + Year past = now.minusYears(10); + Predicate predicate = retrievePredicate(c -> c.before(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeExactInValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate(c -> c.before(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterInValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.plusYears(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.minusYears(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterExactInValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate(c -> c.after(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now.plusYears(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeOrEqualInValid() { + Year now = Year.now(); + Year past = now.minusYears(10); + Predicate predicate = retrievePredicate(c -> c.beforeOrEqual(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualExactInValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate(c -> c.beforeOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualInValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.plusYears(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterOrEqualValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.minusYears(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualExactValid() { + Year now = Year.now(); + Predicate predicate = retrievePredicate(c -> c.afterOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBetweenExactInValid() { + Year now = Year.now(); + Supplier nowSupplier = () -> now; + + Predicate predicate = retrievePredicate( + c -> c.between(nowSupplier, nowSupplier)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBetweenInValidException() { + Year now = Year.now(); + + Predicate predicate = retrievePredicate( + c -> c.between(() -> now.plusYears(1), () -> now.minusYears(1))); + assertThatThrownBy(() -> predicate.test(now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + + @Test + void temporalFieldValid() { + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(YEAR, s -> s >= 0)); + assertThat(predicate.test(Year.of(999_999_999))).isTrue(); + } + + private static Predicate retrievePredicate( + Function, YearConstraint> constraint) { + return constraint.apply(new YearConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/YearMonthConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/YearMonthConstraintTest.java new file mode 100644 index 0000000..c9a1570 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/YearMonthConstraintTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class YearMonthMonthConstraintTest { + + @Test + void isPastValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(YearMonth.now().minus(60, ChronoUnit.YEARS))).isTrue(); + } + + @Test + void isPastInValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(YearMonth.now().plus(60, ChronoUnit.YEARS))).isFalse(); + } + + @Test + void isPastExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.past(clock)); + assertThat(predicate.test(YearMonth.now(clock))).isFalse(); + } + + @Test + void isPastOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(YearMonth.now().minus(60, ChronoUnit.YEARS))).isTrue(); + } + + @Test + void isPastOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(YearMonth.now().plus(60, ChronoUnit.YEARS))).isFalse(); + } + + @Test + void isPastOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.pastOrPresent(clock)); + assertThat(predicate.test(YearMonth.now(clock))).isTrue(); + } + + @Test + void isFutureValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(YearMonth.now().plus(60, ChronoUnit.YEARS))).isTrue(); + } + + @Test + void isFutureInValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(YearMonth.now().minus(60, ChronoUnit.YEARS))).isFalse(); + } + + @Test + void isFutureExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.future(clock)); + assertThat(predicate.test(YearMonth.now(clock))).isFalse(); + } + + @Test + void isFutureOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(YearMonth.now().plus(60, ChronoUnit.YEARS))).isTrue(); + } + + @Test + void isFutureOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(YearMonth.now().minus(60, ChronoUnit.YEARS))).isFalse(); + } + + @Test + void isFutureOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.futureOrPresent(clock)); + assertThat(predicate.test(YearMonth.now(clock))).isTrue(); + } + + @Test + void isBeforeValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate( + c -> c.before(() -> now.plusMonths(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeInValid() { + YearMonth now = YearMonth.now(); + YearMonth past = now.minusMonths(10); + Predicate predicate = retrievePredicate(c -> c.before(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeExactInValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate(c -> c.before(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterInValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.plusMonths(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.minusMonths(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterExactInValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate(c -> c.after(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now.plusMonths(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeOrEqualInValid() { + YearMonth now = YearMonth.now(); + YearMonth past = now.minusMonths(10); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualExactInValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualInValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.plusMonths(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterOrEqualValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.minusMonths(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualExactValid() { + YearMonth now = YearMonth.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBetweenExactInValid() { + YearMonth now = YearMonth.now(); + Supplier nowSupplier = () -> now; + + Predicate predicate = retrievePredicate( + c -> c.between(nowSupplier, nowSupplier)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBetweenInValidException() { + YearMonth now = YearMonth.now(); + + Predicate predicate = retrievePredicate( + c -> c.between(() -> now.plusMonths(1), () -> now.minusMonths(1))); + assertThatThrownBy(() -> predicate.test(now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + + @Test + void temporalFieldValid() { + OffsetDateTime value = OffsetDateTime.of(1970, 1, 1, 0, 0, 0, 0, + ZoneOffset.ofHours(0)); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(MONTH_OF_YEAR, s -> s >= 0)); + assertThat(predicate.test(YearMonth.from(value))).isTrue(); + } + + private static Predicate retrievePredicate( + Function, YearMonthConstraint> constraint) { + return constraint.apply(new YearMonthConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ZonedDateTimeConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ZonedDateTimeConstraintTest.java new file mode 100644 index 0000000..563ff26 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/ZonedDateTimeConstraintTest.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static java.time.DayOfWeek.SATURDAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ZonedDateTimeConstraintTest { + @Test + void isPastValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(ZonedDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isPastInValid() { + Predicate predicate = retrievePredicate(c -> c.past()); + assertThat(predicate.test(ZonedDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isPastExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.past(clock)); + assertThat(predicate.test(ZonedDateTime.now(clock))).isFalse(); + } + + @Test + void isPastOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(ZonedDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isPastOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.pastOrPresent()); + assertThat(predicate.test(ZonedDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isPastOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate( + c -> c.pastOrPresent(clock)); + assertThat(predicate.test(ZonedDateTime.now(clock))).isTrue(); + } + + @Test + void isFutureValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(ZonedDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isFutureInValid() { + Predicate predicate = retrievePredicate(c -> c.future()); + assertThat(predicate.test(ZonedDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isFutureExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate(c -> c.future(clock)); + assertThat(predicate.test(ZonedDateTime.now(clock))).isFalse(); + } + + @Test + void isFutureOrPresentValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(ZonedDateTime.now().plus(60, ChronoUnit.SECONDS))) + .isTrue(); + } + + @Test + void isFutureOrPresentInValid() { + Predicate predicate = retrievePredicate(c -> c.futureOrPresent()); + assertThat(predicate.test(ZonedDateTime.now().minus(60, ChronoUnit.SECONDS))) + .isFalse(); + } + + @Test + void isFutureOrPresentExactInValid() { + Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + Predicate predicate = retrievePredicate( + c -> c.futureOrPresent(clock)); + assertThat(predicate.test(ZonedDateTime.now(clock))).isTrue(); + } + + @Test + void isBeforeValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.before(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeInValid() { + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime past = now.minusDays(10); + Predicate predicate = retrievePredicate(c -> c.before(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeExactInValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate(c -> c.before(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterInValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.after(() -> now.minusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterExactInValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate(c -> c.after(() -> now)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBeforeOrEqualInValid() { + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime past = now.minusDays(10); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> past)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBeforeOrEqualExactInValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.beforeOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualInValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.plusDays(10))); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isAfterOrEqualValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now.minusDays(10))); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isAfterOrEqualExactInValid() { + ZonedDateTime now = ZonedDateTime.now(); + Predicate predicate = retrievePredicate( + c -> c.afterOrEqual(() -> now)); + assertThat(predicate.test(now)).isTrue(); + } + + @ParameterizedTest + @MethodSource("validBetweenDates") + void isBetweenValid(ZonedDateTime now, ZonedDateTime rangeFrom, + ZonedDateTime rangeTo) { + Predicate predicate = retrievePredicate( + c -> c.between(() -> rangeFrom, () -> rangeTo)); + assertThat(predicate.test(now)).isTrue(); + } + + @Test + void isBetweenExactInValid() { + ZonedDateTime now = ZonedDateTime.now(); + Supplier nowSupplier = () -> now; + + Predicate predicate = retrievePredicate( + c -> c.between(nowSupplier, nowSupplier)); + assertThat(predicate.test(now)).isFalse(); + } + + @Test + void isBetweenInValidException() { + ZonedDateTime now = ZonedDateTime.now(); + + Predicate predicate = retrievePredicate( + c -> c.between(() -> now.plusDays(1), () -> now.minusDays(1))); + assertThatThrownBy(() -> predicate.test(now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Parameter 'rangeFrom' has to be before 'rangeTo'"); + } + + @Test + void temporalFieldValid() { + ZonedDateTime value = ZonedDateTime.of(2022, 1, 1, 0, 0, 0, 0, + ZoneOffset.ofHours(9)); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(DAY_OF_WEEK, week -> week == SATURDAY.getValue())); + assertThat(predicate.test(value)).isTrue(); + } + + @Test + void temporalFieldInValid() { + ZonedDateTime value = ZonedDateTime.of(2022, 1, 2, 0, 0, 0, 0, + ZoneOffset.ofHours(9)); + Predicate predicate = retrievePredicate( + c -> c.fieldPredicate(DAY_OF_WEEK, week -> week == SATURDAY.getValue())); + assertThat(predicate.test(value)).isFalse(); + } + + private static Stream validBetweenDates() { + List validBetweenZones = IntStream.rangeClosed(1, 10).boxed() + .map(i -> ZoneId.SHORT_IDS.values().stream().map(ZoneId::of) + .map(ZonedDateTime::now) + .map(zonedDateTime -> Arguments.of(zonedDateTime, + zonedDateTime.minusHours(i), zonedDateTime.plusHours(i))) + .collect(Collectors.toList())) + .flatMap(List::stream).collect(Collectors.toList()); + return validBetweenZones.stream(); + } + + private static Predicate retrievePredicate( + Function, ZonedDateTimeConstraint> constraint) { + return constraint.apply(new ZonedDateTimeConstraint<>()).predicates().peekFirst() + .predicate(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ByteArrayConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ByteArrayConstraintTest.java new file mode 100644 index 0000000..0e9564e --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ByteArrayConstraintTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import org.junit.jupiter.api.Test; + +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class ByteArrayConstraintTest { + private ByteArrayConstraint constraint = new ByteArrayConstraint<>(); + + @Test + void contains() { + Predicate predicate = constraint.contains((byte) 100).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101 })).isTrue(); + assertThat(predicate.test(new byte[] { (byte) 101, (byte) 102 })).isFalse(); + } + + @Test + void fixedSize() { + Predicate predicate = constraint.fixedSize(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new byte[] { (byte) 100 })).isFalse(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101 })).isTrue(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101, (byte) 102 })) + .isFalse(); + } + + @Test + void greaterThan() { + Predicate predicate = constraint.greaterThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101 })).isFalse(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101, (byte) 102 })) + .isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.greaterThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new byte[] { (byte) 100 })).isFalse(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101 })).isTrue(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101, (byte) 102 })) + .isTrue(); + } + + @Test + void lessThan() { + Predicate predicate = constraint.lessThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new byte[] { (byte) 100 })).isTrue(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101 })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.lessThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new byte[] { (byte) 100 })).isTrue(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101 })).isTrue(); + assertThat(predicate.test(new byte[] { (byte) 100, (byte) 101, (byte) 102 })) + .isFalse(); + } + + @Test + void notEmpty() { + Predicate predicate = constraint.notEmpty().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new byte[] { (byte) 100 })).isTrue(); + assertThat(predicate.test(new byte[] {})).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/CharArrayConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/CharArrayConstraintTest.java new file mode 100644 index 0000000..ecf46f8 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/CharArrayConstraintTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import org.junit.jupiter.api.Test; + +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class CharArrayConstraintTest { + private CharArrayConstraint constraint = new CharArrayConstraint<>(); + + @Test + void contains() { + Predicate predicate = constraint.contains((char) 100).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101 })).isTrue(); + assertThat(predicate.test(new char[] { (char) 101, (char) 102 })).isFalse(); + } + + @Test + void fixedSize() { + Predicate predicate = constraint.fixedSize(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new char[] { (char) 100 })).isFalse(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101 })).isTrue(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101, (char) 102 })) + .isFalse(); + } + + @Test + void greaterThan() { + Predicate predicate = constraint.greaterThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101 })).isFalse(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101, (char) 102 })) + .isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.greaterThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new char[] { (char) 100 })).isFalse(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101 })).isTrue(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101, (char) 102 })) + .isTrue(); + } + + @Test + void lessThan() { + Predicate predicate = constraint.lessThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new char[] { (char) 100 })).isTrue(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101 })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.lessThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new char[] { (char) 100 })).isTrue(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101 })).isTrue(); + assertThat(predicate.test(new char[] { (char) 100, (char) 101, (char) 102 })) + .isFalse(); + } + + @Test + void notEmpty() { + Predicate predicate = constraint.notEmpty().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new char[] { (char) 100 })).isTrue(); + assertThat(predicate.test(new char[] {})).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/DoubleArrayConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/DoubleArrayConstraintTest.java new file mode 100644 index 0000000..7c44887 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/DoubleArrayConstraintTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import org.junit.jupiter.api.Test; + +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class DoubleArrayConstraintTest { + private DoubleArrayConstraint constraint = new DoubleArrayConstraint<>(); + + @Test + void contains() { + Predicate predicate = constraint.contains(100.0).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new double[] { 100.0, 101.0 })).isTrue(); + assertThat(predicate.test(new double[] { 101.0, 102.0 })).isFalse(); + } + + @Test + void fixedSize() { + Predicate predicate = constraint.fixedSize(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new double[] { 100.0 })).isFalse(); + assertThat(predicate.test(new double[] { 100.0, 101.0 })).isTrue(); + assertThat(predicate.test(new double[] { 100.0, 101.0, 102.0 })).isFalse(); + } + + @Test + void greaterThan() { + Predicate predicate = constraint.greaterThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new double[] { 100.0, 101.0 })).isFalse(); + assertThat(predicate.test(new double[] { 100.0, 101.0, 102.0 })).isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.greaterThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new double[] { 100.0 })).isFalse(); + assertThat(predicate.test(new double[] { 100.0, 101.0 })).isTrue(); + assertThat(predicate.test(new double[] { 100.0, 101.0, 102.0 })).isTrue(); + } + + @Test + void lessThan() { + Predicate predicate = constraint.lessThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new double[] { 100.0 })).isTrue(); + assertThat(predicate.test(new double[] { 100.0, 101.0 })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.lessThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new double[] { 100.0 })).isTrue(); + assertThat(predicate.test(new double[] { 100.0, 101.0 })).isTrue(); + assertThat(predicate.test(new double[] { 100.0, 101.0, 102.0 })).isFalse(); + } + + @Test + void notEmpty() { + Predicate predicate = constraint.notEmpty().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new double[] { 100.0 })).isTrue(); + assertThat(predicate.test(new double[] {})).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/FloatArrayConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/FloatArrayConstraintTest.java new file mode 100644 index 0000000..d04502e --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/FloatArrayConstraintTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import org.junit.jupiter.api.Test; + +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class FloatArrayConstraintTest { + private FloatArrayConstraint constraint = new FloatArrayConstraint<>(); + + @Test + void contains() { + Predicate predicate = constraint.contains(100.0f).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f })).isTrue(); + assertThat(predicate.test(new float[] { 101.0f, 102.0f })).isFalse(); + } + + @Test + void fixedSize() { + Predicate predicate = constraint.fixedSize(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new float[] { 100.0f })).isFalse(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f })).isTrue(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f, 102.0f })).isFalse(); + } + + @Test + void greaterThan() { + Predicate predicate = constraint.greaterThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f })).isFalse(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f, 102.0f })).isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.greaterThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new float[] { 100.0f })).isFalse(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f })).isTrue(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f, 102.0f })).isTrue(); + } + + @Test + void lessThan() { + Predicate predicate = constraint.lessThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new float[] { 100.0f })).isTrue(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.lessThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new float[] { 100.0f })).isTrue(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f })).isTrue(); + assertThat(predicate.test(new float[] { 100.0f, 101.0f, 102.0f })).isFalse(); + } + + @Test + void notEmpty() { + Predicate predicate = constraint.notEmpty().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new float[] { 100.0f })).isTrue(); + assertThat(predicate.test(new float[] {})).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/IntArrayConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/IntArrayConstraintTest.java new file mode 100644 index 0000000..5578b39 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/IntArrayConstraintTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import org.junit.jupiter.api.Test; + +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class IntArrayConstraintTest { + private IntArrayConstraint constraint = new IntArrayConstraint<>(); + + @Test + void contains() { + Predicate predicate = constraint.contains(100).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new int[] { 100, 101 })).isTrue(); + assertThat(predicate.test(new int[] { 101, 102 })).isFalse(); + } + + @Test + void fixedSize() { + Predicate predicate = constraint.fixedSize(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new int[] { 100 })).isFalse(); + assertThat(predicate.test(new int[] { 100, 101 })).isTrue(); + assertThat(predicate.test(new int[] { 100, 101, 102 })).isFalse(); + } + + @Test + void greaterThan() { + Predicate predicate = constraint.greaterThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new int[] { 100, 101 })).isFalse(); + assertThat(predicate.test(new int[] { 100, 101, 102 })).isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.greaterThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new int[] { 100 })).isFalse(); + assertThat(predicate.test(new int[] { 100, 101 })).isTrue(); + assertThat(predicate.test(new int[] { 100, 101, 102 })).isTrue(); + } + + @Test + void lessThan() { + Predicate predicate = constraint.lessThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new int[] { 100 })).isTrue(); + assertThat(predicate.test(new int[] { 100, 101 })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.lessThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new int[] { 100 })).isTrue(); + assertThat(predicate.test(new int[] { 100, 101 })).isTrue(); + assertThat(predicate.test(new int[] { 100, 101, 102 })).isFalse(); + } + + @Test + void notEmpty() { + Predicate predicate = constraint.notEmpty().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new int[] { 100 })).isTrue(); + assertThat(predicate.test(new int[] {})).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/LongArrayConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/LongArrayConstraintTest.java new file mode 100644 index 0000000..ce13654 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/LongArrayConstraintTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class LongArrayConstraintTest { + private LongArrayConstraint constraint = new LongArrayConstraint<>(); + + @Test + void contains() { + Predicate predicate = constraint.contains(100L).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new long[] { 100L, 101L })).isTrue(); + assertThat(predicate.test(new long[] { 101L, 102L })).isFalse(); + } + + @Test + void fixedSize() { + Predicate predicate = constraint.fixedSize(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new long[] { 100L })).isFalse(); + assertThat(predicate.test(new long[] { 100L, 101L })).isTrue(); + assertThat(predicate.test(new long[] { 100L, 101L, 102L })).isFalse(); + } + + @Test + void greaterThan() { + Predicate predicate = constraint.greaterThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new long[] { 100L, 101L })).isFalse(); + assertThat(predicate.test(new long[] { 100L, 101L, 102L })).isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.greaterThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new long[] { 100L })).isFalse(); + assertThat(predicate.test(new long[] { 100L, 101L })).isTrue(); + assertThat(predicate.test(new long[] { 100L, 101L, 102L })).isTrue(); + } + + @Test + void lessThan() { + Predicate predicate = constraint.lessThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new long[] { 100L })).isTrue(); + assertThat(predicate.test(new long[] { 100L, 101L })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.lessThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new long[] { 100L })).isTrue(); + assertThat(predicate.test(new long[] { 100L, 101L })).isTrue(); + assertThat(predicate.test(new long[] { 100L, 101L, 102L })).isFalse(); + } + + @Test + void notEmpty() { + Predicate predicate = constraint.notEmpty().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new long[] { 100L })).isTrue(); + assertThat(predicate.test(new long[] {})).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ObjectArrayConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ObjectArrayConstraintTest.java new file mode 100644 index 0000000..95dd368 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ObjectArrayConstraintTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import org.junit.jupiter.api.Test; + +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +class ObjectArrayConstraintTest { + private ObjectArrayConstraint constraint = new ObjectArrayConstraint<>(); + + @Test + void contains() { + Predicate predicate = constraint.contains("foo").predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new String[] { "foo", "bar" })).isTrue(); + assertThat(predicate.test(new String[] { "bar", "baz" })).isFalse(); + } + + @Test + void fixedSize() { + Predicate predicate = constraint.fixedSize(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new String[] { "foo" })).isFalse(); + assertThat(predicate.test(new String[] { "foo", "bar" })).isTrue(); + assertThat(predicate.test(new String[] { "foo", "bar", "baz" })).isFalse(); + } + + @Test + void greaterThan() { + Predicate predicate = constraint.greaterThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new String[] { "foo", "bar" })).isFalse(); + assertThat(predicate.test(new String[] { "foo", "bar", "baz" })).isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.greaterThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new String[] { "foo" })).isFalse(); + assertThat(predicate.test(new String[] { "foo", "bar" })).isTrue(); + assertThat(predicate.test(new String[] { "foo", "bar", "baz" })).isTrue(); + } + + @Test + void lessThan() { + Predicate predicate = constraint.lessThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new String[] { "foo" })).isTrue(); + assertThat(predicate.test(new String[] { "foo", "bar" })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.lessThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new String[] { "foo" })).isTrue(); + assertThat(predicate.test(new String[] { "foo", "bar" })).isTrue(); + assertThat(predicate.test(new String[] { "foo", "bar", "baz" })).isFalse(); + } + + @Test + void notEmpty() { + Predicate predicate = constraint.notEmpty().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new String[] { "foo" })).isTrue(); + assertThat(predicate.test(new String[] {})).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ShortArrayConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ShortArrayConstraintTest.java new file mode 100644 index 0000000..7992d95 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/array/ShortArrayConstraintTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.array; + +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ShortArrayConstraintTest { + private ShortArrayConstraint constraint = new ShortArrayConstraint<>(); + + @Test + void contains() { + Predicate predicate = constraint.contains((short) 100).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101 })).isTrue(); + assertThat(predicate.test(new short[] { (short) 101, (short) 102 })).isFalse(); + } + + @Test + void fixedSize() { + Predicate predicate = constraint.fixedSize(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new short[] { (short) 100 })).isFalse(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101 })).isTrue(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101, (short) 102 })) + .isFalse(); + } + + @Test + void greaterThan() { + Predicate predicate = constraint.greaterThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101 })).isFalse(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101, (short) 102 })) + .isTrue(); + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.greaterThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new short[] { (short) 100 })).isFalse(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101 })).isTrue(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101, (short) 102 })) + .isTrue(); + } + + @Test + void lessThan() { + Predicate predicate = constraint.lessThan(2).predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new short[] { (short) 100 })).isTrue(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101 })).isFalse(); + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.lessThanOrEqual(2).predicates() + .peekFirst().predicate(); + assertThat(predicate.test(new short[] { (short) 100 })).isTrue(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101 })).isTrue(); + assertThat(predicate.test(new short[] { (short) 100, (short) 101, (short) 102 })) + .isFalse(); + } + + @Test + void notEmpty() { + Predicate predicate = constraint.notEmpty().predicates().peekFirst() + .predicate(); + assertThat(predicate.test(new short[] { (short) 100 })).isTrue(); + assertThat(predicate.test(new short[] {})).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/ByteSizeConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/ByteSizeConstraintTest.java new file mode 100644 index 0000000..89c90a5 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/ByteSizeConstraintTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; + +class ByteSizeConstraintTest { + private CharSequenceConstraint constraint = new CharSequenceConstraint<>(); + + @Test + void fixedSize() { + Predicate predicate = constraint.asByteArray().fixedSize(4).predicates() + .peekFirst().predicate(); + assertThat(predicate.test("abcd")).isTrue(); + assertThat(predicate.test("abc")).isFalse(); + assertThat(predicate.test("あ")).isFalse(); // 3 + assertThat(predicate.test("あa")).isTrue(); // 4 + assertThat(predicate.test("\uD842\uDFB7")).isTrue(); // 4 + } + + @Test + void greaterThan() { + Predicate predicate = constraint.asByteArray().greaterThan(3).predicates() + .peekFirst().predicate(); + assertThat(predicate.test("abcd")).isTrue(); + assertThat(predicate.test("abc")).isFalse(); + assertThat(predicate.test("あ")).isFalse(); // 3 + } + + @Test + void greaterThanOrEqual() { + Predicate predicate = constraint.asByteArray().greaterThanOrEqual(3) + .predicates().peekFirst().predicate(); + assertThat(predicate.test("abcd")).isTrue(); + assertThat(predicate.test("abc")).isTrue(); + assertThat(predicate.test("ab")).isFalse(); + assertThat(predicate.test("あ")).isTrue(); // 3 + } + + @Test + void lessThan() { + Predicate predicate = constraint.asByteArray().lessThan(3).predicates() + .peekFirst().predicate(); + assertThat(predicate.test("ab")).isTrue(); + assertThat(predicate.test("abc")).isFalse(); + assertThat(predicate.test("あ")).isFalse(); // 3 + } + + @Test + void lessThanOrEqual() { + Predicate predicate = constraint.asByteArray().lessThanOrEqual(3) + .predicates().peekFirst().predicate(); + assertThat(predicate.test("ab")).isTrue(); + assertThat(predicate.test("abc")).isTrue(); + assertThat(predicate.test("abcd")).isFalse(); + assertThat(predicate.test("あ")).isTrue(); // 3 + assertThat(predicate.test("あa")).isFalse(); // 4 + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/CodePointsConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/CodePointsConstraintTest.java new file mode 100644 index 0000000..79e5cd9 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/CodePointsConstraintTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.CodePointsRanges; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.CodePointsSet; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.Range; +import org.xbib.datastructures.validation.core.ConstraintPredicate; + +class CodePointsConstraintTest { + + @Test + void allIncludedRange() { + CodePointsRanges whiteList = () -> Arrays.asList( + Range.of(0x0041/* A */, 0x005A /* Z */), + Range.of(0x0061/* a */, 0x007A /* z */)); + + ConstraintPredicate predicate = new CharSequenceConstraint() + .codePoints(whiteList).asWhiteList().predicates().peekFirst(); + + assertThat(predicate.violatedValue("ABCD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("ABcD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("AbcD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("AbCD").get().value()) + .isEqualTo(Collections.singletonList("D")); + assertThat(predicate.violatedValue("AbあCD").get().value()) + .isEqualTo(Arrays.asList("あ", "D")); + } + + @Test + void allIncludedSet() { + CodePointsSet whiteList = () -> new HashSet<>( + Arrays.asList(0x0041 /* A */, 0x0042 /* B */, 0x0043 /* C */, + 0x0044 /* D */, 0x0045 /* E */, 0x0046 /* F */, 0x0047 /* G */, + 0x0048 /* H */, 0x0049 /* I */, 0x004A /* J */, 0x004B /* K */, + 0x004C /* L */, 0x004D /* M */, 0x004E /* N */, 0x004F /* O */, + 0x0050 /* P */, 0x0051 /* Q */, 0x0052 /* R */, 0x0053 /* S */, + 0x0054 /* T */, 0x0055 /* U */, 0x0056 /* V */, 0x0057 /* W */, + 0x0058 /* X */, 0x0059 /* Y */, 0x005A /* Z */, // + 0x0061 /* a */, 0x0062 /* b */, 0x0063 /* c */, 0x0064 /* d */, + 0x0065 /* e */, 0x0066 /* f */, 0x0067 /* g */, 0x0068 /* h */, + 0x0069 /* i */, 0x006A /* j */, 0x006B /* k */, 0x006C /* l */, + 0x006D /* m */, 0x006E /* n */, 0x006F /* o */, 0x0070 /* p */, + 0x0071 /* q */, 0x0072 /* r */, 0x0073 /* s */, 0x0074 /* t */, + 0x0075 /* u */, 0x0076 /* v */, 0x0077 /* w */, 0x0078 /* x */, + 0x0079 /* y */, 0x007A /* z */)); + + ConstraintPredicate predicate = new CharSequenceConstraint() + .codePoints(whiteList).asWhiteList().predicates().peekFirst(); + + assertThat(predicate.violatedValue("ABCD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("ABcD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("AbcD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("AbCD").get().value()) + .isEqualTo(Collections.singletonList("D")); + assertThat(predicate.violatedValue("AbあCD").get().value()) + .isEqualTo(Arrays.asList("あ", "D")); + } + + @Test + void notIncludedRange() { + CodePointsRanges blackList = () -> Arrays.asList( + Range.of(0x0041/* A */, 0x0042 /* B */), + Range.of(0x0061/* a */, 0x0062 /* b */)); + + ConstraintPredicate predicate = new CharSequenceConstraint() + .codePoints(blackList).asBlackList().predicates().peekFirst(); + + assertThat(predicate.violatedValue("CD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("cd").isPresent()).isFalse(); + assertThat(predicate.violatedValue("ABCD").get().value()) + .isEqualTo(Arrays.asList("A", "B")); + assertThat(predicate.violatedValue("AbCD").get().value()) + .isEqualTo(Arrays.asList("A", "b")); + } + + @Test + void notIncludedSet() { + CodePointsSet blackList = () -> new HashSet<>( + Arrays.asList(0x0041 /* A */, 0x0042 /* B */)); + + ConstraintPredicate predicate = new CharSequenceConstraint() + .codePoints(blackList).asBlackList().predicates().peekFirst(); + + assertThat(predicate.violatedValue("CD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("ab").isPresent()).isFalse(); + assertThat(predicate.violatedValue("abCD").isPresent()).isFalse(); + assertThat(predicate.violatedValue("AbCD").get().value()) + .isEqualTo(Collections.singletonList("A")); + assertThat(predicate.violatedValue("ABCD").get().value()) + .isEqualTo(Arrays.asList("A", "B")); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/EmojiTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/EmojiTest.java new file mode 100644 index 0000000..7c74425 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/EmojiTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class EmojiTest { + + @Test + void elf() { + String emoji = "🧝🧝🏻🧝🏼🧝🏽🧝🏾🧝🏿"; + assertThat(emoji.length()).isEqualTo(22); + assertThat(Emoji.bestEffortCount(emoji)).isEqualTo(6); + } + + @Test + void emoji() { + String emoji = "I am 👱🏿"; + assertThat(emoji.length()).isEqualTo(9); + assertThat(Emoji.bestEffortCount(emoji)).isEqualTo(6); + } + + @Test + void emoji11All() throws Exception { + verifyEmojiAll("emoji-test-11.txt"); + } + + @Test + void emoji12All() throws Exception { + verifyEmojiAll("emoji-test-12.txt"); + } + + @Test + void family() { + String emoji = "👩‍❤️‍💋‍👩👪👩‍👩‍👧‍👦👨‍👦‍👦👨‍👧👩‍👧"; + assertThat(emoji.length()).isEqualTo(42); + assertThat(Emoji.bestEffortCount(emoji)).isEqualTo(6); + } + + @Test + void heart() { + String emoji = "❤️💙💚💛🧡💜🖤"; + assertThat(emoji.length()).isEqualTo(14); + assertThat(Emoji.bestEffortCount(emoji)).isEqualTo(7); + } + + @Test + void subdivisionFlags() { + String emoji = "🏴󠁧󠁢󠁥󠁮󠁧󠁿🏴󠁧󠁢󠁳󠁣󠁴󠁿🏴󠁧󠁢󠁷󠁬󠁳󠁿"; + assertThat(emoji.length()).isEqualTo(42); + assertThat(Emoji.bestEffortCount(emoji)).isEqualTo(3); + } + + void verifyEmojiAll(String file) throws Exception { + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + this.getClass().getClassLoader().getResourceAsStream(file)))) { + String line; + int n = 0; + do { + n++; + line = reader.readLine(); + if (line == null || line.startsWith("#") || line.isEmpty()) { + continue; + } + int[] codePoints = Arrays.stream(line.split(";")[0].trim().split(" ")) + .mapToInt(x -> Integer.parseInt(x, 16)).toArray(); + String emoji = new String(codePoints, 0, codePoints.length); + int len = Emoji.bestEffortCount("This is " + emoji + "."); + assertThat(len).describedAs(emoji + " L" + n).isEqualTo(10); + + } + while (line != null); + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/RangeTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/RangeTest.java new file mode 100644 index 0000000..7adc238 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/RangeTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.Range; + +class RangeTest { + + @Test + void ofString() { + Range range = Range.of("a", "z"); + Assertions.assertThat(range).isEqualTo(Range.of(0x0061 /* a */, 0x007a /* z */)); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/CompositeCodePointsTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/CompositeCodePointsTest.java new file mode 100644 index 0000000..31cffba --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/charsequence/codepoints/CompositeCodePointsTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.charsequence.codepoints; + +import java.util.Arrays; +import java.util.LinkedHashSet; + +import org.junit.jupiter.api.Test; + +import static org.xbib.datastructures.validation.constraint.charsequence.codepoints.UnicodeCodePoints.HIRAGANA; +import static org.xbib.datastructures.validation.constraint.charsequence.codepoints.UnicodeCodePoints.KATAKANA; +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.CodePointsSet; + +class CompositeCodePointsTest { + + @Test + void codePointRange() { + CodePoints codePoints = new CompositeCodePoints<>(HIRAGANA, KATAKANA); + assertThat(codePoints.allExcludedCodePoints("あ")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("い")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("ア")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("イ")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("あア")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("あいアイ")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("E")).containsOnly(0x0045); + assertThat(codePoints.allExcludedCodePoints("EF")).contains(0x0045, 0x0046); + assertThat(codePoints.allExcludedCodePoints("あいアイE")).containsOnly(0x0045); + assertThat(codePoints.allExcludedCodePoints("あいアイEF")).contains(0x0045, 0x0046); + } + + @Test + void codePointsSet() { + CodePointsSet cp1 = () -> new LinkedHashSet<>( + Arrays.asList(0x0041 /* A */, 0x0042 /* B */)); + CodePointsSet cp2 = () -> new LinkedHashSet<>( + Arrays.asList(0x0043 /* C */, 0x0044 /* D */)); + CodePoints codePoints = new CompositeCodePoints<>(cp1, cp2); + assertThat(codePoints.allExcludedCodePoints("A")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("B")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("C")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("D")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("BC")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("ABCD")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("E")).containsOnly(0x0045); + assertThat(codePoints.allExcludedCodePoints("EF")).contains(0x0045, 0x0046); + assertThat(codePoints.allExcludedCodePoints("ABCDE")).containsOnly(0x0045); + assertThat(codePoints.allExcludedCodePoints("ABCDEF")).contains(0x0045, 0x0046); + } + + @Test + void mix() { + CodePointsSet cp1 = () -> new LinkedHashSet<>( + Arrays.asList(0x0041 /* A */, 0x0042 /* B */)); + CodePointsSet cp2 = () -> new LinkedHashSet<>( + Arrays.asList(0x0043 /* C */, 0x0044 /* D */)); + CodePoints codePoints = new CompositeCodePoints<>(cp1, cp2, HIRAGANA); + assertThat(codePoints.allExcludedCodePoints("A")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("B")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("C")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("D")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("あ")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("い")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("BCあ")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("ABCDあい")).isEmpty(); + assertThat(codePoints.allExcludedCodePoints("E")).containsOnly(0x0045); + assertThat(codePoints.allExcludedCodePoints("EF")).contains(0x0045, 0x0046); + assertThat(codePoints.allExcludedCodePoints("ABCDEあい")).containsOnly(0x0045); + assertThat(codePoints.allExcludedCodePoints("ABCDEFあい")).contains(0x0045, 0x0046); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/password/CharSequencePasswordConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/password/CharSequencePasswordConstraintTest.java new file mode 100644 index 0000000..db8c9e2 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/password/CharSequencePasswordConstraintTest.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.password; + +import java.util.List; +import java.util.function.Function; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.Validator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.xbib.datastructures.validation.constraint.password.PasswordPolicy.LOWERCASE; +import static org.xbib.datastructures.validation.constraint.password.PasswordPolicy.NUMBERS; +import static org.xbib.datastructures.validation.constraint.password.PasswordPolicy.SYMBOLS; +import static org.xbib.datastructures.validation.constraint.password.PasswordPolicy.UPPERCASE; +import static org.assertj.core.api.Assertions.assertThat; + +public class CharSequencePasswordConstraintTest { + @ParameterizedTest + @ValueSource(strings = { "ABCD", "A12b", "Ab12#!" }) + void validPassword_uppercase(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.uppercase().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "abc", "ab1", "ab1!" }) + void invalidPassword_uppercase(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.uppercase().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Uppercase"); + } + + @ParameterizedTest + @ValueSource(strings = { "abc", "ab1", "ab1!" }) + void invalidPassword_uppercase_rename(String input) { + final Validator validator = buildValidator(c -> c + .password(policies -> policies.required(UPPERCASE.name("大文字")).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("大文字"); + } + + @ParameterizedTest + @ValueSource(strings = { "abcd", "A12b", "Ab12#!" }) + void validPassword_lowercase(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.lowercase().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "ABC", "AB1", "AB1!" }) + void invalidPassword_lowercase(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.lowercase().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Lowercase"); + } + + @ParameterizedTest + @ValueSource(strings = { "abcd", "A12b", "Ab12#!", "ABC", "AB1", "AB1!" }) + void validPassword_alphabet(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.alphabets().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "123!", "123", "@!" }) + void invalidPassword_alphabet(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.alphabets().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Alphabets"); + } + + @ParameterizedTest + @ValueSource(strings = { "1234", "a12b", "Az12#!" }) + void validPassword_numbers(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.numbers().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "abc", "abC", "abC#!" }) + void invalidPassword_numbers(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.numbers().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Numbers"); + } + + @ParameterizedTest + @ValueSource(strings = { "1234!", "a12b#", "Az12#!", "!", "\"", "#", "$", "%", "&", + "'", "(", ")", "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", + "@", "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~" }) + void validPassword_symbols(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.symbols().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "abc", "abC", "abC1" }) + void invalidPassword_symbols(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.symbols().build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Symbols"); + } + + @ParameterizedTest + @ValueSource(strings = { "VMware1!", "Az12#!" }) + void validPassword_strong(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.strong())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void invalidPassword_strong_symbols_numbers_uppercase_are_missing() { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.strong())); + final ConstraintViolations violations = validator.validate("abc"); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(3); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Uppercase"); + assertThat(violations.get(1).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(1).args()).hasSize(3); + assertThat(violations.get(1).args()[1]).isEqualTo("Numbers"); + assertThat(violations.get(2).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(2).args()).hasSize(3); + assertThat(violations.get(2).args()[1]).isEqualTo("Symbols"); + } + + @Test + void invalidPassword_strong_symbols_numbers_are_missing() { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.strong())); + final ConstraintViolations violations = validator.validate("abC"); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(2); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Numbers"); + assertThat(violations.get(1).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(1).args()).hasSize(3); + assertThat(violations.get(1).args()[1]).isEqualTo("Symbols"); + } + + @Test + void invalidPassword_numbers_are_missing() { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.strong())); + final ConstraintViolations violations = validator.validate("abC#!"); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Numbers"); + } + + @Test + void invalidPassword_symbols_are_missing() { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.strong())); + final ConstraintViolations violations = validator.validate("123abC"); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Symbols"); + } + + @ParameterizedTest + @ValueSource(strings = { "abC1", "A1!", "1#a", "VMware1!" }) + void validPassword_optional(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies + .optional(3, LOWERCASE, UPPERCASE, NUMBERS, SYMBOLS).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "abC", "1!", "1a" }) + @SuppressWarnings({ "unchecked" }) + void invalidPassword_optional(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies + .optional(3, LOWERCASE, UPPERCASE, NUMBERS, SYMBOLS).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.optional"); + assertThat(violations.get(0).args()).hasSize(4); + assertThat(violations.get(0).args()[1]).isEqualTo(3); + assertThat((List) violations.get(0).args()[2]) + .containsExactly("Lowercase", "Uppercase", "Numbers", "Symbols"); + } + + @ParameterizedTest + @ValueSource(strings = { "abC1", "abC!", "VMware1!" }) + void validPassword_required_and_optional(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.required(LOWERCASE, UPPERCASE) + .optional(1, NUMBERS, SYMBOLS).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @Test + @SuppressWarnings({ "unchecked" }) + void invalidPassword_required_and_optional_missing_lowercase_and_optional() { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.required(LOWERCASE, UPPERCASE) + .optional(1, NUMBERS, SYMBOLS).build())); + final ConstraintViolations violations = validator.validate("ABC"); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(2); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Lowercase"); + assertThat(violations.get(1).messageKey()).isEqualTo("password.optional"); + assertThat(violations.get(1).args()).hasSize(4); + assertThat(violations.get(1).args()[1]).isEqualTo(1); + assertThat((List) violations.get(1).args()[2]).containsExactly("Numbers", + "Symbols"); + } + + @Test + void invalidPassword_required_and_optional_missing_lowercase() { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.required(LOWERCASE, UPPERCASE) + .optional(1, NUMBERS, SYMBOLS).build())); + final ConstraintViolations violations = validator.validate("ABC1!"); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Lowercase"); + } + + @Test + @SuppressWarnings({ "unchecked" }) + void invalidPassword_required_and_optional_missing_optional() { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.required(LOWERCASE, UPPERCASE) + .optional(1, NUMBERS, SYMBOLS).build())); + final ConstraintViolations violations = validator.validate("abC"); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.optional"); + assertThat(violations.get(0).args()).hasSize(4); + assertThat(violations.get(0).args()[1]).isEqualTo(1); + assertThat((List) violations.get(0).args()[2]).containsExactly("Numbers", + "Symbols"); + } + + @ParameterizedTest + @ValueSource(strings = { "VMware1!", "Az12#!" }) + void validPassword_required_and_optional_and_custom(String input) { + final Validator validator = buildValidator(c -> c + .password(policies -> policies.uppercase().lowercase().numbers().symbols() + .required(new PredictableWordsPasswordPolicy()).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "PassWord1!" }) + void invalidPassword_required_and_optional_and_custom(String input) { + final Validator validator = buildValidator(c -> c + .password(policies -> policies.uppercase().lowercase().numbers().symbols() + .required(new PredictableWordsPasswordPolicy()).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("PredictableWords"); + } + + @ParameterizedTest + @ValueSource(strings = { "12abc", "12ABC", "12aBc" }) + void valid_count(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.numbers(2).alphabets(3).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "12ab", "123aB" }) + void invalid_alphabets_count(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.numbers(2).alphabets(3).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Alphabets"); + } + + @ParameterizedTest + @ValueSource(strings = { "1aBc", "abC!" }) + void invalid_numbers_count(String input) { + final Validator validator = buildValidator( + c -> c.password(policies -> policies.numbers(2).alphabets(3).build())); + final ConstraintViolations violations = validator.validate(input); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("Numbers"); + } + + static class PredictableWordsPasswordPolicy implements PasswordPolicy { + @Override + public boolean test(String s) { + return !s.toUpperCase().contains("PASSWORD"); + } + } + + private static Validator buildValidator( + Function, CharSequenceConstraint> constraint) { + return ValidatorBuilder.of(String.class)._string(c -> c, "password", constraint) + .build(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/password/ObjectPasswordConstraintTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/password/ObjectPasswordConstraintTest.java new file mode 100644 index 0000000..800e6d7 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/constraint/password/ObjectPasswordConstraintTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.constraint.password; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.Validator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ObjectPasswordConstraintTest { + PasswordPolicy passwordPolicy = PasswordPolicy.of("UsernameNotIncluded", + account -> !account.password().toUpperCase() + .contains(account.username().toUpperCase())); + + Validator validator = ValidatorBuilder. of() + .constraint(Account::username, "username", c -> c.notBlank()) + .constraint(Account::password, "password", + c -> c.greaterThanOrEqual(8).password(policy -> policy.strong())) + .constraintOnTarget("password", + c -> c.password(policy -> policy.required(passwordPolicy).build())) + .build(); + + @Test + void valid() { + final Account account = new Account("test", "Abcd123!"); + final ConstraintViolations violations = validator.validate(account); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void invalid() { + final Account account = new Account("abcd", "Abcd123!"); + final ConstraintViolations violations = validator.validate(account); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).messageKey()).isEqualTo("password.required"); + assertThat(violations.get(0).args()).hasSize(3); + assertThat(violations.get(0).args()[1]).isEqualTo("UsernameNotIncluded"); + } + + static class Account { + private final String username; + + private final String password; + + public Account(String username, String password) { + this.username = username; + this.password = password; + } + + public String username() { + return username; + } + + public String password() { + return password; + } + + @Override + public String toString() { + return "Account{" + "username='" + username + '\'' + ", password='" + password + + '\'' + '}'; + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractArrayValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractArrayValidatorTest.java new file mode 100644 index 0000000..1030410 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractArrayValidatorTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithArray; +import org.xbib.datastructures.validation.PhoneNumber; + +abstract class AbstractArrayValidatorTest { + @Test + void allInvalid() throws Exception { + Validator validator = validator(); + FormWithArray form = new FormWithArray( + new Address[] { new Address(new Country(null), null, new PhoneNumber("")), + new Address(new Country(null), null, new PhoneNumber("")) }); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(8); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses[0].street\" must not be blank"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).message()) + .isEqualTo("\"addresses[0].country.name\" must not be blank"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).message()) + .isEqualTo("\"addresses[0].phoneNumber.value\" must not be blank"); + assertThat(violations.get(2).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(3).message()).isEqualTo( + "The size of \"addresses[0].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(3).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(4).message()) + .isEqualTo("\"addresses[1].street\" must not be blank"); + assertThat(violations.get(4).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(5).message()) + .isEqualTo("\"addresses[1].country.name\" must not be blank"); + assertThat(violations.get(5).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(6).message()) + .isEqualTo("\"addresses[1].phoneNumber.value\" must not be blank"); + assertThat(violations.get(6).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(7).message()).isEqualTo( + "The size of \"addresses[1].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(7).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void inValidOne() throws Exception { + Validator validator = validator(); + + FormWithArray form = new FormWithArray(new Address[] { + new Address(new Country("JP"), "tokyo", new PhoneNumber("0123456789")), + new Address(new Country(null), null, new PhoneNumber("")) }); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(4); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses[1].street\" must not be blank"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).message()) + .isEqualTo("\"addresses[1].country.name\" must not be blank"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).message()) + .isEqualTo("\"addresses[1].phoneNumber.value\" must not be blank"); + assertThat(violations.get(2).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(3).message()).isEqualTo( + "The size of \"addresses[1].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(3).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void nullCollectionInValid() throws Exception { + Validator validator = validator(); + + FormWithArray form = new FormWithArray(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses\" must not be null"); + assertThat(violations.get(0).messageKey()).isEqualTo("object.notNull"); + } + + @Test + void nullElement() throws Exception { + Validator validator = validator(); + + FormWithArray form = new FormWithArray(new Address[] { + new Address(new Country("JP"), "tokyo", new PhoneNumber("0123456789")), + null }); + // FIXME: does not check null element + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void valid() throws Exception { + Validator validator = validator(); + FormWithArray form = new FormWithArray(new Address[] { + new Address(new Country("JP"), "tokyo", new PhoneNumber("0123456789")), + new Address(new Country("JP"), "osaka", new PhoneNumber("0123456788")) }); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + protected abstract Validator validator(); +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractCollectionValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractCollectionValidatorTest.java new file mode 100644 index 0000000..57089f6 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractCollectionValidatorTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithCollection; +import org.xbib.datastructures.validation.PhoneNumber; + +abstract class AbstractCollectionValidatorTest { + @Test + void allInvalid() throws Exception { + Validator validator = validator(); + FormWithCollection form = new FormWithCollection( + Arrays.asList(new Address(new Country(null), null, new PhoneNumber("")), + new Address(new Country(null), null, new PhoneNumber("")))); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(8); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses[0].street\" must not be blank"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).message()) + .isEqualTo("\"addresses[0].country.name\" must not be blank"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).message()) + .isEqualTo("\"addresses[0].phoneNumber.value\" must not be blank"); + assertThat(violations.get(2).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(3).message()).isEqualTo( + "The size of \"addresses[0].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(3).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(4).message()) + .isEqualTo("\"addresses[1].street\" must not be blank"); + assertThat(violations.get(4).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(5).message()) + .isEqualTo("\"addresses[1].country.name\" must not be blank"); + assertThat(violations.get(5).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(6).message()) + .isEqualTo("\"addresses[1].phoneNumber.value\" must not be blank"); + assertThat(violations.get(6).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(7).message()).isEqualTo( + "The size of \"addresses[1].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(7).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void inValidOne() throws Exception { + Validator validator = validator(); + + FormWithCollection form = new FormWithCollection(Arrays.asList( + new Address(new Country("JP"), "tokyo", new PhoneNumber("0123456789")), + new Address(new Country(null), null, new PhoneNumber("")))); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(4); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses[1].street\" must not be blank"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).message()) + .isEqualTo("\"addresses[1].country.name\" must not be blank"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).message()) + .isEqualTo("\"addresses[1].phoneNumber.value\" must not be blank"); + assertThat(violations.get(2).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(3).message()).isEqualTo( + "The size of \"addresses[1].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(3).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void nullCollectionInValid() throws Exception { + Validator validator = validator(); + + FormWithCollection form = new FormWithCollection(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses\" must not be null"); + assertThat(violations.get(0).messageKey()).isEqualTo("object.notNull"); + } + + @Test + void nullElement() throws Exception { + Validator validator = validator(); + + FormWithCollection form = new FormWithCollection(Arrays.asList( + new Address(new Country("JP"), "tokyo", new PhoneNumber("0123456789")), + null)); + // FIXME: does not check null element + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void valid() throws Exception { + Validator validator = validator(); + FormWithCollection form = new FormWithCollection(Arrays.asList( + new Address(new Country("JP"), "tokyo", new PhoneNumber("0123456789")), + new Address(new Country("JP"), "osaka", new PhoneNumber("0123456788")))); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + protected abstract Validator validator(); +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractMapValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractMapValidatorTest.java new file mode 100644 index 0000000..fa3e478 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractMapValidatorTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.LinkedHashMap; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithMap; +import org.xbib.datastructures.validation.PhoneNumber; + +abstract class AbstractMapValidatorTest { + @Test + void allInvalid() throws Exception { + Validator validator = validator(); + FormWithMap form = new FormWithMap(new LinkedHashMap() { + { + put("tokyo", new Address(new Country(null), null, new PhoneNumber(""))); + put("osaka", new Address(new Country(null), null, new PhoneNumber(""))); + } + }); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(8); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses[0].street\" must not be blank"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).message()) + .isEqualTo("\"addresses[0].country.name\" must not be blank"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).message()) + .isEqualTo("\"addresses[0].phoneNumber.value\" must not be blank"); + assertThat(violations.get(2).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(3).message()).isEqualTo( + "The size of \"addresses[0].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(3).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(4).message()) + .isEqualTo("\"addresses[1].street\" must not be blank"); + assertThat(violations.get(4).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(5).message()) + .isEqualTo("\"addresses[1].country.name\" must not be blank"); + assertThat(violations.get(5).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(6).message()) + .isEqualTo("\"addresses[1].phoneNumber.value\" must not be blank"); + assertThat(violations.get(6).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(7).message()).isEqualTo( + "The size of \"addresses[1].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(7).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void inValidOne() throws Exception { + Validator validator = validator(); + FormWithMap form = new FormWithMap(new LinkedHashMap() { + { + put("tokyo", new Address(new Country("JP"), "tokyo", + new PhoneNumber("0123456789"))); + put("osaka", new Address(new Country(null), null, new PhoneNumber(""))); + } + }); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(4); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses[1].street\" must not be blank"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).message()) + .isEqualTo("\"addresses[1].country.name\" must not be blank"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).message()) + .isEqualTo("\"addresses[1].phoneNumber.value\" must not be blank"); + assertThat(violations.get(2).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(3).message()).isEqualTo( + "The size of \"addresses[1].phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(3).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void nullCollectionInValid() throws Exception { + Validator validator = validator(); + + FormWithMap form = new FormWithMap(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"addresses\" must not be null"); + assertThat(violations.get(0).messageKey()).isEqualTo("object.notNull"); + } + + @Test + void nullElement() throws Exception { + Validator validator = validator(); + FormWithMap form = new FormWithMap(new LinkedHashMap() { + { + put("tokyo", new Address(new Country("JP"), "tokyo", + new PhoneNumber("0123456789"))); + put("osaka", null); + } + }); + // FIXME: does not check null element + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void valid() throws Exception { + Validator validator = validator(); + FormWithMap form = new FormWithMap(new LinkedHashMap() { + { + put("tokyo", new Address(new Country("JP"), "tokyo", + new PhoneNumber("0123456789"))); + put("osaka", new Address(new Country("JP"), "osaka", + new PhoneNumber("0123456788"))); + } + }); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + protected abstract Validator validator(); +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractNestedValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractNestedValidatorTest.java new file mode 100644 index 0000000..9093bec --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/AbstractNestedValidatorTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.PhoneNumber; + +abstract class AbstractNestedValidatorTest { + + @Test + void invalid() { + Validator
addressValidator = validator(); + Address address = new Address(new Country(null), null, new PhoneNumber("")); + ConstraintViolations violations = addressValidator.validate(address); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(4); + assertThat(violations.get(0).message()).isEqualTo("\"street\" must not be blank"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).message()) + .isEqualTo("\"country.name\" must not be blank"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).message()) + .isEqualTo("\"phoneNumber.value\" must not be blank"); + assertThat(violations.get(2).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(3).message()).isEqualTo( + "The size of \"phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + assertThat(violations.get(3).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void nestedValueIsNull() { + Validator
addressValidator = validator(); + Address address = new Address(null, null, null /* nullValidity */); + ConstraintViolations violations = addressValidator.validate(address); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(2); + assertThat(violations.get(0).message()).isEqualTo("\"street\" must not be blank"); + assertThat(violations.get(0).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(1).message()).isEqualTo("\"country\" must not be null"); + assertThat(violations.get(1).messageKey()).isEqualTo("object.notNull"); + } + + @Test + void valid() { + Validator
addressValidator = validator(); + Address address = new Address(new Country("JP"), "tokyo", + new PhoneNumber("0123456789")); + ConstraintViolations violations = addressValidator.validate(address); + assertThat(violations.isValid()).isTrue(); + } + + abstract protected Validator
validator(); +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ApplicativeValidationTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ApplicativeValidationTest.java new file mode 100644 index 0000000..79d305c --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ApplicativeValidationTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.fn.Validations; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class ApplicativeValidationTest { + + static final ApplicativeValidator streetValidator = ValidatorBuilder + .of(String.class)._string(s -> s, "street", c -> c.notBlank()).build() + .applicative(); + + static final ValueValidator, Country> mapCountryValidator = Country + .validator().prefixed("country").applicative() + .compose(map -> new Country(map.get("country"))); + + static final ValueValidator countryValidator = Country.validator() + .prefixed("country").applicative().andThen(Country::name); + + @ParameterizedTest + @MethodSource("validValidations") + void validated_valid(Validated
validation) { + assertThat(validation.isValid()).isTrue(); + final Address address = validation.value(); + assertThat(address).isNotNull(); + assertThat(address.country().name()).isEqualTo("jp"); + assertThat(address.street()).isEqualTo("xyz"); + assertThat(address.phoneNumber().value()).isEqualTo("12345678"); + } + + @ParameterizedTest + @MethodSource("invalidValidations") + void validated_invalid(Validated
validation) { + assertThat(validation.isValid()).isFalse(); + final List violations = validation.errors(); + assertThat(violations).hasSize(3); + assertThat(violations.get(0).name()).isEqualTo("country.name"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).name()).isEqualTo("street"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.notBlank"); + assertThat(violations.get(2).name()).isEqualTo("phoneNumber.value"); + assertThat(violations.get(2).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + + @Test + void compose_valid() { + final Validated countryValidated = mapCountryValidator + .validate(Collections.singletonMap("country", "JP")); + assertThat(countryValidated.isValid()).isTrue(); + assertThat(countryValidated.value().name()).isEqualTo("JP"); + } + + @Test + void compose_invalid() { + final Validated countryValidated = mapCountryValidator + .validate(Collections.singletonMap("country", "J")); + assertThat(countryValidated.isValid()).isFalse(); + final ConstraintViolations violations = countryValidated.errors(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(0).name()).isEqualTo("country.name"); + } + + @Test + void andThen_valid() { + final Validated countryValidated = countryValidator + .validate(new Country("JP")); + assertThat(countryValidated.isValid()).isTrue(); + assertThat(countryValidated.value()).isEqualTo("JP"); + } + + @Test + void andThen_invalid() { + final Validated countryValidated = countryValidator + .validate(new Country("J")); + assertThat(countryValidated.isValid()).isFalse(); + final ConstraintViolations violations = countryValidated.errors(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(0).name()).isEqualTo("country.name"); + } + + static Stream validValidations() { + final Validated countryValidation = Country.of("jp"); + final Validated streetValidation = streetValidator.validate("xyz"); + final Validated phoneNumberValidation = PhoneNumber.of("12345678"); + return Stream.of( + arguments(countryValidation.combine(streetValidation) + .combine(phoneNumberValidation).apply(Address::new)), + arguments(Validations.apply(Address::new, countryValidation, + streetValidation, phoneNumberValidation))); + } + + static Stream invalidValidations() { + final Validated countryValidation = Country.of("j"); + final Validated streetValidation = streetValidator.validate(""); + final Validated phoneNumberValidation = PhoneNumber.of("1234567"); + return Stream.of( + arguments(countryValidation.combine(streetValidation) + .combine(phoneNumberValidation).apply(Address::new)), + arguments(Validations.apply(Address::new, countryValidation, + streetValidation, phoneNumberValidation))); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ArrayValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ArrayValidatorTest.java new file mode 100644 index 0000000..aa5b6ca --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ArrayValidatorTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithArray; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +class ArrayValidatorTest extends AbstractArrayValidatorTest { + Validator
addressValidator = ValidatorBuilder.
of() + .constraint(Address::street, "street", c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", PhoneNumber.validator()) + .build(); + + @Test + void nullCollectionValid() throws Exception { + Validator validator = ValidatorBuilder.of(FormWithArray.class) // + .forEachIfPresent(FormWithArray::getAddresses, "addresses", + addressValidator) + .build(); + FormWithArray form = new FormWithArray(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Override + public Validator validator() { + return ValidatorBuilder.of(FormWithArray.class) // + .forEach(FormWithArray::getAddresses, "addresses", addressValidator) + .build(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/BiValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/BiValidatorTest.java new file mode 100644 index 0000000..2f0d50b --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/BiValidatorTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.xbib.datastructures.validation.User; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.message.SimpleMessageFormatter; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class BiValidatorTest { + final BiValidator> validator = ValidatorBuilder + .of(User.class) + .constraint(User::getName, "name", + c -> c.notNull().greaterThanOrEqual(1).lessThanOrEqual(20)) + .constraint(User::getEmail, "email", + c -> c.notNull().greaterThanOrEqual(5).lessThanOrEqual(50).email()) + .constraint(User::getAge, "age", + c -> c.notNull().greaterThanOrEqual(0).lessThanOrEqual(200)) + .constraint(User::isEnabled, "enabled", c -> c.isTrue()) + .build((errors, name, messageKey, args, defaultMessage) -> errors + .add(new ConstraintViolation(name, messageKey, defaultMessage, args, + new SimpleMessageFormatter(), Locale.ENGLISH))); + + @Test + void allValid() throws Exception { + User user = new User("Demo", "demo@example.com", 100); + user.setEnabled(true); + final List violations = new ArrayList<>(); + + validator.accept(user, violations); + assertThat(violations.size()).isEqualTo(0); + } + + @Test + void allInvalid() throws Exception { + User user = new User("", "example.com", 300); + user.setEnabled(false); + final List violations = new ArrayList<>(); + + validator.accept(user, violations); + assertThat(violations.size()).isEqualTo(4); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).message()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.email"); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" must be less than or equal to 200"); + assertThat(violations.get(2).messageKey()).isEqualTo("numeric.lessThanOrEqual"); + assertThat(violations.get(3).message()).isEqualTo("\"enabled\" must be true"); + assertThat(violations.get(3).messageKey()).isEqualTo("boolean.isTrue"); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CastTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CastTest.java new file mode 100644 index 0000000..c584599 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CastTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + + +class CastTest { + @Test + void anotherClonedValidatorShouldAlsoWork_GH23() { + final Student student = new Student(); + final ConstraintViolations result = Student.validator.validate(student); + assertThat(result.isValid()).isFalse(); + assertThat(result).hasSize(2); + assertThat(result.get(0).name()).isEqualTo("name"); + assertThat(result.get(1).name()).isEqualTo("id"); + } + + @Test + void castShouldWork_GH23() { + final Employee employee = new Employee(); + final ConstraintViolations result = Employee.validator.validate(employee); + assertThat(result.isValid()).isFalse(); + assertThat(result).hasSize(2); + assertThat(result.get(0).name()).isEqualTo("name"); + assertThat(result.get(1).name()).isEqualTo("service"); + } + + @Test + void originalValidatorShouldAlsoWork_GH23() { + final Person person = new Person(); + final ConstraintViolations result = Person.validator.validate(person); + assertThat(result.isValid()).isFalse(); + assertThat(result).hasSize(1); + assertThat(result.get(0).name()).isEqualTo("name"); + } + + static class Employee extends Person { + static final Validator validator = Person.validatorBuilder + .cast(Employee.class) + .constraint(Employee::getServiceId, "service", Constraint::notNull) + .build(); + + private String serviceId; + + String getServiceId() { + return serviceId; + } + + void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + } + + static class Person { + static ValidatorBuilder validatorBuilder = ValidatorBuilder + .of(Person.class) + .constraint(Person::getName, "name", Constraint::notNull); + + static final Validator validator = validatorBuilder.build(); + + private String name; + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + } + + static class Student extends Person { + static final Validator validator = Person.validatorBuilder.clone() + .cast(Student.class).constraint(Student::getId, "id", Constraint::notNull) + .build(); + + private String id; + + String getId() { + return id; + } + + void setId(String id) { + this.id = id; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CollectionValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CollectionValidatorTest.java new file mode 100644 index 0000000..53412a3 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CollectionValidatorTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Arrays; +import java.util.List; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithCollection; +import org.xbib.datastructures.validation.PhoneNumber; +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +class CollectionValidatorTest extends AbstractCollectionValidatorTest { + Validator
addressValidator = ValidatorBuilder.
of() + .constraint(Address::street, "street", c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", PhoneNumber.validator()) + .build(); + + @Test + void nullCollectionValid() throws Exception { + Validator validator = ValidatorBuilder + .of(FormWithCollection.class) // + .forEachIfPresent(FormWithCollection::getAddresses, "addresses", + addressValidator) + .build(); + FormWithCollection form = new FormWithCollection(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void stringListAllInValid() throws Exception { + Foo foo = new Foo(Arrays.asList("abc", "def", "ghi")); + Validator stringValidator = ValidatorBuilder.of(String.class).constraint( + String::toString, "value", c -> c.notNull().lessThanOrEqual(2)).build(); + Validator validator = ValidatorBuilder.of(Foo.class) + .forEach(Foo::getTexts, "texts", stringValidator).build(); + ConstraintViolations violations = validator.validate(foo); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"texts[0].value\" must be less than or equal to 2. The given size is 3"); + assertThat(violations.get(0).messageKey()).isEqualTo("container.lessThanOrEqual"); + assertThat(violations.get(1).message()).isEqualTo( + "The size of \"texts[1].value\" must be less than or equal to 2. The given size is 3"); + assertThat(violations.get(1).messageKey()).isEqualTo("container.lessThanOrEqual"); + assertThat(violations.get(2).message()).isEqualTo( + "The size of \"texts[2].value\" must be less than or equal to 2. The given size is 3"); + assertThat(violations.get(2).messageKey()).isEqualTo("container.lessThanOrEqual"); + } + + @Test + void stringListAllInValidEmptyNestedName() throws Exception { + Foo foo = new Foo(Arrays.asList("abc", "def", "ghi")); + Validator stringValidator = ValidatorBuilder.of(String.class) + .constraint(String::toString, "", c -> c.notNull().lessThanOrEqual(2)) + .build(); + Validator validator = ValidatorBuilder.of(Foo.class) + .forEach(Foo::getTexts, "texts", stringValidator).build(); + ConstraintViolations violations = validator.validate(foo); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"texts[0]\" must be less than or equal to 2. The given size is 3"); + assertThat(violations.get(0).messageKey()).isEqualTo("container.lessThanOrEqual"); + assertThat(violations.get(1).message()).isEqualTo( + "The size of \"texts[1]\" must be less than or equal to 2. The given size is 3"); + assertThat(violations.get(1).messageKey()).isEqualTo("container.lessThanOrEqual"); + assertThat(violations.get(2).message()).isEqualTo( + "The size of \"texts[2]\" must be less than or equal to 2. The given size is 3"); + assertThat(violations.get(2).messageKey()).isEqualTo("container.lessThanOrEqual"); + } + + @Test + void stringListAllValid() throws Exception { + Foo foo = new Foo(Arrays.asList("ab", "cd", "ef")); + Validator stringValidator = ValidatorBuilder.of(String.class).constraint( + String::toString, "value", c -> c.notNull().lessThanOrEqual(2)).build(); + Validator validator = ValidatorBuilder.of(Foo.class) + .forEach(Foo::getTexts, "texts", stringValidator).build(); + ConstraintViolations violations = validator.validate(foo); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void unique() throws Exception { + Foo foo = new Foo(Arrays.asList("a", "b", "c", "d", "e")); + Validator validator = ValidatorBuilder.of(Foo.class) + .constraint(Foo::getTexts, "texts", c -> c.notEmpty().unique()).build(); + ConstraintViolations violations = validator.validate(foo); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void notUnique() throws Exception { + Foo foo = new Foo(Arrays.asList("a", "b", "c", "b", "c")); + Validator validator = ValidatorBuilder.of(Foo.class) + .constraint(Foo::getTexts, "texts", c -> c.notEmpty().unique()).build(); + ConstraintViolations violations = validator.validate(foo); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"texts\" must be unique. [b, c] is/are duplicated."); + } + + @Override + public Validator validator() { + return ValidatorBuilder.of(FormWithCollection.class) // + .forEach(FormWithCollection::getAddresses, "addresses", addressValidator) + .build(); + } + + static class Foo { + List texts; + + Foo(List texts) { + this.texts = texts; + } + + List getTexts() { + return texts; + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintContextTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintContextTest.java new file mode 100644 index 0000000..59c6406 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintContextTest.java @@ -0,0 +1,38 @@ +package org.xbib.datastructures.validation.core; + +import java.util.Map; + +import org.xbib.datastructures.validation.core.ConstraintContext.Attribute; +import org.junit.jupiter.api.Test; + +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; + +class ConstraintContextTest { + + @Test + void fromMap() { + final ConstraintContext context = ConstraintContext + .from(singletonMap("country", "IT")); + final Attribute attribute = context.attribute("country"); + assertThat(attribute.exists()).isTrue(); + final Object country = attribute.value(); + assertThat(country).isEqualTo("IT"); + final String typedCountry = attribute.value(String.class); + assertThat(typedCountry).isEqualTo("IT"); + assertThat(context.attribute("foo").exists()).isFalse(); + } + + @Test + void fromFunction() { + final Map headers = singletonMap("country", "IT"); + final ConstraintContext context = ConstraintContext.from(headers::get); + final Attribute attribute = context.attribute("country"); + assertThat(attribute.exists()).isTrue(); + final Object country = attribute.value(); + assertThat(country).isEqualTo("IT"); + final String typedCountry = attribute.value(String.class); + assertThat(typedCountry).isEqualTo("IT"); + assertThat(context.attribute("foo").exists()).isFalse(); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintGroupTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintGroupTest.java new file mode 100644 index 0000000..7bab1e8 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintGroupTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ConstraintGroupTest { + + @Test + void name() { + ConstraintGroup cg = ConstraintGroup.of("foo"); + assertThat(cg.name()).isEqualTo("foo"); + assertThat(cg.name()).isNotEqualTo("Foo"); + } + + @Test + void testEnum() { + ConstraintCondition condition = CustomGroup.FOO.toCondition(); + assertThat(condition.test(null, CustomGroup.FOO)).isTrue(); + assertThat(condition.test(null, CustomGroup.BAR)).isFalse(); + assertThat(condition.test(null, ConstraintGroup.of("FOO"))).isTrue(); + } + + @Test + void toCondition() { + ConstraintGroup cg = ConstraintGroup.of("foo"); + ConstraintCondition condition = cg.toCondition(); + assertThat(condition.test(null, ConstraintGroup.of("foo"))).isTrue(); + assertThat(condition.test(null, ConstraintGroup.of("Foo"))).isFalse(); + assertThat(condition.test(null, ConstraintGroup.of("bar"))).isFalse(); + } + + enum CustomGroup implements ConstraintGroup { + FOO, BAR + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintViolationsExceptionTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintViolationsExceptionTest.java new file mode 100644 index 0000000..9769d0e --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintViolationsExceptionTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Locale; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.message.SimpleMessageFormatter; + +class ConstraintViolationsExceptionTest { + + @Test + void customMessage() { + final ConstraintViolations violations = new ConstraintViolations(); + final SimpleMessageFormatter messageFormatter = new SimpleMessageFormatter(); + violations.add(new ConstraintViolation("name1", "key", "{0} is invalid.", + new Object[] { "a" }, messageFormatter, Locale.ENGLISH)); + final ConstraintViolationsException exception = new ConstraintViolationsException( + "error!", violations); + assertThat(exception.getMessage()).isEqualTo("error!"); + } + + @Test + void defaultMessage() { + final ConstraintViolations violations = new ConstraintViolations(); + final SimpleMessageFormatter messageFormatter = new SimpleMessageFormatter(); + violations.add(new ConstraintViolation("name1", "key", "{0} is invalid.", + new Object[] { "a" }, messageFormatter, Locale.ENGLISH)); + final ConstraintViolationsException exception = new ConstraintViolationsException( + violations); + assertThat(exception.getMessage()).isEqualTo("Constraint violations found!" + + System.lineSeparator() + "* a is invalid."); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintViolationsTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintViolationsTest.java new file mode 100644 index 0000000..06db753 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ConstraintViolationsTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Arrays; +import java.util.Locale; + +import org.xbib.datastructures.validation.message.SimpleMessageFormatter; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConstraintViolationsTest { + @Test + public void apply() { + SimpleMessageFormatter messageFormatter = new SimpleMessageFormatter(); + ConstraintViolations violations = new ConstraintViolations(); + violations.add(new ConstraintViolation("foo0", "abc0", "hello0", + new Object[] { 1 }, messageFormatter, Locale.getDefault())); + violations.add(new ConstraintViolation("foo1", "abc1", "hello1", + new Object[] { 1 }, messageFormatter, Locale.getDefault())); + + BindingResult bindingResult = new BindingResult(); + violations.apply(bindingResult::rejectValue); + assertThat(bindingResult.toString()) + .isEqualTo("[foo0_abc0_[1]_hello0][foo1_abc1_[1]_hello1]"); + } + + static class BindingResult { + final StringBuilder builder = new StringBuilder(); + + public void rejectValue(String field, String errorCode, Object[] errorArgs, + String defaultMessage) { + this.builder.append("[").append(field).append("_").append(errorCode) + .append("_").append(Arrays.toString(errorArgs)).append("_") + .append(defaultMessage).append("]"); + } + + @Override + public String toString() { + return this.builder.toString(); + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CustomValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CustomValidatorTest.java new file mode 100644 index 0000000..1bc2877 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/CustomValidatorTest.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.time.Instant; +import java.util.Objects; + +import org.xbib.datastructures.validation.Book; +import org.xbib.datastructures.validation.Range; +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CustomValidatorTest { + + @Test + public void predicate() { + Validator validator = ValidatorBuilder. of() // + .constraint(Book::isbn, "isbn", c -> c.notNull() // + .predicate(CustomValidatorTest::isISBN13, // + "custom.isbn13", "\"{0}\" must be ISBN13 format")) + .build(); + { + ConstraintViolations violations = validator + .validate(new Book("9784777519699")); + assertThat(violations.isValid()).isTrue(); + } + { + ConstraintViolations violations = validator + .validate(new Book("1111111111111")); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"isbn\" must be ISBN13 format"); + assertThat(violations.get(0).messageKey()).isEqualTo("custom.isbn13"); + } + { + ConstraintViolations violations = validator.validate(new Book(null)); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"isbn\" must not be null"); + assertThat(violations.get(0).messageKey()).isEqualTo("object.notNull"); + } + } + + @Test + public void predicateCustom() { + Validator validator = ValidatorBuilder. of() // + .constraint(Book::isbn, "isbn", c -> c.notNull() // + .predicate(IsbnConstraint.SINGLETON)) + .build(); + { + ConstraintViolations violations = validator + .validate(new Book("9784777519699")); + assertThat(violations.isValid()).isTrue(); + } + { + ConstraintViolations violations = validator + .validate(new Book("1111111111111")); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"isbn\" must be ISBN13 format"); + assertThat(violations.get(0).messageKey()).isEqualTo("custom.isbn13"); + } + { + ConstraintViolations violations = validator.validate(new Book(null)); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"isbn\" must not be null"); + assertThat(violations.get(0).messageKey()).isEqualTo("object.notNull"); + } + } + + @Test + public void predicateNullable() { + Validator validator = ValidatorBuilder. of() // + .constraint(Book::isbn, "isbn", + c -> c.predicateNullable(v -> v != null && isISBN13(v), // + "custom.isbn13", "\"{0}\" must be ISBN13 format")) + .build(); + { + ConstraintViolations violations = validator + .validate(new Book("9784777519699")); + assertThat(violations.isValid()).isTrue(); + } + { + ConstraintViolations violations = validator + .validate(new Book("1111111111111")); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"isbn\" must be ISBN13 format"); + assertThat(violations.get(0).messageKey()).isEqualTo("custom.isbn13"); + } + { + ConstraintViolations violations = validator.validate(new Book(null)); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"isbn\" must be ISBN13 format"); + } + } + + @Test + public void range() throws Exception { + Validator validator = ValidatorBuilder. of() // + .constraintOnObject(r -> r, "range", c -> c.notNull() // + .predicate(r -> { + Range range = Range.class.cast(r); + return range.getFrom() < range.getTo(); + }, "range.cross", "\"from\" must be less than \"to\"")) + .build(); + { + Range range = new Range(0, 10); + ConstraintViolations violations = validator.validate(range); + assertThat(violations.isValid()).isTrue(); + } + { + Range range = new Range(11, 10); + ConstraintViolations violations = validator.validate(range); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"from\" must be less than \"to\""); + assertThat(violations.get(0).messageKey()).isEqualTo("range.cross"); + } + } + + @Test + public void rangeConstraintOnTarget() throws Exception { + Validator validator = ValidatorBuilder. of() // + .constraintOnTarget(RangeConstraint.SINGLETON, "range") // + .build(); + { + Range range = new Range(0, 10); + ConstraintViolations violations = validator.validate(range); + assertThat(violations.isValid()).isTrue(); + } + { + Range range = new Range(11, 10); + ConstraintViolations violations = validator.validate(range); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"from\" must be less than \"to\""); + assertThat(violations.get(0).messageKey()).isEqualTo("range.cross"); + } + } + + @Test + public void rangeCustom() throws Exception { + Validator validator = ValidatorBuilder. of() // + .constraintOnObject(r -> r, "range", c -> c.notNull() // + .predicateNullable(RangeConstraint.SINGLETON)) + .build(); + { + Range range = new Range(0, 10); + ConstraintViolations violations = validator.validate(range); + assertThat(violations.isValid()).isTrue(); + } + { + Range range = new Range(11, 10); + ConstraintViolations violations = validator.validate(range); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"from\" must be less than \"to\""); + assertThat(violations.get(0).messageKey()).isEqualTo("range.cross"); + } + } + + @Test + public void instantRangeCustom_gh36() throws Exception { + final InstantRangeConstraint constraint = new InstantRangeConstraint( + Instant.parse("2020-01-15T00:00:00Z"), + Instant.parse("2020-01-16T00:00:00Z")); + final Validator validator = ValidatorBuilder.of(Instant.class) + .constraintOnTarget(constraint, "instant").build(); + + final Instant instant = Instant.parse("2020-01-17T00:00:00Z"); + final ConstraintViolations violations = validator.validate(instant); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "Instant value \"instant\" must be between \"2020-01-15T00:00:00Z\" and \"2020-01-16T00:00:00Z\"."); + assertThat(violations.get(0).messageKey()).isEqualTo("string.instant"); + } + + // This logic is written in + // http://en.wikipedia.org/wiki/International_Standard_Book_Number + static boolean isISBN13(String isbn) { + if (isbn.length() != 13) { + return false; + } + int check = 0; + try { + for (int i = 0; i < 12; i += 2) { + check += Integer.parseInt(isbn.substring(i, i + 1)); + } + for (int i = 1; i < 12; i += 2) { + check += Integer.parseInt(isbn.substring(i, i + 1)) * 3; + } + check += Integer.parseInt(isbn.substring(12)); + } + catch (NumberFormatException e) { + return false; + } + return check % 10 == 0; + } + + enum IsbnConstraint implements CustomConstraint { + SINGLETON; + + @Override + public String defaultMessageFormat() { + return "\"{0}\" must be ISBN13 format"; + } + + @Override + public String messageKey() { + return "custom.isbn13"; + } + + @Override + public boolean test(String s) { + return isISBN13(s); + } + } + + enum RangeConstraint implements CustomConstraint { + SINGLETON; + + @Override + public String defaultMessageFormat() { + return "\"from\" must be less than \"to\""; + } + + @Override + public String messageKey() { + return "range.cross"; + } + + @Override + public boolean test(Range r) { + if (r == null) { + return false; + } + return r.getFrom() < r.getTo(); + } + } + + static class InstantRangeConstraint implements CustomConstraint { + + private final Instant end; + + private final Instant start; + + InstantRangeConstraint(Instant start, Instant end) { + this.start = Objects.requireNonNull(start); + this.end = Objects.requireNonNull(end); + } + + @Override + public Object[] arguments(Instant violatedValue) { + return new Object[] { this.start, this.end }; + } + + @Override + public String defaultMessageFormat() { + return "Instant value \"{0}\" must be between \"{1}\" and \"{2}\"."; + } + + @Override + public String messageKey() { + return "string.instant"; + } + + @Override + public boolean test(Instant instant) { + return instant.isAfter(this.start) && instant.isBefore(this.end); + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/FailFastTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/FailFastTest.java new file mode 100644 index 0000000..049b40a --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/FailFastTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.jsr305.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FailFastTest { + + @ParameterizedTest + @MethodSource("carValidator") + void failFast_valid(Validator validator) { + final ConstraintViolations violations = validator.validate(new Car("xyz", true)); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @MethodSource("carValidator") + void failFast_invalid(Validator validator) { + final ConstraintViolations violations = validator.validate(new Car(null, false)); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + } + + @ParameterizedTest + @MethodSource("carOwnerValidator") + void failFastNestedAndCollection_valid(Validator validator) { + final CarOwner carOwner = new CarOwner(new OwnerId("abc"), "John Doe", + Arrays.asList(new Car("xyz1", true), new Car("xyz2", true))); + final ConstraintViolations violations = validator.validate(carOwner); + assertThat(violations.isValid()).isTrue(); + } + + @ParameterizedTest + @MethodSource("carOwnerValidator") + void failFastNested_invalid(Validator validator) { + final CarOwner carOwner = new CarOwner(new OwnerId(null), "John Doe", + Arrays.asList(new Car("xyz1", true), new Car("xyz2", true))); + final ConstraintViolations violations = validator.validate(carOwner); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + } + + @ParameterizedTest + @MethodSource("carOwnerValidator") + void failFastCollection_invalid(Validator validator) { + final CarOwner carOwner = new CarOwner(new OwnerId("abc"), "John Doe", + Arrays.asList(new Car(null, false), new Car(null, false))); + final ConstraintViolations violations = validator.validate(carOwner); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + } + + @ParameterizedTest + @MethodSource("carOwnerValidator") + void failFastAll_invalid(Validator validator) { + final CarOwner carOwner = new CarOwner(new OwnerId(null), null, + Arrays.asList(new Car(null, false), new Car(null, false))); + final ConstraintViolations violations = validator.validate(carOwner); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + } + + static Stream> carValidator() { + return Stream.of( + ValidatorBuilder. of() + .constraint(Car::getManufacturer, "manufacturer", + c -> c.notNull().notBlank()) + .constraint(Car::isRegistered, "isRegistered", + c -> c.notNull().isTrue()) + .failFast(true).build(), + ValidatorBuilder. of() + .constraint(Car::getManufacturer, "manufacturer", + c -> c.notNull().notBlank()) + .constraint(Car::isRegistered, "isRegistered", + c -> c.notNull().isTrue()) + .build().failFast(true)); + } + + static Stream> carOwnerValidator() { + return Stream.of( + ValidatorBuilder. of().failFast(true).nest(CarOwner::getOwnerId, + "ownerId", + b -> b.constraint(OwnerId::getValue, "value", + c -> c.notNull().notBlank())) + .constraint(CarOwner::getName, "name", c -> c.notNull() + .notBlank()) + .forEach(CarOwner::getCars, "cars", + b -> b.constraint(Car::getManufacturer, "manufacturer", + c -> c.notNull().notBlank()) + .constraint(Car::isRegistered, "isRegistered", + c -> c.notNull().isTrue())) + .build(), + ValidatorBuilder. of().failFast(true).nest(CarOwner::getOwnerId, + "ownerId", + b -> b.constraint(OwnerId::getValue, "value", + c -> c.notNull().notBlank())) + .constraintOnCondition((carOwner, group) -> true, + b -> b.constraint(CarOwner::getName, "name", + c -> c.notNull().notBlank())) + .forEach(CarOwner::getCars, "cars", b -> b + .constraintOnCondition((car, group) -> true, + bb -> bb.constraint(Car::getManufacturer, + "manufacturer", + c -> c.notNull().notBlank())) + .constraintOnCondition((car, group) -> true, + bb -> bb.constraint(Car::isRegistered, + "isRegistered", + c -> c.notNull().isTrue()))) + .build()); + } + + public static class Car { + + private final String manufacturer; + + private final boolean isRegistered; + + public Car(@Nullable String manufacturer, boolean isRegistered) { + this.manufacturer = manufacturer; + this.isRegistered = isRegistered; + } + + @Nullable + public String getManufacturer() { + return manufacturer; + } + + public boolean isRegistered() { + return isRegistered; + } + } + + public static class CarOwner { + private final OwnerId ownerId; + + private final String name; + + private final List cars; + + public CarOwner(OwnerId ownerId, @Nullable String name, List cars) { + this.ownerId = ownerId; + this.name = name; + this.cars = Collections.unmodifiableList(cars); + } + + public OwnerId getOwnerId() { + return ownerId; + } + + @Nullable + public String getName() { + return name; + } + + public List getCars() { + return cars; + } + } + + public static class OwnerId { + private final String value; + + public OwnerId(@Nullable String value) { + this.value = value; + } + + @Nullable + public String getValue() { + return value; + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh127Test.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh127Test.java new file mode 100644 index 0000000..ee2596f --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh127Test.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class Gh127Test { + + private static Container2 container2(String value) { + return new Container2(new Container1(value)); + } + + @Test + void testContainer2() { + + // Container2 -> Container1 + ConstraintViolations actual = Container2.VALIDATOR.validate(container2("")); + + assertThat(actual.get(0).name()).isEqualTo("container1.value"); + } + + private static Container3 container3(String value) { + return new Container3(container2(value)); + } + + @Test + void testContainer3() { + + // Container3 -> Container2(with ConstraintCondition) -> Container1 + ConstraintViolations actual = Container3.VALIDATOR.validate(container3("")); + + assertThat(actual.get(0).name()).isEqualTo("container2.container1.value"); + } + + private static Container4 container4(String value) { + return new Container4(container3(value)); + } + + @Test + void testContainer4() { + + // Container4 -> Container3 -> Container2(with ConstraintCondition) -> Container1 + ConstraintViolations actual = Container4.VALIDATOR.validate(container4("")); + + assertThat(actual.get(0).name()) + .isEqualTo("container3.container2.container1.value"); + } + + private static class Container4 { + + private static final Validator VALIDATOR = ValidatorBuilder + .of(Container4.class) + .nest(Container4::getContainer3, "container3", Container3.VALIDATOR) + .build(); + + private Container3 container3; + + private Container4(Container3 container3) { + this.container3 = container3; + } + + public Container3 getContainer3() { + return container3; + } + } + + private static class Container3 { + + private static final Validator VALIDATOR = ValidatorBuilder + .of(Container3.class).nest(Container3::getContainer2, "container2", + Container2.VALIDATOR_WITH_CONDITION) + .build(); + + private Container2 container2; + + private Container3(Container2 container2) { + this.container2 = container2; + } + + public Container2 getContainer2() { + return container2; + } + } + + private static class Container2 { + + private static final Validator VALIDATOR = ValidatorBuilder + .of(Container2.class) + .nest(Container2::getContainer1, "container1", Container1.VALIDATOR) + .build(); + + private static final Validator VALIDATOR_WITH_CONDITION = ValidatorBuilder + .of(Container2.class).constraintOnCondition((c, g) -> true, VALIDATOR) + .build(); + + private Container1 container1; + + private Container2(Container1 container1) { + this.container1 = container1; + } + + public Container1 getContainer1() { + return container1; + } + } + + private static class Container1 { + + private static final Validator VALIDATOR = ValidatorBuilder + .of(Container1.class).constraint(Container1::getValue, "value", + CharSequenceConstraint::notBlank) + .build(); + + private String value; + + private Container1(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh145Test.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh145Test.java new file mode 100644 index 0000000..2cfa286 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh145Test.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +public class Gh145Test { + + private static final Validator
ADDRESS_VALIDATOR = ValidatorBuilder + .of(Address.class) + .constraint(Address::getCity, "city", city -> city.notBlank()) + .nestIfPresent(Address::getCountry, "country", + countryValidatorBuilder -> countryValidatorBuilder + .constraint(Country::getCode, "code", + code -> code.lessThan(3)) + .constraintOnCondition( + (country, group) -> country.getName() != null, + conditionalCountryBuilder -> conditionalCountryBuilder + .constraint(Country::getCode, "code", + code -> code.notBlank()))) + .build(); + + private static final Validator PERSON_VALIDATOR = ValidatorBuilder + .of(Person.class).constraint(Person::getName, "name", name -> name.notBlank()) + .nestIfPresent(Person::getAddress, "address", ADDRESS_VALIDATOR).build(); + + @Test + void shouldBeValidAddressWithoutCountry() { + Address address = new Address("Paris", null); + + ConstraintViolations violations = ADDRESS_VALIDATOR.validate(address); + + assertThat(violations.isValid()).isTrue(); + } + + @Test + void shouldBeValidPersonWithoutAddress() { + Person person = new Person("Jack", null); + + ConstraintViolations violations = PERSON_VALIDATOR.validate(person); + + assertThat(violations.isValid()).isTrue(); + } + + @Test + void shouldBeInvalidWhenAddressHasCountryWithoutCode() { + Address address = new Address("Paris", new Country(null, "France")); + + ConstraintViolations violations = ADDRESS_VALIDATOR.validate(address); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations).extracting(ConstraintViolation::message) + .containsOnly("\"country.code\" must not be blank"); + } + + @Test + void shouldBeInvalidWhenPersonHasAddressWithoutCountryCode() { + Person person = new Person("Jack", + new Address("Paris", new Country(null, "France"))); + + ConstraintViolations violations = PERSON_VALIDATOR.validate(person); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations).extracting(ConstraintViolation::message) + .containsOnly("\"address.country.code\" must not be blank"); + } + + @Test + void shouldBeValidWhenPersonHasAddressWithCountryCodeAndName() { + Person person = new Person("Jack", + new Address("Paris", new Country("FR", "France"))); + + ConstraintViolations violations = PERSON_VALIDATOR.validate(person); + + assertThat(violations.isValid()).isTrue(); + } + + private static class Person { + private String name; + private Address address; + + public Person(String name, Address address) { + this.name = name; + this.address = address; + } + + public String getName() { + return name; + } + + public Address getAddress() { + return address; + } + } + + private static class Address { + private String city; + private Country country; + + public Address(String city, Country country) { + this.city = city; + this.country = country; + } + + public String getCity() { + return city; + } + + public Country getCountry() { + return country; + } + } + + private static class Country { + private String code; + private String name; + + public Country(String code, String name) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh165Test.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh165Test.java new file mode 100644 index 0000000..3508c70 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh165Test.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +class Gh165Test { + private final CustomConstraint constraint = new CustomConstraint() { + private final int maxCount = 4; + + @Override + public String defaultMessageFormat() { + return "The tag \"{3}\" is longer than {1} bytes. length is {2}"; + } + + @Override + public Object[] arguments(String violatedValue) { + return new Object[] { this.maxCount, violatedValue.length() }; + } + + @Override + public String messageKey() { + return ""; + } + + @Override + public boolean test(String tag) { + return tag.getBytes(StandardCharsets.UTF_8).length <= this.maxCount; + } + }; + + static class Tag { + private final String name; + + Tag(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + @Test + void valid() { + final Validator validator = ValidatorBuilder.of(Tag.class) + .constraint(Tag::getName, "name", c -> c.notBlank().predicate(constraint)) + .build(); + final ConstraintViolations violations = validator.validate(new Tag("aaaaaa")); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.get(0).message()) + .isEqualTo("The tag \"aaaaaa\" is longer than 4 bytes. length is 6"); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh168Test.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh168Test.java new file mode 100644 index 0000000..8303207 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh168Test.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +class Gh168Test { + + public static enum AnimalType { + DOG, CAT + } + + public static class Action { + private final String name; + + public Action(String name) { + this.name = name; + } + + public String name() { + return name; + } + } + + public static class Animal { + private final AnimalType type; + + private final List actions; + + public Animal(AnimalType type, List actions) { + this.type = type; + this.actions = actions; + } + + public AnimalType type() { + return type; + } + + public List actions() { + return actions; + } + } + + final Validator validator = ValidatorBuilder. of() + .constraintOnCondition( + (animal, constraintContext) -> animal.type() == AnimalType.DOG, + b -> b.forEach(Animal::actions, "actions", action -> action + .constraint(Action::name, "name", c -> c.predicate( + name -> name.equals("run") || name.equals("bark"), + "dog.message", + "For type dog an action \"{1}\" is not allowed. Allowed values are: [\"run\", \"bark\"]")))) + .constraintOnCondition( + (animal, constraintContext) -> animal.type() == AnimalType.CAT, + b -> b.forEach(Animal::actions, "actions", action -> action + .constraint(Action::name, "name", c -> c.predicate( + name -> name.equals("run") || name.equals("meow"), + "cat.message", + "For type cat an action \"{1}\" is not allowed. Allowed values are: [\"run\", \"meow\"]")))) + .build(); + + @Test + void validDog() { + final ConstraintViolations violations = validator.validate(new Animal( + AnimalType.DOG, Arrays.asList(new Action("run"), new Action("bark")))); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void invalidDog() { + final ConstraintViolations violations = validator.validate(new Animal( + AnimalType.DOG, Arrays.asList(new Action("run"), new Action("meow")))); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).name()).isEqualTo("actions[1].name"); + assertThat(violations.get(0).message()).isEqualTo( + "For type dog an action \"meow\" is not allowed. Allowed values are: [\"run\", \"bark\"]"); + } + + @Test + void validCat() { + final ConstraintViolations violations = validator.validate(new Animal( + AnimalType.CAT, Arrays.asList(new Action("run"), new Action("meow")))); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void invalidCat() { + final ConstraintViolations violations = validator.validate(new Animal( + AnimalType.CAT, Arrays.asList(new Action("run"), new Action("bark")))); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).name()).isEqualTo("actions[1].name"); + assertThat(violations.get(0).message()).isEqualTo( + "For type cat an action \"bark\" is not allowed. Allowed values are: [\"run\", \"meow\"]"); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh213Test.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh213Test.java new file mode 100644 index 0000000..97242e1 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Gh213Test.java @@ -0,0 +1,143 @@ +package org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +import static org.xbib.datastructures.validation.core.ConstraintCondition.hasAttributeWithValue; +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; + +public class Gh213Test { + + @Test + void noContextInvalid() { + final User user = new User(null, null, null); + final ConstraintViolations violations = User.VALIDATOR.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"firstName\" must not be null"); + } + + @Test + void noContextValid() { + final User user = new User("foo", null, null); + final ConstraintViolations violations = User.VALIDATOR.validate(user); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void countryContextInvalid() { + final User user = new User("foo", null, null); + final ConstraintViolations violations = User.VALIDATOR.validate(user, + ConstraintContext.from(singletonMap("country", "IT"))); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"lastName\" must not be null"); + } + + @Test + void countryContextValid() { + final User user = new User("foo", null, null); + final ConstraintViolations violations = User.VALIDATOR.validate(user, + ConstraintContext.from(singletonMap("country", "FR"))); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void booleanContextInvalid() { + final User user = new User("foo", null, null); + final ConstraintViolations violations = User.VALIDATOR.validate(user, + ConstraintContext + .from(singletonMap("enableValidationInvoiceCode", true))); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).message()).isEqualTo("\"code\" must not be null"); + } + + @Test + void booleanContextValid() { + final User user = new User("foo", null, null); + final ConstraintViolations violations = User.VALIDATOR.validate(user, + ConstraintContext + .from(singletonMap("enableValidationInvoiceCode", false))); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void booleanContextInvalidNested() { + final User user = new User("foo", null, new InvoiceCode(null)); + final ConstraintViolations violations = User.VALIDATOR.validate(user, + ConstraintContext + .from(singletonMap("enableValidationInvoiceCode", true))); + assertThat(violations.isValid()).isFalse(); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"code.fiscalCode\" must not be null"); + } + + @Test + void booleanContextValidNested() { + final User user = new User("foo", null, new InvoiceCode("a")); + final ConstraintViolations violations = User.VALIDATOR.validate(user, + ConstraintContext + .from(singletonMap("enableValidationInvoiceCode", true))); + assertThat(violations.isValid()).isTrue(); + } + + public static class User { + + private String firstName; + + private String lastName; + + private InvoiceCode code; + + static final Validator VALIDATOR = ValidatorBuilder. of() + .constraint(User::getFirstName, "firstName", Constraint::notNull) + .constraintOnCondition(hasAttributeWithValue("country", "IT"), + b -> b.constraint(User::getLastName, "lastName", + Constraint::notNull)) + .constraintOnCondition( + hasAttributeWithValue("enableValidationInvoiceCode", true), + b -> b.nest(User::getCode, "code", InvoiceCode.VALIDATOR)) + .build(); + + public User(String firstName, String lastName, InvoiceCode code) { + this.firstName = firstName; + this.lastName = lastName; + this.code = code; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public InvoiceCode getCode() { + return code; + } + } + + public static class InvoiceCode { + static final Validator VALIDATOR = ValidatorBuilder + . of() + .constraint(InvoiceCode::getFiscalCode, "fiscalCode", Constraint::notNull) + .build(); + + public String fiscalCode; + + public InvoiceCode(String fiscalCode) { + this.fiscalCode = fiscalCode; + } + + public String getFiscalCode() { + return fiscalCode; + } + } + +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Group.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Group.java new file mode 100644 index 0000000..98c2fc7 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/Group.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +public enum Group implements ConstraintGroup { + CREATE, UPDATE, DELETE +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineArrayValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineArrayValidatorTest.java new file mode 100644 index 0000000..025b91b --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineArrayValidatorTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithArray; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +class InlineArrayValidatorTest extends AbstractArrayValidatorTest { + @Test + void nullCollectionValid() throws Exception { + Validator validator = ValidatorBuilder.of(FormWithArray.class) // + .forEachIfPresent(FormWithArray::getAddresses, "addresses", + b -> b.constraint(Address::street, "street", + c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", + PhoneNumber.validator())) + .build(); + FormWithArray form = new FormWithArray(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Override + public Validator validator() { + return ValidatorBuilder.of(FormWithArray.class) // + .forEach(FormWithArray::getAddresses, "addresses", + b -> b.constraint(Address::street, "street", + c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", + PhoneNumber.validator())) + .build(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineCollectionValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineCollectionValidatorTest.java new file mode 100644 index 0000000..469bf8d --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineCollectionValidatorTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithCollection; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +class InlineCollectionValidatorTest extends AbstractCollectionValidatorTest { + @Test + void nullCollectionValid() throws Exception { + Validator validator = ValidatorBuilder + .of(FormWithCollection.class) // + .forEachIfPresent(FormWithCollection::getAddresses, "addresses", + b -> b.constraint(Address::street, "street", + c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", + PhoneNumber.validator())) + .build(); + FormWithCollection form = new FormWithCollection(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Override + public Validator validator() { + return ValidatorBuilder.of(FormWithCollection.class) // + .forEach(FormWithCollection::getAddresses, "addresses", + b -> b.constraint(Address::street, "street", + c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", + PhoneNumber.validator())) + .build(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineMapValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineMapValidatorTest.java new file mode 100644 index 0000000..027e179 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineMapValidatorTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithMap; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +class InlineMapValidatorTest extends AbstractMapValidatorTest { + @Test + void nullCollectionValid() throws Exception { + Validator validator = ValidatorBuilder.of(FormWithMap.class) // + .forEachIfPresent(FormWithMap::getAddresses, "addresses", + b -> b.constraint(Address::street, "street", + c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", + PhoneNumber.validator())) + .build(); + FormWithMap form = new FormWithMap(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Override + public Validator validator() { + return ValidatorBuilder.of(FormWithMap.class) // + .forEach(FormWithMap::getAddresses, "addresses", + b -> b.constraint(Address::street, "street", + c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", + PhoneNumber.validator())) + .build(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineNestedValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineNestedValidatorTest.java new file mode 100644 index 0000000..6b843df --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/InlineNestedValidatorTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +public class InlineNestedValidatorTest extends AbstractNestedValidatorTest { + @Override + protected Validator
validator() { + return ValidatorBuilder.
of() + .constraint(Address::street, "street", + c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", + b -> b.constraint(Country::name, "name", c -> c.notBlank() // + .greaterThanOrEqual(2))) + .nestIfPresent(Address::phoneNumber, "phoneNumber", + b -> b.constraint(PhoneNumber::value, "value", c -> c.notBlank() // + .greaterThanOrEqual(8))) + .build(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/MapValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/MapValidatorTest.java new file mode 100644 index 0000000..e6347e2 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/MapValidatorTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithMap; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +class MapValidatorTest extends AbstractMapValidatorTest { + Validator
addressValidator = ValidatorBuilder.
of() + .constraint(Address::street, "street", c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", PhoneNumber.validator()) + .build(); + + @Test + void nullCollectionValid() throws Exception { + Validator validator = ValidatorBuilder.of(FormWithMap.class) // + .forEachIfPresent(FormWithMap::getAddresses, "addresses", + addressValidator) + .build(); + FormWithMap form = new FormWithMap(null); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } + + @Override + public Validator validator() { + return ValidatorBuilder.of(FormWithMap.class) // + .forEach(FormWithMap::getAddresses, "addresses", addressValidator) + .build(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/MultiNestedCollectionValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/MultiNestedCollectionValidatorTest.java new file mode 100644 index 0000000..b8c6eda --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/MultiNestedCollectionValidatorTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.FormWithCollection; +import org.xbib.datastructures.validation.NestedFormWithCollection; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +class MultiNestedCollectionValidatorTest { + Validator
addressValidator = ValidatorBuilder.
of() + .constraint(Address::street, "street", c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", PhoneNumber.validator()) + .build(); + Validator formValidator = ValidatorBuilder + .of(FormWithCollection.class) // + .forEach(FormWithCollection::getAddresses, "addresses", addressValidator) + .build(); + + Validator validator = ValidatorBuilder + .of(NestedFormWithCollection.class) + .forEach(NestedFormWithCollection::getForms, "forms", formValidator).build(); + + @Test + void invalid() { + NestedFormWithCollection form = new NestedFormWithCollection( + Arrays.asList(new FormWithCollection(Arrays.asList( + new Address(new Country("JP"), "tokyo", + new PhoneNumber("0123456")), + new Address(new Country("JP"), "", + new PhoneNumber("0123456788")))))); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(2); + assertThat(violations.get(0).name()) + .isEqualTo("forms[0].addresses[0].phoneNumber.value"); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"forms[0].addresses[0].phoneNumber.value\" must be greater than or equal to 8. The given size is 7"); + assertThat(violations.get(1).name()).isEqualTo("forms[0].addresses[1].street"); + assertThat(violations.get(1).message()) + .isEqualTo("\"forms[0].addresses[1].street\" must not be blank"); + } + + @Test + void valid() { + NestedFormWithCollection form = new NestedFormWithCollection( + Arrays.asList(new FormWithCollection(Arrays.asList( + new Address(new Country("JP"), "tokyo", + new PhoneNumber("0123456789")), + new Address(new Country("JP"), "osaka", + new PhoneNumber("0123456788")))))); + ConstraintViolations violations = validator.validate(form); + assertThat(violations.isValid()).isTrue(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/NestedConstraintConditionGh127Test.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/NestedConstraintConditionGh127Test.java new file mode 100644 index 0000000..b48065c --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/NestedConstraintConditionGh127Test.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NestedConstraintConditionGh127Test { + + @Test + void shouldReturnProperViolationForOneLevelNestedObjectWithConditionalValidator() { + ConstraintViolations violations = D.CONDITIONAL_VALIDATOR + .validate(new D(new E(null))); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations.violations()).extracting(ConstraintViolation::name) + .containsOnly("e.value"); + assertThat(violations.violations()).extracting(ConstraintViolation::message) + .containsOnly("\"e.value\" must not be blank"); + } + + @Test + void shouldReturnProperViolationForOneLevelNestedObjectWithoutConditionalValidator() { + ConstraintViolations violations = D.VALIDATOR.validate(new D(new E(null))); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations.violations()).extracting(ConstraintViolation::name) + .containsOnly("e.value"); + assertThat(violations.violations()).extracting(ConstraintViolation::message) + .containsOnly("\"e.value\" must not be blank"); + } + + @Test + void shouldReturnProperViolationWhenTwiceConditionalValidatorIsUsed() { + Validator validator = ValidatorBuilder.of(D.class) + .constraintOnCondition((c, g) -> true, D.CONDITIONAL_VALIDATOR).build(); + + ConstraintViolations violations = validator.validate(new D(new E(null))); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations.violations()).extracting(ConstraintViolation::name) + .containsOnly("e.value"); + assertThat(violations.violations()).extracting(ConstraintViolation::message) + .containsOnly("\"e.value\" must not be blank"); + } + + @Test + void shouldReturnProperViolationForFourLevelNestedObjectWithConditionalValidator() { + Validator validator = ValidatorBuilder.of(A.class) + .nest(A::getB, "b", + b -> b.nest(B::getC, "c", + c -> c.nest(C::getD, "d", D.CONDITIONAL_VALIDATOR))) + .build(); + + ConstraintViolations violations = validator + .validate(new A(new B(new C(new D(new E(null)))))); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations.violations()).extracting(ConstraintViolation::name) + .containsOnly("b.c.d.e.value"); + assertThat(violations.violations()).extracting(ConstraintViolation::message) + .containsOnly("\"b.c.d.e.value\" must not be blank"); + } + + @Test + void shouldReturnProperViolationForFourLevelNestedObjectWithoutConditionalValidator() { + Validator validator = ValidatorBuilder.of(A.class) + .nest(A::getB, "b", + b -> b.nest(B::getC, "c", c -> c.nest(C::getD, "d", D.VALIDATOR))) + .build(); + + ConstraintViolations violations = validator + .validate(new A(new B(new C(new D(new E(null)))))); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations.violations()).extracting(ConstraintViolation::name) + .containsOnly("b.c.d.e.value"); + assertThat(violations.violations()).extracting(ConstraintViolation::message) + .containsOnly("\"b.c.d.e.value\" must not be blank"); + } + + @Test + void shouldReturnProperViolationWhenValidatorHasManyNestedConditionalValidators() { + Validator validator = ValidatorBuilder.of(A.class).constraintOnCondition( + (c, g) -> true, + ValidatorBuilder.of(A.class).nest(A::getB, "b", ValidatorBuilder + .of(B.class) + .constraintOnCondition((c1, g1) -> true, ValidatorBuilder + .of(B.class) + .nest(B::getC, "c", ValidatorBuilder.of(C.class) + .constraintOnCondition((c2, g2) -> true, + ValidatorBuilder.of(C.class) + .nest(C::getD, "d", + D.CONDITIONAL_VALIDATOR) + .build()) + .build()) + .build()) + .build()).build()) + .build(); + + ConstraintViolations violations = validator + .validate(new A(new B(new C(new D(new E(null)))))); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations.violations()).extracting(ConstraintViolation::name) + .containsOnly("b.c.d.e.value"); + assertThat(violations.violations()).extracting(ConstraintViolation::message) + .containsOnly("\"b.c.d.e.value\" must not be blank"); + } + + private static class A { + + private final B b; + + public A(B b) { + this.b = b; + } + + public B getB() { + return b; + } + } + + private static class B { + + private final C c; + + public B(C c) { + this.c = c; + } + + public C getC() { + return c; + } + } + + private static class C { + + private final D d; + + public C(D d) { + this.d = d; + } + + public D getD() { + return d; + } + } + + private static class D { + private static final Validator VALIDATOR = ValidatorBuilder.of(D.class) + .nest(D::getE, "e", E.VALIDATOR).build(); + private static final Validator CONDITIONAL_VALIDATOR = ValidatorBuilder + .of(D.class).constraintOnCondition((c, g) -> true, VALIDATOR).build(); + + private final E e; + + public D(E e) { + this.e = e; + } + + public E getE() { + return e; + } + } + + private static class E { + private static final Validator VALIDATOR = ValidatorBuilder.of(E.class) + .constraint(E::getValue, "value", CharSequenceConstraint::notBlank) + .build(); + + private final String value; + + public E(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/NestedValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/NestedValidatorTest.java new file mode 100644 index 0000000..efe3b69 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/NestedValidatorTest.java @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.constraint.CharSequenceConstraint; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.xbib.datastructures.validation.core.Group.CREATE; +import static org.xbib.datastructures.validation.core.Group.UPDATE; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +public class NestedValidatorTest extends AbstractNestedValidatorTest { + @Override + protected Validator
validator() { + return ValidatorBuilder.
of() + .constraint(Address::street, "street", c -> c.notBlank().lessThan(32)) + .nest(Address::country, "country", Country.validator()) + .nestIfPresent(Address::phoneNumber, "phoneNumber", + PhoneNumber.validator()) + .build(); + } + + private ConstraintCondition whenAllOfNumbersNotNull = (r, + g) -> r.small != null && r.big != null; + + private Validator thenCompareNumbers = ValidatorBuilder.of(IntRange.class) + .constraintOnTarget(r -> r.big > r.small, "big", null, + "[big] must be greater than [small]") + .build(); + + private Validator intRangeValidator = ValidatorBuilder.of(IntRange.class) + .constraint((ValidatorBuilder.ToInteger) r -> r.small, "small", + Constraint::notNull) + .constraint((ValidatorBuilder.ToInteger) r -> r.big, "big", + Constraint::notNull) + .constraintOnCondition(whenAllOfNumbersNotNull, thenCompareNumbers).build(); + + private Validator nestedObjectValidator = ValidatorBuilder + . of() + .constraintOnCondition(CREATE.toCondition(), + b -> b.constraint(NestedObject::getId, "id", Constraint::isNull)) + .constraintOnCondition(UPDATE.toCondition(), + b -> b.constraint(NestedObject::getId, "id", Constraint::notNull)) + .constraint(NestedObject::getText, "text", CharSequenceConstraint::notBlank) + .build(); + + private Validator mainObjectValidator = ValidatorBuilder. of() + .constraintOnCondition(CREATE.toCondition(), + b -> b.constraint(MainObject::getId, "id", Constraint::isNull)) + .constraintOnCondition(UPDATE.toCondition(), + b -> b.constraint(MainObject::getId, "id", Constraint::notNull)) + .nest(MainObject::getNested, "nested", nestedObjectValidator).build(); + + private Validator mainObjectIfPresentValidator = ValidatorBuilder + . of() + .constraintOnCondition(CREATE.toCondition(), + b -> b.constraint(MainObject::getId, "id", Constraint::isNull)) + .constraintOnCondition(UPDATE.toCondition(), + b -> b.constraint(MainObject::getId, "id", Constraint::notNull)) + .nestIfPresent(MainObject::getNested, "nested", nestedObjectValidator) + .build(); + + private Validator nestedObjectValidator_GH29 = ValidatorBuilder + . of() + .constraintOnCondition(CREATE.toCondition(), + b -> b.constraint(NestedObject::getId, "id", Constraint::isNull)) + .constraintOnCondition(UPDATE.toCondition(), + b -> b.constraint(NestedObject::getId, "id", Constraint::notNull)) + .constraint(NestedObject::getText, "text", CharSequenceConstraint::notBlank) + .build(); + + private Validator mainObjectValidator_GH29 = ValidatorBuilder + . of() + .constraintOnCondition(CREATE.toCondition(), + b -> b.constraint(MainObject::getId, "id", Constraint::isNull)) + .constraintOnCondition(UPDATE.toCondition(), + b -> b.constraint(MainObject::getId, "id", Constraint::notNull)) + .constraintOnCondition(CREATE.toCondition(), + b -> b.nest(MainObject::getNested, "nested", + nestedObjectValidator_GH29)) + .constraintOnCondition(UPDATE.toCondition(), b -> b + .nest(MainObject::getNested, "nested", nestedObjectValidator_GH29)) + .build(); + + private Validator nestedObjectValidator_GH90 = ValidatorBuilder + . of() + .constraintOnCondition(CREATE.toCondition(), + b -> b.constraint(NestedObject::getId, "id", Constraint::isNull)) + .constraintOnCondition(UPDATE.toCondition(), + b -> b.constraint(NestedObject::getId, "id", Constraint::notNull)) + .constraint(NestedObject::getText, "text", CharSequenceConstraint::notBlank) + .forEach(NestedObject::getIntRanges, "intRanges", intRangeValidator).build(); + + private Validator mainObjectValidator_GH90 = ValidatorBuilder + . of() + .constraintOnCondition(CREATE.toCondition(), + b -> b.constraint(MainObject::getId, "id", Constraint::isNull)) + .constraintOnCondition(UPDATE.toCondition(), + b -> b.constraint(MainObject::getId, "id", Constraint::notNull)) + .nest(MainObject::getNested, "nested", nestedObjectValidator_GH90).build(); + + @Test + void shouldBeInValid_GH28() { + MainObject target = new MainObject(); + + ConstraintViolations result = mainObjectValidator.validate(target, CREATE); + + assertThat(result.isValid()).isFalse(); + assertThat(result.get(0).name()).isEqualTo("nested"); + assertThat(result.get(0).messageKey()).isEqualTo("object.notNull"); + } + + @Test + void shouldBeInvalid_GH24() { + MainObject target = new MainObject(); + target.setId(1L); + + NestedObject nested = new NestedObject(); + nested.setId(10L); + target.setNested(nested); + + ConstraintViolations result = mainObjectValidator.validate(target, CREATE); + + assertThat(result.isValid()).isFalse(); + assertThat(result).hasSize(3); + assertThat(result.get(0).name()).isEqualTo("nested.text"); + assertThat(result.get(1).name()).isEqualTo("id"); + assertThat(result.get(2).name()).isEqualTo("nested.id"); + } + + @Test + void shouldBeInvalid_GH29() { + final MainObject target = new MainObject(); + target.setId(1L); + + final NestedObject nested = new NestedObject(); + nested.setId(1L); + target.setNested(nested); + + final ConstraintViolations result = mainObjectValidator_GH29.validate(target, + CREATE); + + assertThat(result.isValid()).isFalse(); + assertThat(result).hasSize(3); + } + + @Test + void shouldBeValid_GH24() { + MainObject target = new MainObject(); + target.setId(1L); + + NestedObject nested = new NestedObject(); + nested.setId(1L); + nested.setText("test"); + target.setNested(nested); + + ConstraintViolations result = mainObjectValidator.validate(target, UPDATE); + + assertThat(result.isValid()).isTrue(); + } + + @Test + void testNested_GH20() { + Validator validator = ValidatorBuilder.of(Nested.class) + .nest(n -> n.intRange, "intRange", intRangeValidator).build(); + + Nested nested = new Nested(new IntRange(1, 0)); + + final ConstraintViolations result = validator.validate(nested); + assertThat(result.isValid()).isFalse(); + assertThat(result.get(0).name()).isEqualTo("intRange.big"); + } + + @Test + void testStandAlone_GH20() { + IntRange intRange = new IntRange(1, 0); + + final ConstraintViolations result = intRangeValidator.validate(intRange); + assertThat(result.isValid()).isFalse(); + assertThat(result.get(0).name()).isEqualTo("big"); + } + + @Test + void shouldBeValid_GH28() { + MainObject target = new MainObject(); + + ConstraintViolations result = mainObjectIfPresentValidator.validate(target, + CREATE); + + assertThat(result.isValid()).isTrue(); + } + + @Test + void shouldBeValid_GH29() { + final MainObject target = new MainObject(); + target.setId(1L); + + final NestedObject nested = new NestedObject(); + nested.setId(1L); + nested.setText("test"); + target.setNested(nested); + + final ConstraintViolations result = mainObjectValidator_GH29.validate(target, + UPDATE); + + assertThat(result.isValid()).isTrue(); + } + + @Test + void shouldBeValid_GH90() { + MainObject target = new MainObject(); + + NestedObject nested = new NestedObject(); + nested.setText("test"); + nested.setIntRanges(singletonList(new IntRange(1, 2))); + + target.setNested(nested); + + ConstraintViolations result = mainObjectValidator_GH90.validate(target, CREATE); + + assertThat(result.isValid()).isTrue(); + } + + @Test + void shouldBeInvalid_GH90() { + MainObject target = new MainObject(); + + NestedObject nested = new NestedObject(); + nested.setText("test"); + nested.setIntRanges(singletonList(new IntRange(null, 2))); + + target.setNested(nested); + + ConstraintViolations result = mainObjectValidator_GH90.validate(target, CREATE); + + assertThat(result.isValid()).isFalse(); + assertThat(result).extracting(ConstraintViolation::name) + .containsExactly("nested.intRanges[0].small"); + } + + @org.junit.jupiter.api.Nested + class DeepNestingGH60Tests { + @Test + void shouldValidateObjectWithManyLayersNestedObjectWhenInnerNestedObjectIsRequired() { + final Validator validator = ValidatorBuilder.of(A.class) + .nest(A::getB, "b", b -> b.nest(B::getC, "c", + c -> c.nest(C::getD, "d", d -> d.nest(D::getE, "e", + e -> e._integer(E::getValue, "value", + value -> value.greaterThanOrEqual(100)))))) + .build(); + + assertSoftly(softly -> { + softly.assertThat(validator.validate(new A(null)).isValid()) + .as("1-layer nested object is null").isFalse(); + softly.assertThat(validator.validate(new A(new B(null))).isValid()) + .as("2-layer nested object is null").isFalse(); + softly.assertThat(validator.validate(new A(new B(new C(null)))).isValid()) + .as("3-layer nested object is null").isFalse(); + softly.assertThat(validator + .validate(new A(new B(new C(new D(null, null))))).isValid()) + .as("4-layer nested object is null (when it is required)") + .isFalse(); + softly.assertThat(validator + .validate(new A(new B(new C(new D(new E(100), null))))).isValid()) + .as("5-layer constraint is valid").isTrue(); + }); + } + + @Test + void shouldValidateObjectWithManyLayersNestedObjectWhenInnerNestedObjectIsOptional() { + final Validator validator = ValidatorBuilder.of(A.class) + .nest(A::getB, "b", b -> b.nestIfPresent(B::getC, "c", + c -> c.nest(C::getD, "d", d -> d.nestIfPresent(D::getE, "e", + e -> e._integer(E::getValue, "value", + value -> value.greaterThanOrEqual(100)))))) + .build(); + + assertSoftly(softly -> { + softly.assertThat(validator.validate(new A(null)).isValid()) + .as("1-layer nested object is null").isFalse(); + softly.assertThat(validator.validate(new A(new B(null))).isValid()) + .as("2-layer nested object is null (when it is optional)") + .isTrue(); + softly.assertThat(validator.validate(new A(new B(new C(null)))).isValid()) + .as("3-layer nested object is null").isFalse(); + softly.assertThat(validator + .validate(new A(new B(new C(new D(null, null))))).isValid()) + .as("4-layer nested object is null (when it is optional)") + .isTrue(); + softly.assertThat(validator + .validate(new A(new B(new C(new D(new E(100), null))))).isValid()) + .as("5-layer constraint is valid").isTrue(); + }); + } + + @Test + void shouldValidateObjectWithManyLayersNestedObjectWhenForEachMethodIsUsed() { + final Validator validator = ValidatorBuilder.of(A.class) + .nest(A::getB, "b", b -> b.nest(B::getC, "c", + c -> c.nest(C::getD, "d", d -> d.forEach(D::getList, "list", + e -> e._integer(E::getValue, "value", + value -> value.greaterThanOrEqual(100)))))) + .build(); + + assertSoftly(softly -> { + softly.assertThat(validator.validate(new A(null)).isValid()) + .as("1-layer nested object is null").isFalse(); + softly.assertThat(validator.validate(new A(new B(null))).isValid()) + .as("2-layer nested object is null").isFalse(); + softly.assertThat(validator.validate(new A(new B(new C(null)))).isValid()) + .as("3-layer nested object is null").isFalse(); + softly.assertThat(validator + .validate(new A(new B(new C(new D(null, null))))).isValid()) + .as("4-layer nested object is null (when it is required)") + .isFalse(); + softly.assertThat(validator + .validate(new A( + new B(new C(new D(null, singletonList(new E(100))))))) + .isValid()).as("5-layer constraint is valid").isTrue(); + }); + } + + @Test + void shouldValidateObjectWithManyLayersNestedObjectWhenForEachIfPresentMethodIsUsed() { + final Validator validator = ValidatorBuilder.of(A.class).nest(A::getB, "b", + b -> b.nestIfPresent(B::getC, "c", c -> c.nest(C::getD, "d", + d -> d.forEachIfPresent(D::getList, "list", + e -> e._integer(E::getValue, "value", + value -> value.greaterThanOrEqual(100)))))) + .build(); + + assertSoftly(softly -> { + softly.assertThat(validator.validate(new A(null)).isValid()) + .as("1-layer nested object is null").isFalse(); + softly.assertThat(validator.validate(new A(new B(null))).isValid()) + .as("2-layer nested object is null (when it is optional)") + .isTrue(); + softly.assertThat(validator.validate(new A(new B(new C(null)))).isValid()) + .as("3-layer nested object is null").isFalse(); + softly.assertThat(validator + .validate(new A(new B(new C(new D(null, null))))).isValid()) + .as("4-layer nested object is null (when it is optional)") + .isTrue(); + softly.assertThat(validator + .validate(new A( + new B(new C(new D(null, singletonList(new E(100))))))) + .isValid()).as("5-layer constraint is valid").isTrue(); + }); + } + } + + public static class Nested { + IntRange intRange; + + Nested(IntRange intRange) { + this.intRange = intRange; + } + } + + public static class IntRange { + public Integer small; + public Integer big; + + IntRange(Integer small, Integer big) { + this.small = small; + this.big = big; + } + } + + public static class MainObject { + Long id; + NestedObject nested; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public NestedObject getNested() { + return nested; + } + + public void setNested(NestedObject nested) { + this.nested = nested; + } + } + + public static class NestedObject { + Long id; + String text; + List intRanges; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public List getIntRanges() { + return intRanges; + } + + public void setIntRanges(List intRanges) { + this.intRanges = intRanges; + } + } + + private static class A { + private final B b; + + public A(B b) { + this.b = b; + } + + public B getB() { + return b; + } + } + + private static class B { + private final C c; + + public B(C c) { + this.c = c; + } + + public C getC() { + return c; + } + } + + private static class C { + private final D d; + + public C(D d) { + this.d = d; + } + + public D getD() { + return d; + } + } + + private static class D { + private final E e; + private final List list; + + private D(E e, List list) { + this.e = e; + this.list = list; + } + + public E getE() { + return e; + } + + public List getList() { + return list; + } + } + + private static class E { + private final int value; + + public E(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ValidatorTest.java new file mode 100644 index 0000000..4b9a31d --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ValidatorTest.java @@ -0,0 +1,1103 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.xbib.datastructures.validation.CalendarEntryLocalDateTime; +import org.xbib.datastructures.validation.CalendarEntryLocalTime; +import org.xbib.datastructures.validation.ConstraintViolationsException; +import org.xbib.datastructures.validation.Range; +import org.xbib.datastructures.validation.User; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.constraint.base.NumericConstraintBase; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.CodePointsRanges; +import org.xbib.datastructures.validation.constraint.charsequence.CodePoints.CodePointsSet; +import org.junit.jupiter.api.Test; + +import static org.xbib.datastructures.validation.constraint.charsequence.variant.IdeographicVariationSequence.IGNORE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +class ValidatorTest { + @Test + void allInvalid() throws Exception { + User user = new User("", "example.com", 300); + user.setEnabled(false); + Validator validator = validator(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(4); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).message()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.email"); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" must be less than or equal to 200"); + assertThat(violations.get(2).messageKey()).isEqualTo("numeric.lessThanOrEqual"); + assertThat(violations.get(3).message()).isEqualTo("\"enabled\" must be true"); + assertThat(violations.get(3).messageKey()).isEqualTo("boolean.isTrue"); + } + + @Test + void codePointsAllIncludedRange() throws Exception { + CodePointsRanges whiteList = () -> Arrays.asList( + CodePoints.Range.of(0x0041 /* A */, 0x005A /* Z */), + CodePoints.Range.of(0x0061 /* a */, 0x007A /* z */)); + + User user = new User("abc@b.c", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.codePoints(whiteList).asWhiteList()) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"[@, .]\" is/are not allowed for \"name\""); + assertThat(violations.get(0).messageKey()).isEqualTo("codePoints.asWhiteList"); + } + + @Test + void codePointsAllIncludedRangeBeginToEnd() throws Exception { + User user = new User("abc@b.c", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.codePoints(0x0041 /* A */, 0x007A /* z */).asWhiteList()) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"[@, .]\" is/are not allowed for \"name\""); + assertThat(violations.get(0).messageKey()).isEqualTo("codePoints.asWhiteList"); + } + + @Test + void codePointsAllIncludedRangeRange() throws Exception { + User user = new User("abc@b.c", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", c -> c + .codePoints(CodePoints.Range.of(0x0041 /* A */, 0x005A /* Z */), + CodePoints.Range.of(0x0061 /* a */, 0x007A /* z */)) + .asWhiteList()) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"[@, .]\" is/are not allowed for \"name\""); + assertThat(violations.get(0).messageKey()).isEqualTo("codePoints.asWhiteList"); + } + + @Test + void codePointsAllIncludedSet() throws Exception { + CodePointsSet whiteList = () -> new HashSet<>( + Arrays.asList(0x0041 /* A */, 0x0042 /* B */, 0x0043 /* C */, + 0x0044 /* D */, 0x0045 /* E */, 0x0046 /* F */, 0x0047 /* G */, + 0x0048 /* H */, 0x0049 /* I */, 0x004A /* J */, 0x004B /* K */, + 0x004C /* L */, 0x004D /* M */, 0x004E /* N */, 0x004F /* O */, + 0x0050 /* P */, 0x0051 /* Q */, 0x0052 /* R */, 0x0053 /* S */, + 0x0054 /* T */, 0x0055 /* U */, 0x0056 /* V */, 0x0057 /* W */, + 0x0058 /* X */, 0x0059 /* Y */, 0x005A /* Z */, // + 0x0061 /* a */, 0x0062 /* b */, 0x0063 /* c */, 0x0064 /* d */, + 0x0065 /* e */, 0x0066 /* f */, 0x0067 /* g */, 0x0068 /* h */, + 0x0069 /* i */, 0x006A /* j */, 0x006B /* k */, 0x006C /* l */, + 0x006D /* m */, 0x006E /* n */, 0x006F /* o */, 0x0070 /* p */, + 0x0071 /* q */, 0x0072 /* r */, 0x0073 /* s */, 0x0074 /* t */, + 0x0075 /* u */, 0x0076 /* v */, 0x0077 /* w */, 0x0078 /* x */, + 0x0079 /* y */, 0x007A /* z */)); + + User user = new User("abc@b.c", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.codePoints(whiteList).asWhiteList()) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"[@, .]\" is/are not allowed for \"name\""); + assertThat(violations.get(0).messageKey()).isEqualTo("codePoints.asWhiteList"); + } + + @Test + void codePointsAllIncludedSetSet() throws Exception { + Set whiteList = new HashSet<>( + Arrays.asList(0x0041 /* A */, 0x0042 /* B */, 0x0043 /* C */, + 0x0044 /* D */, 0x0045 /* E */, 0x0046 /* F */, 0x0047 /* G */, + 0x0048 /* H */, 0x0049 /* I */, 0x004A /* J */, 0x004B /* K */, + 0x004C /* L */, 0x004D /* M */, 0x004E /* N */, 0x004F /* O */, + 0x0050 /* P */, 0x0051 /* Q */, 0x0052 /* R */, 0x0053 /* S */, + 0x0054 /* T */, 0x0055 /* U */, 0x0056 /* V */, 0x0057 /* W */, + 0x0058 /* X */, 0x0059 /* Y */, 0x005A /* Z */, // + 0x0061 /* a */, 0x0062 /* b */, 0x0063 /* c */, 0x0064 /* d */, + 0x0065 /* e */, 0x0066 /* f */, 0x0067 /* g */, 0x0068 /* h */, + 0x0069 /* i */, 0x006A /* j */, 0x006B /* k */, 0x006C /* l */, + 0x006D /* m */, 0x006E /* n */, 0x006F /* o */, 0x0070 /* p */, + 0x0071 /* q */, 0x0072 /* r */, 0x0073 /* s */, 0x0074 /* t */, + 0x0075 /* u */, 0x0076 /* v */, 0x0077 /* w */, 0x0078 /* x */, + 0x0079 /* y */, 0x007A /* z */)); + + User user = new User("abc@b.c", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.codePoints(whiteList).asWhiteList()) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"[@, .]\" is/are not allowed for \"name\""); + assertThat(violations.get(0).messageKey()).isEqualTo("codePoints.asWhiteList"); + } + + @Test + void codePointsNotIncludedRange() throws Exception { + CodePointsRanges blackList = () -> Arrays.asList( + CodePoints.Range.of(0x0041 /* A */, 0x0042 /* B */), + CodePoints.Range.of(0x0061 /* a */, 0x0062 /* b */)); + + User user = new User("abcA@Bb.c", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.codePoints(blackList).asBlackList()) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"[a, b, A, B]\" is/are not allowed for \"name\""); + assertThat(violations.get(0).messageKey()).isEqualTo("codePoints.asBlackList"); + } + + @Test + void codePointsNotIncludedSet() throws Exception { + CodePointsSet blackList = () -> new HashSet<>( + Arrays.asList(0x0061 /* a */, 0x0062 /* b */)); + + User user = new User("abcA@Bb.c", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.codePoints(blackList).asBlackList()) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"[a, b]\" is/are not allowed for \"name\""); + assertThat(violations.get(0).messageKey()).isEqualTo("codePoints.asBlackList"); + } + + @Test + void combiningCharacterByteSizeInValid() throws Exception { + User user = new User("モジ" /* モシ\u3099 */, null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.lessThanOrEqual(2).asByteArray().lessThanOrEqual(6)) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "The byte size of \"name\" must be less than or equal to 6. The given size is 9"); + } + + @Test + void combiningCharacterSizeAndByteSizeInValid() throws Exception { + User user = new User("モジ" /* モシ\u3099 */, null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.lessThanOrEqual(1).asByteArray().lessThanOrEqual(3)) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(2); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be less than or equal to 1. The given size is 2"); + assertThat(violations.get(1).message()).isEqualTo( + "The byte size of \"name\" must be less than or equal to 3. The given size is 9"); + } + + @Test + void combiningCharacterValid() throws Exception { + User user = new User("モジ" /* モシ\u3099 */, null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.fixedSize(2).asByteArray().fixedSize(9)) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void condition() { + Validator validator = ValidatorBuilder.of(User.class) // + .constraintOnCondition((u, cg) -> !u.getName().isEmpty(), // + b -> b.constraint(User::getEmail, "email", + c -> c.email().notEmpty())) + .build(); + { + User user = new User("", "", -1); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + { + User user = new User("foobar", "", -1); + ConstraintViolations violations = validator.validate(user, Group.UPDATE); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"email\" must not be empty"); + assertThat(violations.get(0).messageKey()).isEqualTo("container.notEmpty"); + } + } + + @Test + void constraintOnTarget() { + Validator validator = ValidatorBuilder.of(Range.class) // + .constraintOnTarget(Range::isToGreaterThanFrom, "to", + "to.isGreaterThanFrom", "\"to\" must be greater than \"from\".") // + .build(); + { + Range range = new Range(1, 10); + ConstraintViolations violations = validator.validate(range); + assertThat(violations.isValid()).isTrue(); + } + { + Range range = new Range(10, 1); + ConstraintViolations violations = validator.validate(range); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"to\" must be greater than \"from\"."); + assertThat(violations.get(0).messageKey()).isEqualTo("to.isGreaterThanFrom"); + } + } + + @Test + void customMessageFormatter() throws Exception { + Validator validator = ValidatorBuilder.of(User.class) + .messageFormatter((messageKey, defaultMessageFormat, args, + locale) -> args[0].toString().toUpperCase() + "." + + messageKey.toUpperCase()) + .constraint(User::getAge, "age", c -> c.notNull() // + .greaterThanOrEqual(0) // + .lessThanOrEqual(20)) + .build(); + User user = new User("foo", "foo@example.com", 30); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("AGE.NUMERIC.LESSTHANOREQUAL"); + assertThat(violations.get(0).messageKey()).isEqualTo("numeric.lessThanOrEqual"); + } + + @Test + void details() throws Exception { + User user = new User("", "example.com", 300); + Validator validator = validator(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + List details = violations.details(); + assertThat(details.size()).isEqualTo(3); + assertThat(details.get(0).getDefaultMessage()).isEqualTo( + "The size of \"name\" must be greater than or equal to 1. The given size is 0"); + assertThat(details.get(0).getKey()).isEqualTo("container.greaterThanOrEqual"); + assertThat(details.get(0).getArgs()).containsExactly("name", 1, 0); + assertThat(details.get(1).getDefaultMessage()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(details.get(1).getKey()).isEqualTo("charSequence.email"); + assertThat(details.get(1).getArgs()).containsExactly("email", "example.com"); + assertThat(details.get(2).getDefaultMessage()) + .isEqualTo("\"age\" must be less than or equal to 200"); + assertThat(details.get(2).getKey()).isEqualTo("numeric.lessThanOrEqual"); + assertThat(details.get(2).getArgs()).containsExactly("age", 200, 300); + } + + @Test + void emojiInValid() throws Exception { + User user = new User("I❤️☕️", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", c -> c.emoji().greaterThan(3)).build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be greater than 3. The given size is 3"); + } + + @Test + void emojiValid() throws Exception { + User user = new User("I❤️☕️", null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", c -> c.emoji().lessThanOrEqual(3)) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void group() { + User user = new User("foobar", "foo@example.com", -1); + Validator validator = ValidatorBuilder.of(User.class) // + .constraintOnCondition(Group.UPDATE.toCondition(), // + b -> b.constraint(User::getName, "name", + c -> c.lessThanOrEqual(5))) + .build(); + { + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + { + ConstraintViolations violations = validator.validate(user, Group.UPDATE); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be less than or equal to 5. The given size is 6"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.lessThanOrEqual"); + } + } + + @Test + void groupConditionByGroup() { + User user = new User("foobar", "foo@example.com", -1); + Validator validator = ValidatorBuilder.of(User.class) // + .constraintOnCondition( + (u, cg) -> cg == Group.UPDATE || cg == Group.DELETE, // + b -> b.constraint(User::getName, "name", + c -> c.lessThanOrEqual(5))) + .build(); + { + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + { + ConstraintViolations violations = validator.validate(user, Group.UPDATE); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be less than or equal to 5. The given size is 6"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.lessThanOrEqual"); + } + { + ConstraintViolations violations = validator.validate(user, Group.DELETE); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be less than or equal to 5. The given size is 6"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.lessThanOrEqual"); + } + } + + @Test + void groupTwoCondition() { + User user = new User("foobar", "foo@example.com", -1); + Validator validator = ValidatorBuilder.of(User.class) // + .constraintOnGroup(Group.UPDATE, // + b -> b.constraint(User::getName, "name", + c -> c.lessThanOrEqual(5))) + .constraintOnGroup(Group.DELETE, // + b -> b.constraint(User::getName, "name", + c -> c.greaterThanOrEqual(5))) + .build(); + { + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + { + ConstraintViolations violations = validator.validate(user, Group.UPDATE); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be less than or equal to 5. The given size is 6"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.lessThanOrEqual"); + } + { + ConstraintViolations violations = validator.validate(user, Group.DELETE); + assertThat(violations.isValid()).isTrue(); + } + } + + @Test + void ivsByteSizeInValid() throws Exception { + User user = new User("葛󠄁飾区" /* 葛\uDB40\uDD01飾区 */, null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.variant(opts -> opts.ivs(IGNORE)).lessThanOrEqual(3) + .asByteArray().lessThanOrEqual(12)) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "The byte size of \"name\" must be less than or equal to 12. The given size is 13"); + } + + @Test + void ivsInValid() throws Exception { + User user = new User("葛󠄁飾区" /* 葛\uDB40\uDD01飾区 */, null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.fixedSize(3).asByteArray().fixedSize(13)) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("The size of \"name\" must be 3. The given size is 4"); + } + + @Test + void ivsSizeAndByteSizeInValid() throws Exception { + User user = new User("葛󠄁飾区" /* 葛\uDB40\uDD01飾区 */, null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.lessThanOrEqual(3).asByteArray().lessThanOrEqual(12)) + .build(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(2); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be less than or equal to 3. The given size is 4"); + assertThat(violations.get(1).message()).isEqualTo( + "The byte size of \"name\" must be less than or equal to 12. The given size is 13"); + } + + @Test + void ivsValid() throws Exception { + User user = new User("葛󠄁飾区" /* 葛\uDB40\uDD01飾区 */, null, null); + Validator validator = ValidatorBuilder.of(User.class) + .constraint(User::getName, "name", + c -> c.variant(opts -> opts.ivs(IGNORE)).fixedSize(3) + .asByteArray().fixedSize(13)) + .build(); + ConstraintViolations violations = validator.validate(user); + violations.forEach(x -> System.out.println(x.message())); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void multipleViolationOnOneProperty() throws Exception { + User user = new User("foo", "aa", 200); + Validator validator = validator(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(2); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"email\" must be greater than or equal to 5. The given size is 2"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).message()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.email"); + } + + @Test + void nullValues() throws Exception { + User user = new User(null, null, null); + Validator validator = validator(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo("\"name\" must not be null"); + assertThat(violations.get(0).messageKey()).isEqualTo("object.notNull"); + assertThat(violations.get(1).message()).isEqualTo("\"email\" must not be null"); + assertThat(violations.get(1).messageKey()).isEqualTo("object.notNull"); + assertThat(violations.get(2).message()).isEqualTo("\"age\" must not be null"); + assertThat(violations.get(2).messageKey()).isEqualTo("object.notNull"); + } + + @Test + void overrideMessage() { + Validator validator = ValidatorBuilder. of() // + .constraint(User::getName, "name", + c -> c.notNull().message("name is required!") // + .greaterThanOrEqual(1).message("name is too small!") // + .lessThanOrEqual(20).message("name is too large!")) // + .build(); + + { + User user = new User(null, "a@b.c", 10); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("name is required!"); + assertThat(violations.get(0).messageKey()).isEqualTo("object.notNull"); + } + { + User user = new User("", "a@b.c", 10); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("name is too small!"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + } + { + User user = new User("012345678901234567890", "a@b.c", 10); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("name is too large!"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.lessThanOrEqual"); + } + } + + @Test + void overrideViolationMessage() { + Validator validator = ValidatorBuilder. of() // + .constraint(User::getName, "name", + c -> c.notNull() + .message(ViolationMessage.of("a", "name is required!")) // + .greaterThanOrEqual(1) + .message(ViolationMessage.of("b", "name is too small!")) // + .lessThanOrEqual(20) + .message(ViolationMessage.of("c", "name is too large!"))) // + .build(); + + { + User user = new User(null, "a@b.c", 10); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("name is required!"); + assertThat(violations.get(0).messageKey()).isEqualTo("a"); + } + { + User user = new User("", "a@b.c", 10); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("name is too small!"); + assertThat(violations.get(0).messageKey()).isEqualTo("b"); + } + { + User user = new User("012345678901234567890", "a@b.c", 10); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("name is too large!"); + assertThat(violations.get(0).messageKey()).isEqualTo("c"); + } + } + + @Test + void throwIfInValidInValid() throws Exception { + User user = new User("foo", "foo@example.com", -1); + try { + validator().validate(user).throwIfInvalid(ConstraintViolationsException::new); + fail("fail"); + } + catch (ConstraintViolationsException e) { + ConstraintViolations violations = e.getViolations(); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"age\" must be greater than or equal to 0"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("numeric.greaterThanOrEqual"); + } + } + + @Test + void throwIfInValidValid() throws Exception { + User user = new User("foo", "foo@example.com", 30); + validator().validate(user).throwIfInvalid(ConstraintViolationsException::new); + } + + @Test + void valid() throws Exception { + User user = new User("foo", "foo@example.com", 30); + Validator validator = validator(); + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void violateGroupAndDefault() { + User user = new User("foobar", "foo@example.com", -1); + Validator validator = ValidatorBuilder.of(User.class) // + .constraint(User::getEmail, "email", c -> c.email().lessThanOrEqual(10)) + .constraintOnGroup(Group.UPDATE, // + b -> b.constraint(User::getName, "name", + c -> c.lessThanOrEqual(5))) + .build(); + { + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"email\" must be less than or equal to 10. The given size is 15"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.lessThanOrEqual"); + } + { + ConstraintViolations violations = validator.validate(user, Group.UPDATE); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(2); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"email\" must be less than or equal to 10. The given size is 15"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.lessThanOrEqual"); + assertThat(violations.get(1).message()).isEqualTo( + "The size of \"name\" must be less than or equal to 5. The given size is 6"); + assertThat(violations.get(1).messageKey()) + .isEqualTo("container.lessThanOrEqual"); + } + } + + @Test + void agePositiveValidatorUserValid() { + User user = new User("Diego", "foo@bar.com", 10); + + Validator validator = ValidatorBuilder. of() + .constraint(User::getAge, "age", NumericConstraintBase::positive).build(); + + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void agePositiveValidatorUserInValid() { + User user = new User("Diego", "foo@bar.com", -1); + + Validator validator = ValidatorBuilder. of() + .constraint(User::getAge, "age", NumericConstraintBase::positive).build(); + + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("\"age\" must be positive"); + } + + @Test + void ageNegativeValidatorUserValid() { + User user = new User("Diego", "foo@bar.com", -1); + + Validator validator = ValidatorBuilder. of() + .constraint(User::getAge, "age", NumericConstraintBase::negative).build(); + + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void ageNegativeValidatorUserInValid() { + User user = new User("Diego", "foo@bar.com", 10); + + Validator validator = ValidatorBuilder. of() + .constraint(User::getAge, "age", NumericConstraintBase::negative).build(); + + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()).isEqualTo("\"age\" must be negative"); + } + + @Test + void calendarDateIsBeforeNowValid() { + LocalDateTime now = LocalDateTime.now(); + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "datetime", + c -> c.before(() -> now.plusHours(10))) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void nameStartWithValid() { + User user = new User("Diego Krupitza", "foo@bar.com", 22); + + Validator validator = ValidatorBuilder. of() + .constraint(User::getName, "name", c -> c.startsWith("Diego")).build(); + + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void calendarDateIsBeforeNowInValid() { + LocalDateTime now = LocalDateTime.now(); + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "datetime", + c -> c.before(() -> now.minusHours(10))) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"datetime\" has to be before " + now.minusHours(10)); + } + + @Test + void calendarDateIsAfterNowInValid() { + LocalDateTime now = LocalDateTime.now(); + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "datetime", + c -> c.after(() -> now.plusHours(10))) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"datetime\" has to be after " + now.plusHours(10)); + } + + @Test + void calendarDateIsAfterNowValid() { + LocalDateTime now = LocalDateTime.now(); + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "datetime", + c -> c.after(() -> now.minusHours(10))) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void calendarDateIsBetweenNowValid() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime before = now.minusHours(10); + LocalDateTime after = now.plusHours(10); + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "datetime", + c -> c.between(() -> before, () -> after)) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void nameStartWithInValid() { + User user = new User("NotDiego", "foo@bar.com", 22); + + Validator validator = ValidatorBuilder. of() + .constraint(User::getName, "name", c -> c.startsWith("Diego")).build(); + + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"name\" must start with \"Diego\""); + } + + @Test + void nameEndsWithValid() { + User user = new User("Diego Krupitza", "foo@bar.com", 22); + + Validator validator = ValidatorBuilder. of() + .constraint(User::getName, "name", c -> c.endsWith("Krupitza")).build(); + + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void calendarDateIsBetweenNowEqualInValid() { + LocalDateTime now = LocalDateTime.now(); + + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "datetime", + c -> c.between(() -> now, () -> now)) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"datetime\" has to be between " + now + " and " + now); + } + + @Test + void calendarDateIsBetweenNowInValid() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime before = now.plusDays(10); + LocalDateTime after = now.plusDays(20); + + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "datetime", + c -> c.between(() -> before, () -> after)) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"datetime\" has to be between " + before + " and " + after); + } + + @Test + void nameEndsWithInValid() { + User user = new User("Diego Not", "foo@bar.com", 22); + + Validator validator = ValidatorBuilder. of() + .constraint(User::getName, "name", c -> c.endsWith("Diego")).build(); + + ConstraintViolations violations = validator.validate(user); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"name\" must end with \"Diego\""); + } + + @Test + void timeIsBeforeNowValid() { + LocalTime now = LocalTime.of(12, 30); + CalendarEntryLocalTime birthdayPartyEntry = new CalendarEntryLocalTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of().constraint(CalendarEntryLocalTime::getTime, + "time", c -> c.before(() -> now.plusHours(10))) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void timeIsBeforeNowInValid() { + LocalTime now = LocalTime.of(12, 30); + CalendarEntryLocalTime birthdayPartyEntry = new CalendarEntryLocalTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of().constraint(CalendarEntryLocalTime::getTime, + "time", c -> c.before(() -> now.minusHours(10))) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"time\" has to be before " + now.minusHours(10)); + } + + @Test + void timeIsAfterNowInValid() { + LocalTime now = LocalTime.of(12, 30); + CalendarEntryLocalTime birthdayPartyEntry = new CalendarEntryLocalTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of().constraint(CalendarEntryLocalTime::getTime, + "time", c -> c.after(() -> now.plusHours(10))) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .startsWith("\"time\" has to be after " + now.plusHours(10)); + } + + @Test + void timeIsAfterNowValid() { + LocalTime now = LocalTime.of(12, 30); + CalendarEntryLocalTime birthdayPartyEntry = new CalendarEntryLocalTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of().constraint(CalendarEntryLocalTime::getTime, + "time", c -> c.after(() -> now.minusHours(10))) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void timeIsBetweenNowValid() { + LocalTime now = LocalTime.of(12, 30); + LocalTime before = now.minusHours(10); + LocalTime after = now.plusHours(10); + + CalendarEntryLocalTime birthdayPartyEntry = new CalendarEntryLocalTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of().constraint(CalendarEntryLocalTime::getTime, + "time", c -> c.between(() -> before, () -> after)) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void timeIsBetweenNowEqualInValid() { + LocalTime now = LocalTime.of(12, 30); + + CalendarEntryLocalTime birthdayPartyEntry = new CalendarEntryLocalTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of().constraint(CalendarEntryLocalTime::getTime, + "time", c -> c.between(() -> now, () -> now)) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .isEqualTo("\"time\" has to be between " + now + " and " + now); + } + + @Test + void timeIsBetweenNowInValid() { + LocalTime now = LocalTime.of(12, 30); + LocalTime before = now.plusHours(10); + LocalTime after = now.plusHours(10).plusMinutes(1); + + CalendarEntryLocalTime birthdayPartyEntry = new CalendarEntryLocalTime( + "BirthdayParty", now); + + Validator validator = ValidatorBuilder + . of().constraint(CalendarEntryLocalTime::getTime, + "time", c -> c.between(() -> before, () -> after)) + .build(); + + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .startsWith("\"time\" has to be between " + before + " and " + after); + } + + @Test + void futureValid() { + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", LocalDateTime.now().plusDays(1)); + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "dateTime", + c -> c.future()) + .build(); + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void futureInValid() { + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", LocalDateTime.now().minusDays(1)); + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "dateTime", + c -> c.future()) + .build(); + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .startsWith("\"dateTime\" must be a future date"); + } + + @Test + void pastValid() { + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", LocalDateTime.now().minusDays(1)); + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "dateTime", + c -> c.past()) + .build(); + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isTrue(); + } + + @Test + void pastInValid() { + CalendarEntryLocalDateTime birthdayPartyEntry = new CalendarEntryLocalDateTime( + "BirthdayParty", LocalDateTime.now().plusDays(1)); + Validator validator = ValidatorBuilder + . of() + .constraint(CalendarEntryLocalDateTime::getDateTime, "dateTime", + c -> c.past()) + .build(); + ConstraintViolations violations = validator.validate(birthdayPartyEntry); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(1); + assertThat(violations.get(0).message()) + .startsWith("\"dateTime\" must be a past date"); + } + + Validator validator() { + return ValidatorBuilder. of() // + .constraint(User::getName, "name", c -> c.notNull() // + .greaterThanOrEqual(1) // + .lessThanOrEqual(20)) // + .constraint(User::getEmail, "email", c -> c.notNull() // + .greaterThanOrEqual(5) // + .lessThanOrEqual(50) // + .email()) // + .constraint(User::getAge, "age", c -> c.notNull() // + .greaterThanOrEqual(0) // + .lessThanOrEqual(200)) // + .constraint(User::isEnabled, "enabled", c -> c.isTrue()) // + .build(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ValueValidatorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ValueValidatorTest.java new file mode 100644 index 0000000..109b072 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/core/ValueValidatorTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.core; + +import org.xbib.datastructures.validation.Address; +import org.xbib.datastructures.validation.Country; +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.arguments.Arguments1Validator; +import org.xbib.datastructures.validation.arguments.Arguments3Validator; +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +class ValueValidatorTest { + + @Test + void passThrough() { + final ValueValidator validator = ValueValidator.passThrough(); + final Validated validated = validator.validate("hello"); + assertThat(validated.isValid()).isTrue(); + assertThat(validated.value()).isEqualTo("hello"); + } + + @Test + void andThenChainsValidators() { + ValueValidator countryValidator = Country.validator() + .applicative().compose(Country::new); + ValueValidator streetValidator = ValueValidator.passThrough(); + ValueValidator phoneValidator = PhoneNumber.validator() + .applicative().compose(PhoneNumber::new); + + Arguments3Validator addressValidator = Arguments1Validator + .from(countryValidator).split(streetValidator).split(phoneValidator) + .apply(Address::new); + + ValueValidator foreignAddressValidator = ValidatorBuilder + .
of() + .constraintOnCondition( + (address, + group) -> !"JP" + .equalsIgnoreCase(address.country().name()), + ValidatorBuilder.
of() + ._string(a -> a.phoneNumber().value(), "PhoneNumber", + c -> c.startsWith("+")) + .build()) + .build().applicative(); + + assertThat(addressValidator.validate("JP", "tokyo", "0123456789").isValid()) + .isTrue(); + assertThat(addressValidator.andThen(foreignAddressValidator) + .validate("JP", "tokyo", "0123456789").isValid()).isTrue(); + assertThat(addressValidator.validate("BE", "brussels", "9876543210").isValid()) + .isTrue(); + assertThat(addressValidator.andThen(foreignAddressValidator) + .validate("BE", "brussels", "9876543210").isValid()).isFalse(); + assertThat(addressValidator.validate("J", "tokyo", "0123456789").isValid()) + .isFalse(); + assertThat(addressValidator.andThen(foreignAddressValidator) + .validate("J", "tokyo", "+0123456789").isValid()).isFalse(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/factory/BiValidatorFactoryTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/factory/BiValidatorFactoryTest.java new file mode 100644 index 0000000..ec491ef --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/factory/BiValidatorFactoryTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.factory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.xbib.datastructures.validation.User; +import org.xbib.datastructures.validation.core.BiValidator; +import org.xbib.datastructures.validation.core.BiValidator.ErrorHandler; +import org.xbib.datastructures.validation.core.ConstraintViolation; +import org.xbib.datastructures.validation.message.SimpleMessageFormatter; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class BiValidatorFactoryTest { + private final ErrorHandler> errorHandler = (errors, name, + messageKey, args, + defaultMessage) -> errors.add(new ConstraintViolation(name, messageKey, + defaultMessage, args, new SimpleMessageFormatter(), Locale.ENGLISH)); + + private final BiValidatorFactory> validatorFactory = new BiValidatorFactory<>( + this.errorHandler); + + @Test + void validator() { + final BiValidator> validator = this.validatorFactory + .validator(builder -> builder + .constraint(User::getName, "name", + c -> c.notNull().greaterThanOrEqual(1) + .lessThanOrEqual(20)) + .constraint(User::getEmail, "email", + c -> c.notNull().greaterThanOrEqual(5).lessThanOrEqual(50) + .email()) + .constraint(User::getAge, "age", + c -> c.notNull().greaterThanOrEqual(0) + .lessThanOrEqual(200)) + .constraint(User::isEnabled, "enabled", c -> c.isTrue())); + + final User user = new User("", "example.com", 300); + user.setEnabled(false); + final List violations = new ArrayList<>(); + + validator.accept(user, violations); + assertThat(violations.size()).isEqualTo(4); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).message()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.email"); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" must be less than or equal to 200"); + assertThat(violations.get(2).messageKey()).isEqualTo("numeric.lessThanOrEqual"); + assertThat(violations.get(3).message()).isEqualTo("\"enabled\" must be true"); + assertThat(violations.get(3).messageKey()).isEqualTo("boolean.isTrue"); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/factory/ValidatorFactoryTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/factory/ValidatorFactoryTest.java new file mode 100644 index 0000000..0f93502 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/factory/ValidatorFactoryTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.factory; + +import org.xbib.datastructures.validation.User; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.Validator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ValidatorFactoryTest { + private final ValidatorFactory validatorFactory = new ValidatorFactory(); + + @Test + void validator() { + final Validator validator = this.validatorFactory + .validator(builder -> builder + .constraint(User::getName, "name", + c -> c.notNull().greaterThanOrEqual(1) + .lessThanOrEqual(20)) + .constraint(User::getEmail, "email", + c -> c.notNull().greaterThanOrEqual(5).lessThanOrEqual(50) + .email()) + .constraint(User::getAge, "age", + c -> c.notNull().greaterThanOrEqual(0) + .lessThanOrEqual(200)) + .constraint(User::isEnabled, "enabled", c -> c.isTrue())); + + final User user = new User("", "example.com", 300); + user.setEnabled(false); + + final ConstraintViolations violations = validator.validate(user); + assertThat(violations.size()).isEqualTo(4); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"name\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(0).messageKey()) + .isEqualTo("container.greaterThanOrEqual"); + assertThat(violations.get(1).message()) + .isEqualTo("\"email\" must be a valid email address"); + assertThat(violations.get(1).messageKey()).isEqualTo("charSequence.email"); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" must be less than or equal to 200"); + assertThat(violations.get(2).messageKey()).isEqualTo("numeric.lessThanOrEqual"); + assertThat(violations.get(3).message()).isEqualTo("\"enabled\" must be true"); + assertThat(violations.get(3).messageKey()).isEqualTo("boolean.isTrue"); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/FunctionsTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/FunctionsTest.java new file mode 100644 index 0000000..05db4a2 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/FunctionsTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class FunctionsTest { + + @Test + void curry() { + final Function2 add = (x, y) -> x + y; + final Function1 add10 = Functions.curry(add).apply(10); + final Integer result = add10.apply(2); + assertThat(result).isEqualTo(12); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/ValidationTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/ValidationTest.java new file mode 100644 index 0000000..34a89a8 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/ValidationTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import org.junit.jupiter.api.Test; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ValidationTest { + + @Test + void success() { + final Validation validation = Validation.success("test"); + assertThat(validation.isValid()).isTrue(); + assertThat(validation.value()).isEqualTo("test"); + assertThatThrownBy(validation::errors).isInstanceOf(NoSuchElementException.class); + assertThat(validation.map(String::toUpperCase).value()).isEqualTo("TEST"); + assertThat(validation + .mapErrors(x -> x.stream().map(String::toUpperCase).collect(toList())) + .value()).isEqualTo("test"); + assertThat(validation.flatMap(s -> Validation.success("hello " + s)).value()) + .isEqualTo("hello test"); + assertThat(validation).isEqualTo(Validation.success("test")); + assertThat(validation.hashCode()) + .isEqualTo(Validation.success("test").hashCode()); + } + + @Test + void failure() { + final Validation validation = Validation.failure("errors1", + "errors2"); + assertThat(validation.isValid()).isFalse(); + assertThatThrownBy(validation::value).isInstanceOf(NoSuchElementException.class); + assertThat(validation.errors()).isEqualTo(Arrays.asList("errors1", "errors2")); + assertThat(validation.map(String::toUpperCase).errors()) + .isEqualTo(Arrays.asList("errors1", "errors2")); + assertThat(validation + .mapErrors(x -> x.stream().map(String::toUpperCase).collect(toList())) + .errors()).isEqualTo(Arrays.asList("ERRORS1", "ERRORS2")); + assertThat(validation.flatMap(s -> Validation.success("hello " + s)).errors()) + .isEqualTo(Arrays.asList("errors1", "errors2")); + assertThat(validation).isEqualTo(Validation.failure("errors1", "errors2")); + assertThat(validation.hashCode()) + .isEqualTo(Validation.failure("errors1", "errors2").hashCode()); + } + + @Test + void foldSuccess() { + final Validation validation = Validation.success("test"); + final String fold = validation.fold(e -> String.join(",", e), + String::toUpperCase); + assertThat(fold).isEqualTo("TEST"); + } + + @Test + void foldFailure() { + final Validation validation = Validation.failure("errors1", + "errors2"); + final String fold = validation.fold(e -> String.join(",", e), + String::toUpperCase); + assertThat(fold).isEqualTo("errors1,errors2"); + } + + @Test + void bimapSuccess() { + final Validation validation = Validation.success("test"); + final Validation bimap = validation.bimap( + errors -> errors.stream().map(String::toUpperCase).collect(toList()), + String::toUpperCase); + assertThat(bimap.isValid()).isTrue(); + assertThat(bimap.value()).isEqualTo("TEST"); + } + + @Test + void bimapFailure() { + final Validation validation = Validation.failure("errors1", + "errors2"); + final Validation bimap = validation.bimap( + errors -> errors.stream().map(String::toUpperCase).collect(toList()), + String::toUpperCase); + assertThat(bimap.isValid()).isFalse(); + assertThat(bimap.errors()).isEqualTo(Arrays.asList("ERRORS1", "ERRORS2")); + } + + @Test + void combine_all_valid() { + final Validation v1 = Validation.success("s1"); + final Validation v2 = Validation.success("s2"); + final Validation v3 = Validation.success("s3"); + final Validation v4 = Validation.success("s4"); + final Validation v5 = Validation.success("s5"); + final Validation v6 = Validation.success("s6"); + final Validation v7 = Validation.success("s7"); + final Validation v8 = Validation.success("s8"); + final Validation v9 = Validation.success("s9"); + final Validation v10 = Validation.success("s10"); + + final Validation validation = v1.combine(v2).combine(v3) + .combine(v4).combine(v5).combine(v6).combine(v7).combine(v8).combine(v9) + .combine(v10).apply((s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) -> String + .join(", ", s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)); + assertThat(validation.isValid()).isTrue(); + assertThat(validation.value()) + .isEqualTo("s1, s2, s3, s4, s5, s6, s7, s8, s9, s10"); + } + + @Test + void combine_all_invalid() { + final Validation v1 = Validation.failure("f1"); + final Validation v2 = Validation.failure("f2"); + final Validation v3 = Validation.failure("f3"); + final Validation v4 = Validation.failure("f4"); + final Validation v5 = Validation.failure("f5"); + final Validation v6 = Validation.failure("f6"); + final Validation v7 = Validation.failure("f7"); + final Validation v8 = Validation.failure("f8"); + final Validation v9 = Validation.failure("f9"); + final Validation v10 = Validation.failure("f10"); + + final Validation validation = v1.combine(v2).combine(v3) + .combine(v4).combine(v5).combine(v6).combine(v7).combine(v8).combine(v9) + .combine(v10).apply((s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) -> String + .join(", ", s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)); + + assertThat(validation.isValid()).isFalse(); + assertThat(validation.errors()).containsExactly("f1", "f2", "f3", "f4", "f5", + "f6", "f7", "f8", "f9", "f10"); + } + + @Test + void combine_first_invalid() { + final Validation v1 = Validation.failure("f1"); + final Validation v2 = Validation.success("s2"); + final Validation v3 = Validation.success("s3"); + final Validation v4 = Validation.success("s4"); + final Validation v5 = Validation.success("s5"); + final Validation v6 = Validation.success("s6"); + final Validation v7 = Validation.success("s7"); + final Validation v8 = Validation.success("s8"); + final Validation v9 = Validation.success("s9"); + final Validation v10 = Validation.success("s10"); + + final Validation validation = v1.combine(v2).combine(v3) + .combine(v4).combine(v5).combine(v6).combine(v7).combine(v8).combine(v9) + .combine(v10).apply((s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) -> String + .join(", ", s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)); + + assertThat(validation.isValid()).isFalse(); + assertThat(validation.errors()).containsExactly("f1"); + } + + @Test + void combine_last_invalid() { + final Validation v1 = Validation.success("s1"); + final Validation v2 = Validation.success("s2"); + final Validation v3 = Validation.success("s3"); + final Validation v4 = Validation.success("s4"); + final Validation v5 = Validation.success("s5"); + final Validation v6 = Validation.success("s6"); + final Validation v7 = Validation.success("s7"); + final Validation v8 = Validation.success("s8"); + final Validation v9 = Validation.success("s9"); + final Validation v10 = Validation.failure("f10"); + + final Validation validation = v1.combine(v2).combine(v3) + .combine(v4).combine(v5).combine(v6).combine(v7).combine(v8).combine(v9) + .combine(v10).apply((s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) -> String + .join(", ", s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)); + + assertThat(validation.isValid()).isFalse(); + assertThat(validation.errors()).containsExactly("f10"); + } + + @Test + void orElseThrowSuccess() { + final String s = Validation. success("test") + .orElseThrow(errors -> new IllegalArgumentException("errors=" + errors)); + assertThat(s).isEqualTo("test"); + } + + @Test + void orElseThrowFailure() { + assertThatThrownBy(() -> Validation + . failure(Arrays.asList("e1", "e2")) + .orElseThrow(errors -> new IllegalArgumentException("errors=" + errors))) + .hasMessage("errors=[e1, e2]") + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void orElseGetSuccess() { + final String s = Validation. success("test") + .orElseGet(errors -> String.join(",", errors)); + assertThat(s).isEqualTo("test"); + } + + @Test + void orElseGetFailure() { + final String s = Validation. failure("e1", "e2") + .orElseGet(errors -> String.join(",", errors)); + assertThat(s).isEqualTo("e1,e2"); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/ValidationsTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/ValidationsTest.java new file mode 100644 index 0000000..7808fe2 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/fn/ValidationsTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.fn; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ValidationsTest { + @Test + void combine() { + final Validation, String> v1 = Validation.success("s1"); + final Validation, String> v2 = Validation.success("s2"); + final Validation, String> v3 = Validation.success("s3"); + + final String s = Validations.combine(v1, v2, v3) + .apply((s1, s2, s3) -> String.join("-", s1, s2, s3)).value(); + assertThat(s).isEqualTo("s1-s2-s3"); + } + + @Test + void apply() { + final Validation, String> v1 = Validation.success("s1"); + final Validation, String> v2 = Validation.success("s2"); + final Validation, String> v3 = Validation.success("s3"); + + final String s = Validations + .apply((s1, s2, s3) -> String.join("-", s1, s2, s3), v1, v2, v3).value(); + assertThat(s).isEqualTo("s1-s2-s3"); + } + + @Test + void sequenceValid() { + final Validation, List> validation = Validations + .sequence(Arrays.asList(Validation.success(1), Validation.success(2))); + assertThat(validation.value()).containsExactly(1, 2); + } + + @Test + void sequenceInvalid() { + final Validation> validation = Validations + .sequence(Arrays.asList(Validation.success(1), + Validation.failure(Arrays.asList("e1", "e2")), + Validation.success(2), + Validation.failure(Arrays.asList("e3", "e4")))); + assertThat(validation.errors()).containsExactly("e1", "e2", "e3", "e4"); + } + + @Test + void traverseValid() { + final Validation> validation = Validations + .traverse(Arrays.asList(1, 2), i -> Validation.success(i)); + assertThat(validation.value()).containsExactly(1, 2); + } + + @Test + void traverseInvalid() { + final Validation> validation = Validations + .traverse(Arrays.asList(1, -1, 2, -2), i -> { + if (i > 0) { + return Validation.success(i); + } + else { + return Validation.failure( + Arrays.asList("e" + (-2 * i - 1), "e" + (-2 * i))); + } + }); + assertThat(validation.errors()).containsExactly("e1", "e2", "e3", "e4"); + } + + @Test + void traverseIndexedInvalid() { + final Validation> validation = Validations + .traverseIndexed(Arrays.asList(1, -1, 2, -2), (i, index) -> { + if (i > 0) { + return Validation.success(i); + } + else { + return Validation.failure("e[" + index + "]"); + } + }); + assertThat(validation.errors()).containsExactly("e[1]", "e[3]"); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/CustomMessageFormatter.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/CustomMessageFormatter.java new file mode 100644 index 0000000..316b371 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/CustomMessageFormatter.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.message; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public enum CustomMessageFormatter implements MessageFormatter { + INSTANCE; + + private final Map formats; + + CustomMessageFormatter() { + this.formats = Collections.unmodifiableMap(new HashMap() { + { + put("object.notNull", "Für \"{0}\" muss ein Wert vorhanden sein."); + put("container.lessThanOrEqual", + "Die Länge von \"{0}\" muss kleiner oder gleich {1} sein. Aktuelle Länge ist {2}."); + put("container.greaterThanOrEqual", + "Die Länge von \"{0}\" muss größer oder gleich {1} sein. Aktuelle Länge ist {2}."); + put("numeric.greaterThanOrEqual", "\"{0}\" muss größer gleich {1} sein."); + put("numeric.lessThanOrEqual", "\"{0}\" muss kleiner gleich {1} sein."); + put("boolean.isTrue", "\"{0}\" muss wahr sein."); + put("charSequence.email", + "\"{0}\" muss eine gültige E-Mail Adresse sein."); + } + }); + } + + @Override + public String format(String messageKey, String defaultMessageFormat, Object[] args, + Locale locale) { + final String format = this.formats.getOrDefault(messageKey, defaultMessageFormat); + return new MessageFormat(format, locale).format(args); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/CustomMessageFormatterTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/CustomMessageFormatterTest.java new file mode 100644 index 0000000..4e8bf69 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/CustomMessageFormatterTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.message; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.xbib.datastructures.validation.User; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.Validator; + +class CustomMessageFormatterTest { + + @Test + void customMessage() { + final Validator validator = ValidatorBuilder. of() + .messageFormatter(CustomMessageFormatter.INSTANCE) + .constraint(User::getName, "name", c -> c.greaterThanOrEqual(2)) + .constraint(User::getEmail, "email", c -> c.notNull()) + .constraint(User::getAge, "age", c -> c.lessThanOrEqual(20)).build(); + + final ConstraintViolations violations = validator + .validate(new User("a", null, 30)); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo( + "Die Länge von \"name\" muss größer oder gleich 2 sein. Aktuelle Länge ist 1."); + assertThat(violations.get(1).message()) + .isEqualTo("Für \"email\" muss ein Wert vorhanden sein."); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" muss kleiner gleich 20 sein."); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/MessageSourceMessageFormatterTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/MessageSourceMessageFormatterTest.java new file mode 100644 index 0000000..d6279a8 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/message/MessageSourceMessageFormatterTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.message; + +import java.util.Arrays; +import java.util.Locale; + +import org.xbib.datastructures.validation.message.MessageSourceMessageFormatter.MessageSource; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MessageSourceMessageFormatterTest { + + @Test + void format() { + final MessageSource messageSource = (code, args, defaultMessage, + locale) -> "Message " + code + " " + Arrays.toString(args); + final MessageSourceMessageFormatter messageFormatter = new MessageSourceMessageFormatter( + messageSource); + final String message = messageFormatter.format("demo", "", new Object[] { 1, 2 }, + Locale.ENGLISH); + assertThat(message).isEqualTo("Message demo [1, 2]"); + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/Address.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/Address.java new file mode 100644 index 0000000..fd3ad64 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/Address.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +public class Address { + + private final Country country; + + private final PhoneNumber phoneNumber; + + private final String street; + + public Address(@ConstraintTarget(getter = false) Country country, + @ConstraintTarget(getter = false) String street, + @ConstraintTarget(getter = false) PhoneNumber phoneNumber) { + this.country = country; + this.street = street; + this.phoneNumber = phoneNumber; + } + + public Country country() { + return this.country; + } + + public PhoneNumber phoneNumber() { + return this.phoneNumber; + } + + public String street() { + return this.street; + } + + public static class Country { + + private final String name; + + public Country(@ConstraintTarget(getter = false) String name) { + this.name = name; + } + + public String name() { + return this.name; + } + } + + public static class PhoneNumber { + + private final String value; + + public PhoneNumber(@ConstraintTarget(getter = false) String value) { + this.value = value; + } + + public String value() { + return this.value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesBean.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesBean.java new file mode 100644 index 0000000..178b944 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesBean.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; + +public class AllTypesBean { + + private String stringValue; + + private Boolean booleanValue; + + private boolean booleanPrimitiveValue; + + private Character characterValue; + + private char characterPrimitiveValue; + + private Byte byteValue; + + private byte bytePrimitiveValue; + + private Short shortValue; + + private short shortPrimitiveValue; + + private Integer integerValue; + + private int integerPrimitiveValue; + + private Long longValue; + + private long longPrimitiveValue; + + private Float floatValue; + + private float floatPrimitiveValue; + + private Double doubleValue; + + private double doublePrimitiveValue; + + private BigInteger bigIntegerValue; + + private BigDecimal bigDecimalValue; + + private LocalDate localDateValue; + + @ConstraintTarget + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + @ConstraintTarget + public Boolean getBooleanValue() { + return booleanValue; + } + + public void setBooleanValue(Boolean booleanValue) { + this.booleanValue = booleanValue; + } + + @ConstraintTarget + public boolean isBooleanPrimitiveValue() { + return booleanPrimitiveValue; + } + + public void setBooleanPrimitiveValue(boolean booleanPrimitiveValue) { + this.booleanPrimitiveValue = booleanPrimitiveValue; + } + + @ConstraintTarget + public Character getCharacterValue() { + return characterValue; + } + + public void setCharacterValue(Character characterValue) { + this.characterValue = characterValue; + } + + @ConstraintTarget + public char getCharacterPrimitiveValue() { + return characterPrimitiveValue; + } + + public void setCharacterPrimitiveValue(char characterPrimitiveValue) { + this.characterPrimitiveValue = characterPrimitiveValue; + } + + @ConstraintTarget + public Byte getByteValue() { + return byteValue; + } + + public void setByteValue(Byte byteValue) { + this.byteValue = byteValue; + } + + @ConstraintTarget + public byte getBytePrimitiveValue() { + return bytePrimitiveValue; + } + + public void setBytePrimitiveValue(byte bytePrimitiveValue) { + this.bytePrimitiveValue = bytePrimitiveValue; + } + + @ConstraintTarget + public Short getShortValue() { + return shortValue; + } + + public void setShortValue(Short shortValue) { + this.shortValue = shortValue; + } + + @ConstraintTarget + public short getShortPrimitiveValue() { + return shortPrimitiveValue; + } + + public void setShortPrimitiveValue(short shortPrimitiveValue) { + this.shortPrimitiveValue = shortPrimitiveValue; + } + + @ConstraintTarget + public Integer getIntegerValue() { + return integerValue; + } + + public void setIntegerValue(Integer integerValue) { + this.integerValue = integerValue; + } + + @ConstraintTarget + public int getIntegerPrimitiveValue() { + return integerPrimitiveValue; + } + + public void setIntegerPrimitiveValue(int integerPrimitiveValue) { + this.integerPrimitiveValue = integerPrimitiveValue; + } + + @ConstraintTarget + public Long getLongValue() { + return longValue; + } + + public void setLongValue(Long longValue) { + this.longValue = longValue; + } + + @ConstraintTarget + public long getLongPrimitiveValue() { + return longPrimitiveValue; + } + + public void setLongPrimitiveValue(long longPrimitiveValue) { + this.longPrimitiveValue = longPrimitiveValue; + } + + @ConstraintTarget + public Float getFloatValue() { + return floatValue; + } + + public void setFloatValue(Float floatValue) { + this.floatValue = floatValue; + } + + @ConstraintTarget + public float getFloatPrimitiveValue() { + return floatPrimitiveValue; + } + + public void setFloatPrimitiveValue(float floatPrimitiveValue) { + this.floatPrimitiveValue = floatPrimitiveValue; + } + + @ConstraintTarget + public Double getDoubleValue() { + return doubleValue; + } + + public void setDoubleValue(Double doubleValue) { + this.doubleValue = doubleValue; + } + + @ConstraintTarget + public double getDoublePrimitiveValue() { + return doublePrimitiveValue; + } + + public void setDoublePrimitiveValue(double doublePrimitiveValue) { + this.doublePrimitiveValue = doublePrimitiveValue; + } + + @ConstraintTarget + public BigInteger getBigIntegerValue() { + return bigIntegerValue; + } + + public void setBigIntegerValue(BigInteger bigIntegerValue) { + this.bigIntegerValue = bigIntegerValue; + } + + @ConstraintTarget + public BigDecimal getBigDecimalValue() { + return bigDecimalValue; + } + + public void setBigDecimalValue(BigDecimal bigDecimalValue) { + this.bigDecimalValue = bigDecimalValue; + } + + @ConstraintTarget + public LocalDate getLocalDateValue() { + return localDateValue; + } + + public void setLocalDateValue(@Nullable LocalDate localDateValue) { + this.localDateValue = localDateValue; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesField.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesField.java new file mode 100644 index 0000000..6bbb1ef --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesField.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; + +public class AllTypesField { + + final String stringValue; + + final Boolean booleanValue; + + final boolean booleanPrimitiveValue; + + final Character characterValue; + + final char characterPrimitiveValue; + + final Byte byteValue; + + final byte bytePrimitiveValue; + + final Short shortValue; + + final short shortPrimitiveValue; + + final Integer integerValue; + + final int integerPrimitiveValue; + + final Long longValue; + + final long longPrimitiveValue; + + final Float floatValue; + + final float floatPrimitiveValue; + + final Double doubleValue; + + final double doublePrimitiveValue; + + final BigInteger bigIntegerValue; + + final BigDecimal bigDecimalValue; + + final LocalDate localDateValue; + + public AllTypesField(@ConstraintTarget(field = true) String stringValue, + @ConstraintTarget(field = true) Boolean booleanValue, + @ConstraintTarget(field = true) boolean booleanPrimitiveValue, + @ConstraintTarget(field = true) Character characterValue, + @ConstraintTarget(field = true) char characterPrimitiveValue, + @ConstraintTarget(field = true) Byte byteValue, + @ConstraintTarget(field = true) byte bytePrimitiveValue, + @ConstraintTarget(field = true) Short shortValue, + @ConstraintTarget(field = true) short shortPrimitiveValue, + @ConstraintTarget(field = true) Integer integerValue, + @ConstraintTarget(field = true) int integerPrimitiveValue, + @ConstraintTarget(field = true) Long longValue, + @ConstraintTarget(field = true) long longPrimitiveValue, + @ConstraintTarget(field = true) Float floatValue, + @ConstraintTarget(field = true) float floatPrimitiveValue, + @ConstraintTarget(field = true) Double doubleValue, + @ConstraintTarget(field = true) double doublePrimitiveValue, + @ConstraintTarget(field = true) BigInteger bigIntegerValue, + @ConstraintTarget(field = true) BigDecimal bigDecimalValue, + @ConstraintTarget(field = true) @Nullable LocalDate localDateValue) { + this.stringValue = stringValue; + this.booleanValue = booleanValue; + this.booleanPrimitiveValue = booleanPrimitiveValue; + this.characterValue = characterValue; + this.characterPrimitiveValue = characterPrimitiveValue; + this.byteValue = byteValue; + this.bytePrimitiveValue = bytePrimitiveValue; + this.shortValue = shortValue; + this.shortPrimitiveValue = shortPrimitiveValue; + this.integerValue = integerValue; + this.integerPrimitiveValue = integerPrimitiveValue; + this.longValue = longValue; + this.longPrimitiveValue = longPrimitiveValue; + this.floatValue = floatValue; + this.floatPrimitiveValue = floatPrimitiveValue; + this.doubleValue = doubleValue; + this.doublePrimitiveValue = doublePrimitiveValue; + this.bigIntegerValue = bigIntegerValue; + this.bigDecimalValue = bigDecimalValue; + this.localDateValue = localDateValue; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesImmutable.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesImmutable.java new file mode 100644 index 0000000..94b0e5f --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/AllTypesImmutable.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import org.xbib.datastructures.validation.jsr305.Nullable; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; + +public class AllTypesImmutable { + + private final String stringValue; + + private final Boolean booleanValue; + + private final boolean booleanPrimitiveValue; + + private final Character characterValue; + + private final char characterPrimitiveValue; + + private final Byte byteValue; + + private final byte bytePrimitiveValue; + + private final Short shortValue; + + private final short shortPrimitiveValue; + + private final Integer integerValue; + + private final int integerPrimitiveValue; + + private final Long longValue; + + private final long longPrimitiveValue; + + private final Float floatValue; + + private final float floatPrimitiveValue; + + private final Double doubleValue; + + private final double doublePrimitiveValue; + + private final BigInteger bigIntegerValue; + + private final BigDecimal bigDecimalValue; + + private final LocalDate localDateValue; + + public AllTypesImmutable(String stringValue, Boolean booleanValue, + boolean booleanPrimitiveValue, Character characterValue, + char characterPrimitiveValue, Byte byteValue, byte bytePrimitiveValue, + Short shortValue, short shortPrimitiveValue, Integer integerValue, + int integerPrimitiveValue, Long longValue, long longPrimitiveValue, + Float floatValue, float floatPrimitiveValue, Double doubleValue, + double doublePrimitiveValue, BigInteger bigIntegerValue, + BigDecimal bigDecimalValue, @Nullable LocalDate localDateValue) { + this.stringValue = stringValue; + this.booleanValue = booleanValue; + this.booleanPrimitiveValue = booleanPrimitiveValue; + this.characterValue = characterValue; + this.characterPrimitiveValue = characterPrimitiveValue; + this.byteValue = byteValue; + this.bytePrimitiveValue = bytePrimitiveValue; + this.shortValue = shortValue; + this.shortPrimitiveValue = shortPrimitiveValue; + this.integerValue = integerValue; + this.integerPrimitiveValue = integerPrimitiveValue; + this.longValue = longValue; + this.longPrimitiveValue = longPrimitiveValue; + this.floatValue = floatValue; + this.floatPrimitiveValue = floatPrimitiveValue; + this.doubleValue = doubleValue; + this.doublePrimitiveValue = doublePrimitiveValue; + this.bigIntegerValue = bigIntegerValue; + this.bigDecimalValue = bigDecimalValue; + this.localDateValue = localDateValue; + } + + @ConstraintTarget(getter = false) + public String stringValue() { + return stringValue; + } + + @ConstraintTarget(getter = false) + public Boolean booleanValue() { + return booleanValue; + } + + @ConstraintTarget(getter = false) + public boolean booleanPrimitiveValue() { + return booleanPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Character characterValue() { + return characterValue; + } + + @ConstraintTarget(getter = false) + public char characterPrimitiveValue() { + return characterPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Byte byteValue() { + return byteValue; + } + + @ConstraintTarget(getter = false) + public byte bytePrimitiveValue() { + return bytePrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Short shortValue() { + return shortValue; + } + + @ConstraintTarget(getter = false) + public short shortPrimitiveValue() { + return shortPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Integer integerValue() { + return integerValue; + } + + @ConstraintTarget(getter = false) + public int integerPrimitiveValue() { + return integerPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Long longValue() { + return longValue; + } + + @ConstraintTarget(getter = false) + public long longPrimitiveValue() { + return longPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Float floatValue() { + return floatValue; + } + + @ConstraintTarget(getter = false) + public float floatPrimitiveValue() { + return floatPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Double doubleValue() { + return doubleValue; + } + + @ConstraintTarget(getter = false) + public double doublePrimitiveValue() { + return doublePrimitiveValue; + } + + @ConstraintTarget(getter = false) + public BigInteger bigIntegerValue() { + return bigIntegerValue; + } + + @ConstraintTarget(getter = false) + public BigDecimal bigDecimalValue() { + return bigDecimalValue; + } + + @ConstraintTarget(getter = false) + public LocalDate localDateValue() { + return localDateValue; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/Person.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/Person.java new file mode 100644 index 0000000..704fe04 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/Person.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +public class Person { + + private final String firstName; + + private final String lastName; + + private final int age; + + @ConstraintArguments + public Person(String firstName, String lastName, int age) { + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/User.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/User.java new file mode 100644 index 0000000..f032038 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/User.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +public class User { + private final String email; + private final String name; + + public User(String email, String name) { + this.email = email; + this.name = name; + } + + @Override + public String toString() { + return "User{" + "email='" + email + '\'' + ", name='" + name + '\'' + '}'; + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/UserService.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/UserService.java new file mode 100644 index 0000000..812dda2 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/UserService.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +public class UserService { + @ConstraintArguments + public User createUser(String email, String name) { + return new User(email, name); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/ValidatorBuilderTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/ValidatorBuilderTest.java new file mode 100644 index 0000000..f9365cf --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/ValidatorBuilderTest.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import org.xbib.datastructures.validation.PhoneNumber; +import org.xbib.datastructures.validation.arguments.Arguments3Validator; +import org.xbib.datastructures.validation.builder.ArgumentsValidatorBuilder; +import org.xbib.datastructures.validation.builder.ValidatorBuilder; +import org.xbib.datastructures.validation.core.ConstraintContext; +import org.xbib.datastructures.validation.core.ConstraintGroup; +import org.xbib.datastructures.validation.core.ConstraintViolations; +import org.xbib.datastructures.validation.core.ConstraintViolationsException; +import org.xbib.datastructures.validation.core.Validator; +import org.xbib.datastructures.validation.core.ValueValidator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ValidatorBuilderTest { + + @Test + void allTypesBeanMeta() { + final Validator validator = ValidatorBuilder. of() + .constraint(_AllTypesBeanMeta.BIGDECIMALVALUE, + c -> c.greaterThan(BigDecimal.ZERO)) + .constraint(_AllTypesBeanMeta.BIGINTEGERVALUE, + c -> c.greaterThan(BigInteger.ZERO)) + .constraint(_AllTypesBeanMeta.BOOLEANPRIMITIVEVALUE, c -> c.isTrue()) + .constraint(_AllTypesBeanMeta.BOOLEANVALUE, c -> c.isTrue()) + .constraint(_AllTypesBeanMeta.BYTEPRIMITIVEVALUE, + c -> c.greaterThan((byte) 0)) + .constraint(_AllTypesBeanMeta.BYTEVALUE, c -> c.greaterThan((byte) 0)) + .constraint(_AllTypesBeanMeta.CHARACTERPRIMITIVEVALUE, + c -> c.greaterThan((char) 0)) + .constraint(_AllTypesBeanMeta.CHARACTERVALUE, + c -> c.greaterThan((char) 0)) + .constraint(_AllTypesBeanMeta.DOUBLEPRIMITIVEVALUE, + c -> c.greaterThan(0.0)) + .constraint(_AllTypesBeanMeta.DOUBLEVALUE, c -> c.greaterThan(0.0)) + .constraint(_AllTypesBeanMeta.FLOATPRIMITIVEVALUE, + c -> c.greaterThan(0.0f)) + .constraint(_AllTypesBeanMeta.FLOATVALUE, c -> c.greaterThan(0.0f)) + .constraint(_AllTypesBeanMeta.INTEGERPRIMITIVEVALUE, + c -> c.greaterThan(0)) + .constraint(_AllTypesBeanMeta.INTEGERVALUE, c -> c.greaterThan(0)) + .constraint(_AllTypesBeanMeta.LOCALDATEVALUE, c -> c.notNull()) + .constraint(_AllTypesBeanMeta.LONGPRIMITIVEVALUE, + c -> c.greaterThan((long) 0)) + .constraint(_AllTypesBeanMeta.LONGVALUE, c -> c.greaterThan((long) 0)) + .constraint(_AllTypesBeanMeta.SHORTPRIMITIVEVALUE, + c -> c.greaterThan((short) 0)) + .constraint(_AllTypesBeanMeta.SHORTVALUE, c -> c.greaterThan((short) 0)) + .constraint(_AllTypesBeanMeta.STRINGVALUE, c -> c.notEmpty()) + // + .build(); + + final AllTypesBean inValidTarget = new AllTypesBean(); + inValidTarget.setBigDecimalValue(BigDecimal.ZERO); + inValidTarget.setBigIntegerValue(BigInteger.ZERO); + inValidTarget.setBooleanPrimitiveValue(false); + inValidTarget.setBooleanValue(false); + inValidTarget.setBytePrimitiveValue((byte) 0); + inValidTarget.setByteValue((byte) 0); + inValidTarget.setCharacterPrimitiveValue((char) 0); + inValidTarget.setCharacterValue((char) 0); + inValidTarget.setDoublePrimitiveValue(0.0); + inValidTarget.setDoubleValue(0.0); + inValidTarget.setFloatPrimitiveValue(0.0f); + inValidTarget.setFloatValue(0.0f); + inValidTarget.setIntegerPrimitiveValue(0); + inValidTarget.setIntegerValue(0); + inValidTarget.setLocalDateValue(null); + inValidTarget.setLongPrimitiveValue(0); + inValidTarget.setLongValue((long) 0); + inValidTarget.setShortPrimitiveValue((short) 0); + inValidTarget.setShortValue((short) 0); + inValidTarget.setStringValue(""); + + final ConstraintViolations constraintViolations = validator + .validate(inValidTarget); + assertThat(constraintViolations.isValid()).isFalse(); + assertThat(constraintViolations.size()).isEqualTo(20); + assertThat(constraintViolations.get(0).message()) + .isEqualTo("\"bigDecimalValue\" must be greater than 0"); + assertThat(constraintViolations.get(1).message()) + .isEqualTo("\"bigIntegerValue\" must be greater than 0"); + assertThat(constraintViolations.get(2).message()) + .isEqualTo("\"booleanPrimitiveValue\" must be true"); + assertThat(constraintViolations.get(3).message()) + .isEqualTo("\"booleanValue\" must be true"); + assertThat(constraintViolations.get(4).message()) + .isEqualTo("\"bytePrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(5).message()) + .isEqualTo("\"byteValue\" must be greater than 0"); + assertThat(constraintViolations.get(6).message()) + .isEqualTo("\"characterPrimitiveValue\" must be greater than \u0000"); + assertThat(constraintViolations.get(7).message()) + .isEqualTo("\"characterValue\" must be greater than \u0000"); + assertThat(constraintViolations.get(8).message()) + .isEqualTo("\"doublePrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(9).message()) + .isEqualTo("\"doubleValue\" must be greater than 0"); + assertThat(constraintViolations.get(10).message()) + .isEqualTo("\"floatPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(11).message()) + .isEqualTo("\"floatValue\" must be greater than 0"); + assertThat(constraintViolations.get(12).message()) + .isEqualTo("\"integerPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(13).message()) + .isEqualTo("\"integerValue\" must be greater than 0"); + assertThat(constraintViolations.get(14).message()) + .isEqualTo("\"localDateValue\" must not be null"); + assertThat(constraintViolations.get(15).message()) + .isEqualTo("\"longPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(16).message()) + .isEqualTo("\"longValue\" must be greater than 0"); + assertThat(constraintViolations.get(17).message()) + .isEqualTo("\"shortPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(18).message()) + .isEqualTo("\"shortValue\" must be greater than 0"); + assertThat(constraintViolations.get(19).message()) + .isEqualTo("\"stringValue\" must not be empty"); + } + + @Test + void allTypesImmutableMeta() { + final Validator validator = ValidatorBuilder + . of() + .constraint(_AllTypesImmutableMeta.BIGDECIMALVALUE, + c -> c.greaterThan(BigDecimal.ZERO)) + .constraint(_AllTypesImmutableMeta.BIGINTEGERVALUE, + c -> c.greaterThan(BigInteger.ZERO)) + .constraint(_AllTypesImmutableMeta.BOOLEANPRIMITIVEVALUE, c -> c.isTrue()) + .constraint(_AllTypesImmutableMeta.BOOLEANVALUE, c -> c.isTrue()) + .constraint(_AllTypesImmutableMeta.BYTEPRIMITIVEVALUE, + c -> c.greaterThan((byte) 0)) + .constraint(_AllTypesImmutableMeta.BYTEVALUE, + c -> c.greaterThan((byte) 0)) + .constraint(_AllTypesImmutableMeta.CHARACTERPRIMITIVEVALUE, + c -> c.greaterThan((char) 0)) + .constraint(_AllTypesImmutableMeta.CHARACTERVALUE, + c -> c.greaterThan((char) 0)) + .constraint(_AllTypesImmutableMeta.DOUBLEPRIMITIVEVALUE, + c -> c.greaterThan(0.0)) + .constraint(_AllTypesImmutableMeta.DOUBLEVALUE, c -> c.greaterThan(0.0)) + .constraint(_AllTypesImmutableMeta.FLOATPRIMITIVEVALUE, + c -> c.greaterThan(0.0f)) + .constraint(_AllTypesImmutableMeta.FLOATVALUE, c -> c.greaterThan(0.0f)) + .constraint(_AllTypesImmutableMeta.INTEGERPRIMITIVEVALUE, + c -> c.greaterThan(0)) + .constraint(_AllTypesImmutableMeta.INTEGERVALUE, c -> c.greaterThan(0)) + .constraint(_AllTypesImmutableMeta.LOCALDATEVALUE, c -> c.notNull()) + .constraint(_AllTypesImmutableMeta.LONGPRIMITIVEVALUE, + c -> c.greaterThan((long) 0)) + .constraint(_AllTypesImmutableMeta.LONGVALUE, + c -> c.greaterThan((long) 0)) + .constraint(_AllTypesImmutableMeta.SHORTPRIMITIVEVALUE, + c -> c.greaterThan((short) 0)) + .constraint(_AllTypesImmutableMeta.SHORTVALUE, + c -> c.greaterThan((short) 0)) + .constraint(_AllTypesImmutableMeta.STRINGVALUE, c -> c.notEmpty()) + // + .build(); + + final AllTypesImmutable inValidTarget = new AllTypesImmutable("", false, false, + (char) 0, (char) 0, (byte) 0, (byte) 0, (short) 0, (short) 0, 0, 0, + (long) 0, 0, 0.0f, 0.0f, 0.0, 0.0, BigInteger.ZERO, BigDecimal.ZERO, + null); + + final ConstraintViolations constraintViolations = validator + .validate(inValidTarget); + assertThat(constraintViolations.isValid()).isFalse(); + assertThat(constraintViolations.size()).isEqualTo(20); + assertThat(constraintViolations.get(0).message()) + .isEqualTo("\"bigDecimalValue\" must be greater than 0"); + assertThat(constraintViolations.get(1).message()) + .isEqualTo("\"bigIntegerValue\" must be greater than 0"); + assertThat(constraintViolations.get(2).message()) + .isEqualTo("\"booleanPrimitiveValue\" must be true"); + assertThat(constraintViolations.get(3).message()) + .isEqualTo("\"booleanValue\" must be true"); + assertThat(constraintViolations.get(4).message()) + .isEqualTo("\"bytePrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(5).message()) + .isEqualTo("\"byteValue\" must be greater than 0"); + assertThat(constraintViolations.get(6).message()) + .isEqualTo("\"characterPrimitiveValue\" must be greater than \u0000"); + assertThat(constraintViolations.get(7).message()) + .isEqualTo("\"characterValue\" must be greater than \u0000"); + assertThat(constraintViolations.get(8).message()) + .isEqualTo("\"doublePrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(9).message()) + .isEqualTo("\"doubleValue\" must be greater than 0"); + assertThat(constraintViolations.get(10).message()) + .isEqualTo("\"floatPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(11).message()) + .isEqualTo("\"floatValue\" must be greater than 0"); + assertThat(constraintViolations.get(12).message()) + .isEqualTo("\"integerPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(13).message()) + .isEqualTo("\"integerValue\" must be greater than 0"); + assertThat(constraintViolations.get(14).message()) + .isEqualTo("\"localDateValue\" must not be null"); + assertThat(constraintViolations.get(15).message()) + .isEqualTo("\"longPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(16).message()) + .isEqualTo("\"longValue\" must be greater than 0"); + assertThat(constraintViolations.get(17).message()) + .isEqualTo("\"shortPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(18).message()) + .isEqualTo("\"shortValue\" must be greater than 0"); + assertThat(constraintViolations.get(19).message()) + .isEqualTo("\"stringValue\" must not be empty"); + } + + @Test + void allTypesFieldMeta() { + final Validator validator = ValidatorBuilder. of() + .constraint(_AllTypesFieldMeta.BIGDECIMALVALUE, + c -> c.greaterThan(BigDecimal.ZERO)) + .constraint(_AllTypesFieldMeta.BIGINTEGERVALUE, + c -> c.greaterThan(BigInteger.ZERO)) + .constraint(_AllTypesFieldMeta.BOOLEANPRIMITIVEVALUE, c -> c.isTrue()) + .constraint(_AllTypesFieldMeta.BOOLEANVALUE, c -> c.isTrue()) + .constraint(_AllTypesFieldMeta.BYTEPRIMITIVEVALUE, + c -> c.greaterThan((byte) 0)) + .constraint(_AllTypesFieldMeta.BYTEVALUE, c -> c.greaterThan((byte) 0)) + .constraint(_AllTypesFieldMeta.CHARACTERPRIMITIVEVALUE, + c -> c.greaterThan((char) 0)) + .constraint(_AllTypesFieldMeta.CHARACTERVALUE, + c -> c.greaterThan((char) 0)) + .constraint(_AllTypesFieldMeta.DOUBLEPRIMITIVEVALUE, + c -> c.greaterThan(0.0)) + .constraint(_AllTypesFieldMeta.DOUBLEVALUE, c -> c.greaterThan(0.0)) + .constraint(_AllTypesFieldMeta.FLOATPRIMITIVEVALUE, + c -> c.greaterThan(0.0f)) + .constraint(_AllTypesFieldMeta.FLOATVALUE, c -> c.greaterThan(0.0f)) + .constraint(_AllTypesFieldMeta.INTEGERPRIMITIVEVALUE, + c -> c.greaterThan(0)) + .constraint(_AllTypesFieldMeta.INTEGERVALUE, c -> c.greaterThan(0)) + .constraint(_AllTypesFieldMeta.LOCALDATEVALUE, c -> c.notNull()) + .constraint(_AllTypesFieldMeta.LONGPRIMITIVEVALUE, + c -> c.greaterThan((long) 0)) + .constraint(_AllTypesFieldMeta.LONGVALUE, c -> c.greaterThan((long) 0)) + .constraint(_AllTypesFieldMeta.SHORTPRIMITIVEVALUE, + c -> c.greaterThan((short) 0)) + .constraint(_AllTypesFieldMeta.SHORTVALUE, c -> c.greaterThan((short) 0)) + .constraint(_AllTypesFieldMeta.STRINGVALUE, c -> c.notEmpty()) + // + .build(); + + final AllTypesField inValidTarget = new AllTypesField("", false, false, (char) 0, + (char) 0, (byte) 0, (byte) 0, (short) 0, (short) 0, 0, 0, (long) 0, 0, + 0.0f, 0.0f, 0.0, 0.0, BigInteger.ZERO, BigDecimal.ZERO, null); + + final ConstraintViolations constraintViolations = validator + .validate(inValidTarget); + assertThat(constraintViolations.isValid()).isFalse(); + assertThat(constraintViolations.size()).isEqualTo(20); + assertThat(constraintViolations.get(0).message()) + .isEqualTo("\"bigDecimalValue\" must be greater than 0"); + assertThat(constraintViolations.get(1).message()) + .isEqualTo("\"bigIntegerValue\" must be greater than 0"); + assertThat(constraintViolations.get(2).message()) + .isEqualTo("\"booleanPrimitiveValue\" must be true"); + assertThat(constraintViolations.get(3).message()) + .isEqualTo("\"booleanValue\" must be true"); + assertThat(constraintViolations.get(4).message()) + .isEqualTo("\"bytePrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(5).message()) + .isEqualTo("\"byteValue\" must be greater than 0"); + assertThat(constraintViolations.get(6).message()) + .isEqualTo("\"characterPrimitiveValue\" must be greater than \u0000"); + assertThat(constraintViolations.get(7).message()) + .isEqualTo("\"characterValue\" must be greater than \u0000"); + assertThat(constraintViolations.get(8).message()) + .isEqualTo("\"doublePrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(9).message()) + .isEqualTo("\"doubleValue\" must be greater than 0"); + assertThat(constraintViolations.get(10).message()) + .isEqualTo("\"floatPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(11).message()) + .isEqualTo("\"floatValue\" must be greater than 0"); + assertThat(constraintViolations.get(12).message()) + .isEqualTo("\"integerPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(13).message()) + .isEqualTo("\"integerValue\" must be greater than 0"); + assertThat(constraintViolations.get(14).message()) + .isEqualTo("\"localDateValue\" must not be null"); + assertThat(constraintViolations.get(15).message()) + .isEqualTo("\"longPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(16).message()) + .isEqualTo("\"longValue\" must be greater than 0"); + assertThat(constraintViolations.get(17).message()) + .isEqualTo("\"shortPrimitiveValue\" must be greater than 0"); + assertThat(constraintViolations.get(18).message()) + .isEqualTo("\"shortValue\" must be greater than 0"); + assertThat(constraintViolations.get(19).message()) + .isEqualTo("\"stringValue\" must not be empty"); + } + + @Test + void constructorArguments() { + final Arguments3Validator validator = ArgumentsValidatorBuilder + .of(Person::new) + .builder(b -> b + .constraint(_PersonArgumentsMeta.FIRSTNAME, + c -> c.greaterThanOrEqual(1).lessThanOrEqual(50)) + .constraint(_PersonArgumentsMeta.LASTNAME, + c -> c.greaterThanOrEqual(1).lessThanOrEqual(50)) + .constraint(_PersonArgumentsMeta.AGE, + c -> c.greaterThanOrEqual(20).lessThanOrEqual(99))) + .build(); + + assertThatThrownBy(() -> validator.validated("", "", 0)) + .isInstanceOfSatisfying(ConstraintViolationsException.class, e -> { + final ConstraintViolations violations = e.violations(); + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"firstName\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(1).message()).isEqualTo( + "The size of \"lastName\" must be greater than or equal to 1. The given size is 0"); + assertThat(violations.get(2).message()) + .isEqualTo("\"age\" must be greater than or equal to 20"); + }); + } + + @Test + void methodArguments() { + final UserService userService = new UserService(); + final Arguments3Validator validator = ArgumentsValidatorBuilder + .of(UserService::createUser) // + .builder(b -> b // + .constraint(_UserServiceCreateUserArgumentsMeta.USERSERVICE, + c -> c.notNull()) + .constraint(_UserServiceCreateUserArgumentsMeta.EMAIL, + c -> c.email()) + .constraint(_UserServiceCreateUserArgumentsMeta.NAME, + c -> c.notNull())) + .build(); + + assertThatThrownBy(() -> validator.validated(userService, "jd", null)) // + .isInstanceOfSatisfying(ConstraintViolationsException.class, + e -> assertThat(e.getMessage()).isEqualTo( + "Constraint violations found!" + System.lineSeparator() + + "* \"email\" must be a valid email address" + + System.lineSeparator() + + "* \"name\" must not be null")); + } + + @Test + void nested() { + final Validator
validator = ValidatorBuilder.
of() // + .nest(_AddressMeta.COUNTRY, b -> b // + .constraint(_Address_CountryMeta.NAME, c -> c // + .greaterThanOrEqual(2) // + .lessThanOrEqual(16))) + .constraint(_AddressMeta.STREET, c -> c // + .greaterThanOrEqual(2) // + .lessThanOrEqual(100)) + .nest(_AddressMeta.PHONENUMBER, b -> b // + .constraint(_Address_PhoneNumberMeta.VALUE, c -> c // + .greaterThanOrEqual(8) // + .lessThanOrEqual(16))) + .build(); + + final ConstraintViolations violations = validator.validate( + new Address(new Address.Country(""), "", new Address.PhoneNumber(""))); + + assertThat(violations.isValid()).isFalse(); + assertThat(violations.size()).isEqualTo(3); + assertThat(violations.get(0).message()).isEqualTo( + "The size of \"country.name\" must be greater than or equal to 2. The given size is 0"); + assertThat(violations.get(1).message()).isEqualTo( + "The size of \"street\" must be greater than or equal to 2. The given size is 0"); + assertThat(violations.get(2).message()).isEqualTo( + "The size of \"phoneNumber.value\" must be greater than or equal to 8. The given size is 0"); + } + + @Test + void conditionalValueValidators() { + ConstraintGroup group = ConstraintGroup.of("JP"); + ValueValidator passThrough = ValueValidator.passThrough(); + ValueValidator validator = ValidatorBuilder. of() + .constraintOnCondition( + (String address, + ConstraintContext c) -> !"JP".equalsIgnoreCase(c.name()), + passThrough) + .constraintOnGroup(group, + PhoneNumber.validator().applicative().compose(PhoneNumber::new)) + .build().applicative(); + assertThat(validator.validate("1234", group).isValid()).isFalse(); + assertThat(validator.validate("1234").isValid()).isTrue(); + } +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AddressMeta.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AddressMeta.java new file mode 100644 index 0000000..4eb5dfa --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AddressMeta.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +public class _AddressMeta { + + public static final ObjectConstraintMeta COUNTRY = new ObjectConstraintMeta() { + + @Override + public String name() { + return "country"; + } + + @Override + public java.util.function.Function toValue() { + return Address::country; + } + }; + + public static final StringConstraintMeta
STREET = new StringConstraintMeta
() { + + @Override + public String name() { + return "street"; + } + + @Override + public java.util.function.Function toValue() { + return Address::street; + } + }; + + public static final ObjectConstraintMeta PHONENUMBER = new ObjectConstraintMeta() { + + @Override + public String name() { + return "phoneNumber"; + } + + @Override + public java.util.function.Function toValue() { + return Address::phoneNumber; + } + }; +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_Address_CountryMeta.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_Address_CountryMeta.java new file mode 100644 index 0000000..116a9df --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_Address_CountryMeta.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +public class _Address_CountryMeta { + + public static final StringConstraintMeta NAME = new StringConstraintMeta() { + + @Override + public String name() { + return "name"; + } + + @Override + public java.util.function.Function toValue() { + return Address.Country::name; + } + }; +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_Address_PhoneNumberMeta.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_Address_PhoneNumberMeta.java new file mode 100644 index 0000000..13a2ce6 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_Address_PhoneNumberMeta.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +public class _Address_PhoneNumberMeta { + + public static final StringConstraintMeta VALUE = new StringConstraintMeta() { + + @Override + public String name() { + return "value"; + } + + @Override + public java.util.function.Function toValue() { + return Address.PhoneNumber::value; + } + }; +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesBeanMeta.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesBeanMeta.java new file mode 100644 index 0000000..5757933 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesBeanMeta.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +// Generated at 2020-02-10T01:04:18.257747+09:00 +public class _AllTypesBeanMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta STRINGVALUE = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "stringValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getStringValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getBooleanValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::isBooleanPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getCharacterValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getCharacterPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "byteValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getByteValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "bytePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getBytePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getShortValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getShortPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getIntegerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getIntegerPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getLongValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getLongPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getFloatValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getFloatPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doubleValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getDoubleValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doublePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getDoublePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta BIGINTEGERVALUE = new org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta() { + + @Override + public String name() { + return "bigIntegerValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getBigIntegerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta BIGDECIMALVALUE = new org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta() { + + @Override + public String name() { + return "bigDecimalValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getBigDecimalValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ObjectConstraintMeta LOCALDATEVALUE = new org.xbib.datastructures.validation.meta.ObjectConstraintMeta() { + + @Override + public String name() { + return "localDateValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesBean::getLocalDateValue; + } + }; +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesFieldMeta.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesFieldMeta.java new file mode 100644 index 0000000..c2c69e6 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesFieldMeta.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +// Generated at 2020-02-10T10:08:30.794554+09:00 +public class _AllTypesFieldMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta STRINGVALUE = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "stringValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.stringValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.booleanValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.booleanPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.characterValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.characterPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "byteValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.byteValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "bytePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.bytePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.shortValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.shortPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.integerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.integerPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.longValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.longPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.floatValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.floatPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doubleValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.doubleValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doublePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.doublePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta BIGINTEGERVALUE = new org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta() { + + @Override + public String name() { + return "bigIntegerValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.bigIntegerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta BIGDECIMALVALUE = new org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta() { + + @Override + public String name() { + return "bigDecimalValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.bigDecimalValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ObjectConstraintMeta LOCALDATEVALUE = new org.xbib.datastructures.validation.meta.ObjectConstraintMeta() { + + @Override + public String name() { + return "localDateValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.localDateValue; + } + }; +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesImmutableMeta.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesImmutableMeta.java new file mode 100644 index 0000000..c28961c --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_AllTypesImmutableMeta.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +// Generated at 2020-02-10T00:57:32.251466+09:00 +public class _AllTypesImmutableMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta STRINGVALUE = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "stringValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::stringValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::booleanValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::booleanPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::characterValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::characterPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "byteValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::byteValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "bytePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::bytePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::shortValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::shortPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::integerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::integerPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::longValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::longPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::floatValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::floatPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doubleValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::doubleValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doublePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::doublePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta BIGINTEGERVALUE = new org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta() { + + @Override + public String name() { + return "bigIntegerValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::bigIntegerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta BIGDECIMALVALUE = new org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta() { + + @Override + public String name() { + return "bigDecimalValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::bigDecimalValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ObjectConstraintMeta LOCALDATEVALUE = new org.xbib.datastructures.validation.meta.ObjectConstraintMeta() { + + @Override + public String name() { + return "localDateValue"; + } + + @Override + public java.util.function.Function toValue() { + return AllTypesImmutable::localDateValue; + } + }; +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_PersonArgumentsMeta.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_PersonArgumentsMeta.java new file mode 100644 index 0000000..f552f79 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_PersonArgumentsMeta.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +// Generated at 2020-02-10T11:56:30.484098+09:00 +public class _PersonArgumentsMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta> FIRSTNAME = new org.xbib.datastructures.validation.meta.StringConstraintMeta>() { + + @Override + public String name() { + return "firstName"; + } + + @Override + public java.util.function.Function, String> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments1::arg1; + } + }; + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta> LASTNAME = new org.xbib.datastructures.validation.meta.StringConstraintMeta>() { + + @Override + public String name() { + return "lastName"; + } + + @Override + public java.util.function.Function, String> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments2::arg2; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta> AGE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta>() { + + @Override + public String name() { + return "age"; + } + + @Override + public java.util.function.Function, Integer> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments3::arg3; + } + }; +} diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_UserServiceCreateUserArgumentsMeta.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_UserServiceCreateUserArgumentsMeta.java new file mode 100644 index 0000000..1248723 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/meta/_UserServiceCreateUserArgumentsMeta.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.meta; + +public class _UserServiceCreateUserArgumentsMeta { + + public static final ObjectConstraintMeta, UserService> USERSERVICE = new ObjectConstraintMeta, UserService>() { + + @Override + public String name() { + return "userService"; + } + + @Override + public java.util.function.Function, UserService> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments1::arg1; + } + }; + + public static final StringConstraintMeta> EMAIL = new StringConstraintMeta>() { + + @Override + public String name() { + return "email"; + } + + @Override + public java.util.function.Function, String> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments2::arg2; + } + }; + + public static final StringConstraintMeta> NAME = new StringConstraintMeta>() { + + @Override + public String name() { + return "name"; + } + + @Override + public java.util.function.Function, String> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments3::arg3; + } + }; +} \ No newline at end of file diff --git a/datastructures-validation/src/test/java/org/xbib/datastructures/validation/processor/ConstraintMetaProcessorTest.java b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/processor/ConstraintMetaProcessorTest.java new file mode 100644 index 0000000..691d6a0 --- /dev/null +++ b/datastructures-validation/src/test/java/org/xbib/datastructures/validation/processor/ConstraintMetaProcessorTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 org.xbib.datastructures.validation.processor; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.google.testing.compile.JavaFileObjects; + +import static com.google.testing.compile.JavaSourcesSubject.assertThat; + +class ConstraintMetaProcessorTest { + + @Test + void processBean() { + assertThat(JavaFileObjects.forResource("test/CarBean.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and() + .generatesSources(JavaFileObjects.forResource("test/_CarBeanMeta.java")); + } + + @Test + void processImmutable() { + assertThat(JavaFileObjects.forResource("test/Car.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and() + .generatesSources(JavaFileObjects.forResource("test/_CarMeta.java")); + } + + @Test + void processFiled() { + assertThat(JavaFileObjects.forResource("test/CarField.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and() + .generatesSources(JavaFileObjects.forResource("test/_CarFieldMeta.java")); + } + + @Test + void allTypesBean() { + assertThat(JavaFileObjects.forResource("test/AllTypesBean.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and().generatesSources( + JavaFileObjects.forResource("test/_AllTypesBeanMeta.java")); + } + + @Test + void allTypesImmutable() { + assertThat(JavaFileObjects.forResource("test/AllTypesImmutable.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and().generatesSources( + JavaFileObjects.forResource("test/_AllTypesImmutableMeta.java")); + } + + @Test + void allTypesField() { + assertThat(JavaFileObjects.forResource("test/AllTypesField.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and().generatesSources( + JavaFileObjects.forResource("test/_AllTypesFieldMeta.java")); + } + + @Test + void processConstructorArguments() { + assertThat(JavaFileObjects.forResource("test/Car2.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and().generatesSources( + JavaFileObjects.forResource("test/_Car2ArgumentsMeta.java")); + } + + @Test + void processMethodArguments() { + assertThat(JavaFileObjects.forResource("test/UserService.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and().generatesSources(JavaFileObjects + .forResource("test/_UserServiceCreateUserArgumentsMeta.java")); + } + + @Test + void processInnerClass() { + assertThat(JavaFileObjects.forResource("test/Address.java")) + .processedWith(new ConstraintMetaProcessor()) // + .compilesWithoutError().and() + .generatesSources(JavaFileObjects.forResource("test/_AddressMeta.java"), + JavaFileObjects.forResource("test/_Address_CountryMeta.java"), + JavaFileObjects + .forResource("test/_Address_PhoneNumberMeta.java")); + } + + @Test + void testBeanLowerCamel() { + Assertions.assertThat(ConstraintMetaProcessor.beanLowerCamel("Name")) + .isEqualTo("name"); + Assertions.assertThat(ConstraintMetaProcessor.beanLowerCamel("NAme")) + .isEqualTo("NAme"); + Assertions.assertThat(ConstraintMetaProcessor.beanLowerCamel("NAME")) + .isEqualTo("NAME"); + } + + @Test + void testBeanUpperCamel() { + Assertions.assertThat(ConstraintMetaProcessor.beanUpperCamel("name")) + .isEqualTo("Name"); + Assertions.assertThat(ConstraintMetaProcessor.beanUpperCamel("NAme")) + .isEqualTo("NAme"); + Assertions.assertThat(ConstraintMetaProcessor.beanUpperCamel("NAME")) + .isEqualTo("NAME"); + } + +} \ No newline at end of file diff --git a/datastructures-validation/src/test/resources/emoji-test-11.txt b/datastructures-validation/src/test/resources/emoji-test-11.txt new file mode 100644 index 0000000..ed3541d --- /dev/null +++ b/datastructures-validation/src/test/resources/emoji-test-11.txt @@ -0,0 +1,3793 @@ +# emoji-test.txt +# Date: 2018-02-07, 09:44:06 GMT +# © 2018 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see http://www.unicode.org/terms_of_use.html +# +# Emoji Keyboard/Display Test Data for UTS #51 +# Version: 11.0 +# +# For documentation and usage, see http://www.unicode.org/reports/tr51 +# +# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. +# Format +# Code points; status # emoji name +# Status +# fully-qualified — see “Emoji Implementation Notes” in UTS #51 +# non-fully-qualified — see “Emoji Implementation Notes” in UTS #51 +# Notes: +# • This currently omits the 12 keycap bases, the 5 modifier characters, and 26 singleton Regional Indicator characters +# • The file is in CLDR order, not codepoint order. This is recommended (but not required!) for keyboard palettes. +# • The groups and subgroups are purely illustrative. See the Emoji Order chart for more information. + +# group: Smileys & People + +# subgroup: face-positive +1F600 ; fully-qualified # 😀 grinning face +1F601 ; fully-qualified # 😁 beaming face with smiling eyes +1F602 ; fully-qualified # 😂 face with tears of joy +1F923 ; fully-qualified # 🤣 rolling on the floor laughing +1F603 ; fully-qualified # 😃 grinning face with big eyes +1F604 ; fully-qualified # 😄 grinning face with smiling eyes +1F605 ; fully-qualified # 😅 grinning face with sweat +1F606 ; fully-qualified # 😆 grinning squinting face +1F609 ; fully-qualified # 😉 winking face +1F60A ; fully-qualified # 😊 smiling face with smiling eyes +1F60B ; fully-qualified # 😋 face savoring food +1F60E ; fully-qualified # 😎 smiling face with sunglasses +1F60D ; fully-qualified # 😍 smiling face with heart-eyes +1F618 ; fully-qualified # 😘 face blowing a kiss +1F970 ; fully-qualified # 🥰 smiling face with 3 hearts +1F617 ; fully-qualified # 😗 kissing face +1F619 ; fully-qualified # 😙 kissing face with smiling eyes +1F61A ; fully-qualified # 😚 kissing face with closed eyes +263A FE0F ; fully-qualified # ☺️ smiling face +263A ; non-fully-qualified # ☺ smiling face +1F642 ; fully-qualified # 🙂 slightly smiling face +1F917 ; fully-qualified # 🤗 hugging face +1F929 ; fully-qualified # 🤩 star-struck + +# subgroup: face-neutral +1F914 ; fully-qualified # 🤔 thinking face +1F928 ; fully-qualified # 🤨 face with raised eyebrow +1F610 ; fully-qualified # 😐 neutral face +1F611 ; fully-qualified # 😑 expressionless face +1F636 ; fully-qualified # 😶 face without mouth +1F644 ; fully-qualified # 🙄 face with rolling eyes +1F60F ; fully-qualified # 😏 smirking face +1F623 ; fully-qualified # 😣 persevering face +1F625 ; fully-qualified # 😥 sad but relieved face +1F62E ; fully-qualified # 😮 face with open mouth +1F910 ; fully-qualified # 🤐 zipper-mouth face +1F62F ; fully-qualified # 😯 hushed face +1F62A ; fully-qualified # 😪 sleepy face +1F62B ; fully-qualified # 😫 tired face +1F634 ; fully-qualified # 😴 sleeping face +1F60C ; fully-qualified # 😌 relieved face +1F61B ; fully-qualified # 😛 face with tongue +1F61C ; fully-qualified # 😜 winking face with tongue +1F61D ; fully-qualified # 😝 squinting face with tongue +1F924 ; fully-qualified # 🤤 drooling face +1F612 ; fully-qualified # 😒 unamused face +1F613 ; fully-qualified # 😓 downcast face with sweat +1F614 ; fully-qualified # 😔 pensive face +1F615 ; fully-qualified # 😕 confused face +1F643 ; fully-qualified # 🙃 upside-down face +1F911 ; fully-qualified # 🤑 money-mouth face +1F632 ; fully-qualified # 😲 astonished face + +# subgroup: face-negative +2639 FE0F ; fully-qualified # ☹️ frowning face +2639 ; non-fully-qualified # ☹ frowning face +1F641 ; fully-qualified # 🙁 slightly frowning face +1F616 ; fully-qualified # 😖 confounded face +1F61E ; fully-qualified # 😞 disappointed face +1F61F ; fully-qualified # 😟 worried face +1F624 ; fully-qualified # 😤 face with steam from nose +1F622 ; fully-qualified # 😢 crying face +1F62D ; fully-qualified # 😭 loudly crying face +1F626 ; fully-qualified # 😦 frowning face with open mouth +1F627 ; fully-qualified # 😧 anguished face +1F628 ; fully-qualified # 😨 fearful face +1F629 ; fully-qualified # 😩 weary face +1F92F ; fully-qualified # 🤯 exploding head +1F62C ; fully-qualified # 😬 grimacing face +1F630 ; fully-qualified # 😰 anxious face with sweat +1F631 ; fully-qualified # 😱 face screaming in fear +1F975 ; fully-qualified # 🥵 hot face +1F976 ; fully-qualified # 🥶 cold face +1F633 ; fully-qualified # 😳 flushed face +1F92A ; fully-qualified # 🤪 zany face +1F635 ; fully-qualified # 😵 dizzy face +1F621 ; fully-qualified # 😡 pouting face +1F620 ; fully-qualified # 😠 angry face +1F92C ; fully-qualified # 🤬 face with symbols on mouth + +# subgroup: face-sick +1F637 ; fully-qualified # 😷 face with medical mask +1F912 ; fully-qualified # 🤒 face with thermometer +1F915 ; fully-qualified # 🤕 face with head-bandage +1F922 ; fully-qualified # 🤢 nauseated face +1F92E ; fully-qualified # 🤮 face vomiting +1F927 ; fully-qualified # 🤧 sneezing face + +# subgroup: face-role +1F607 ; fully-qualified # 😇 smiling face with halo +1F920 ; fully-qualified # 🤠 cowboy hat face +1F973 ; fully-qualified # 🥳 partying face +1F974 ; fully-qualified # 🥴 woozy face +1F97A ; fully-qualified # 🥺 pleading face +1F925 ; fully-qualified # 🤥 lying face +1F92B ; fully-qualified # 🤫 shushing face +1F92D ; fully-qualified # 🤭 face with hand over mouth +1F9D0 ; fully-qualified # 🧐 face with monocle +1F913 ; fully-qualified # 🤓 nerd face + +# subgroup: face-fantasy +1F608 ; fully-qualified # 😈 smiling face with horns +1F47F ; fully-qualified # 👿 angry face with horns +1F921 ; fully-qualified # 🤡 clown face +1F479 ; fully-qualified # 👹 ogre +1F47A ; fully-qualified # 👺 goblin +1F480 ; fully-qualified # 💀 skull +2620 FE0F ; fully-qualified # ☠️ skull and crossbones +2620 ; non-fully-qualified # ☠ skull and crossbones +1F47B ; fully-qualified # 👻 ghost +1F47D ; fully-qualified # 👽 alien +1F47E ; fully-qualified # 👾 alien monster +1F916 ; fully-qualified # 🤖 robot face +1F4A9 ; fully-qualified # 💩 pile of poo + +# subgroup: cat-face +1F63A ; fully-qualified # 😺 grinning cat face +1F638 ; fully-qualified # 😸 grinning cat face with smiling eyes +1F639 ; fully-qualified # 😹 cat face with tears of joy +1F63B ; fully-qualified # 😻 smiling cat face with heart-eyes +1F63C ; fully-qualified # 😼 cat face with wry smile +1F63D ; fully-qualified # 😽 kissing cat face +1F640 ; fully-qualified # 🙀 weary cat face +1F63F ; fully-qualified # 😿 crying cat face +1F63E ; fully-qualified # 😾 pouting cat face + +# subgroup: monkey-face +1F648 ; fully-qualified # 🙈 see-no-evil monkey +1F649 ; fully-qualified # 🙉 hear-no-evil monkey +1F64A ; fully-qualified # 🙊 speak-no-evil monkey + +# subgroup: skin-tone +1F3FB ; fully-qualified # 🏻 light skin tone +1F3FC ; fully-qualified # 🏼 medium-light skin tone +1F3FD ; fully-qualified # 🏽 medium skin tone +1F3FE ; fully-qualified # 🏾 medium-dark skin tone +1F3FF ; fully-qualified # 🏿 dark skin tone + +# subgroup: person +1F476 ; fully-qualified # 👶 baby +1F476 1F3FB ; fully-qualified # 👶🏻 baby: light skin tone +1F476 1F3FC ; fully-qualified # 👶🏼 baby: medium-light skin tone +1F476 1F3FD ; fully-qualified # 👶🏽 baby: medium skin tone +1F476 1F3FE ; fully-qualified # 👶🏾 baby: medium-dark skin tone +1F476 1F3FF ; fully-qualified # 👶🏿 baby: dark skin tone +1F9D2 ; fully-qualified # 🧒 child +1F9D2 1F3FB ; fully-qualified # 🧒🏻 child: light skin tone +1F9D2 1F3FC ; fully-qualified # 🧒🏼 child: medium-light skin tone +1F9D2 1F3FD ; fully-qualified # 🧒🏽 child: medium skin tone +1F9D2 1F3FE ; fully-qualified # 🧒🏾 child: medium-dark skin tone +1F9D2 1F3FF ; fully-qualified # 🧒🏿 child: dark skin tone +1F466 ; fully-qualified # 👦 boy +1F466 1F3FB ; fully-qualified # 👦🏻 boy: light skin tone +1F466 1F3FC ; fully-qualified # 👦🏼 boy: medium-light skin tone +1F466 1F3FD ; fully-qualified # 👦🏽 boy: medium skin tone +1F466 1F3FE ; fully-qualified # 👦🏾 boy: medium-dark skin tone +1F466 1F3FF ; fully-qualified # 👦🏿 boy: dark skin tone +1F467 ; fully-qualified # 👧 girl +1F467 1F3FB ; fully-qualified # 👧🏻 girl: light skin tone +1F467 1F3FC ; fully-qualified # 👧🏼 girl: medium-light skin tone +1F467 1F3FD ; fully-qualified # 👧🏽 girl: medium skin tone +1F467 1F3FE ; fully-qualified # 👧🏾 girl: medium-dark skin tone +1F467 1F3FF ; fully-qualified # 👧🏿 girl: dark skin tone +1F9D1 ; fully-qualified # 🧑 adult +1F9D1 1F3FB ; fully-qualified # 🧑🏻 adult: light skin tone +1F9D1 1F3FC ; fully-qualified # 🧑🏼 adult: medium-light skin tone +1F9D1 1F3FD ; fully-qualified # 🧑🏽 adult: medium skin tone +1F9D1 1F3FE ; fully-qualified # 🧑🏾 adult: medium-dark skin tone +1F9D1 1F3FF ; fully-qualified # 🧑🏿 adult: dark skin tone +1F468 ; fully-qualified # 👨 man +1F468 1F3FB ; fully-qualified # 👨🏻 man: light skin tone +1F468 1F3FC ; fully-qualified # 👨🏼 man: medium-light skin tone +1F468 1F3FD ; fully-qualified # 👨🏽 man: medium skin tone +1F468 1F3FE ; fully-qualified # 👨🏾 man: medium-dark skin tone +1F468 1F3FF ; fully-qualified # 👨🏿 man: dark skin tone +1F469 ; fully-qualified # 👩 woman +1F469 1F3FB ; fully-qualified # 👩🏻 woman: light skin tone +1F469 1F3FC ; fully-qualified # 👩🏼 woman: medium-light skin tone +1F469 1F3FD ; fully-qualified # 👩🏽 woman: medium skin tone +1F469 1F3FE ; fully-qualified # 👩🏾 woman: medium-dark skin tone +1F469 1F3FF ; fully-qualified # 👩🏿 woman: dark skin tone +1F9D3 ; fully-qualified # 🧓 older adult +1F9D3 1F3FB ; fully-qualified # 🧓🏻 older adult: light skin tone +1F9D3 1F3FC ; fully-qualified # 🧓🏼 older adult: medium-light skin tone +1F9D3 1F3FD ; fully-qualified # 🧓🏽 older adult: medium skin tone +1F9D3 1F3FE ; fully-qualified # 🧓🏾 older adult: medium-dark skin tone +1F9D3 1F3FF ; fully-qualified # 🧓🏿 older adult: dark skin tone +1F474 ; fully-qualified # 👴 old man +1F474 1F3FB ; fully-qualified # 👴🏻 old man: light skin tone +1F474 1F3FC ; fully-qualified # 👴🏼 old man: medium-light skin tone +1F474 1F3FD ; fully-qualified # 👴🏽 old man: medium skin tone +1F474 1F3FE ; fully-qualified # 👴🏾 old man: medium-dark skin tone +1F474 1F3FF ; fully-qualified # 👴🏿 old man: dark skin tone +1F475 ; fully-qualified # 👵 old woman +1F475 1F3FB ; fully-qualified # 👵🏻 old woman: light skin tone +1F475 1F3FC ; fully-qualified # 👵🏼 old woman: medium-light skin tone +1F475 1F3FD ; fully-qualified # 👵🏽 old woman: medium skin tone +1F475 1F3FE ; fully-qualified # 👵🏾 old woman: medium-dark skin tone +1F475 1F3FF ; fully-qualified # 👵🏿 old woman: dark skin tone + +# subgroup: person-role +1F468 200D 2695 FE0F ; fully-qualified # 👨‍⚕️ man health worker +1F468 200D 2695 ; non-fully-qualified # 👨‍⚕ man health worker +1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻‍⚕️ man health worker: light skin tone +1F468 1F3FB 200D 2695 ; non-fully-qualified # 👨🏻‍⚕ man health worker: light skin tone +1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼‍⚕️ man health worker: medium-light skin tone +1F468 1F3FC 200D 2695 ; non-fully-qualified # 👨🏼‍⚕ man health worker: medium-light skin tone +1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽‍⚕️ man health worker: medium skin tone +1F468 1F3FD 200D 2695 ; non-fully-qualified # 👨🏽‍⚕ man health worker: medium skin tone +1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾‍⚕️ man health worker: medium-dark skin tone +1F468 1F3FE 200D 2695 ; non-fully-qualified # 👨🏾‍⚕ man health worker: medium-dark skin tone +1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿‍⚕️ man health worker: dark skin tone +1F468 1F3FF 200D 2695 ; non-fully-qualified # 👨🏿‍⚕ man health worker: dark skin tone +1F469 200D 2695 FE0F ; fully-qualified # 👩‍⚕️ woman health worker +1F469 200D 2695 ; non-fully-qualified # 👩‍⚕ woman health worker +1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻‍⚕️ woman health worker: light skin tone +1F469 1F3FB 200D 2695 ; non-fully-qualified # 👩🏻‍⚕ woman health worker: light skin tone +1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼‍⚕️ woman health worker: medium-light skin tone +1F469 1F3FC 200D 2695 ; non-fully-qualified # 👩🏼‍⚕ woman health worker: medium-light skin tone +1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽‍⚕️ woman health worker: medium skin tone +1F469 1F3FD 200D 2695 ; non-fully-qualified # 👩🏽‍⚕ woman health worker: medium skin tone +1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾‍⚕️ woman health worker: medium-dark skin tone +1F469 1F3FE 200D 2695 ; non-fully-qualified # 👩🏾‍⚕ woman health worker: medium-dark skin tone +1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿‍⚕️ woman health worker: dark skin tone +1F469 1F3FF 200D 2695 ; non-fully-qualified # 👩🏿‍⚕ woman health worker: dark skin tone +1F468 200D 1F393 ; fully-qualified # 👨‍🎓 man student +1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻‍🎓 man student: light skin tone +1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼‍🎓 man student: medium-light skin tone +1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽‍🎓 man student: medium skin tone +1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾‍🎓 man student: medium-dark skin tone +1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿‍🎓 man student: dark skin tone +1F469 200D 1F393 ; fully-qualified # 👩‍🎓 woman student +1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻‍🎓 woman student: light skin tone +1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼‍🎓 woman student: medium-light skin tone +1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽‍🎓 woman student: medium skin tone +1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾‍🎓 woman student: medium-dark skin tone +1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿‍🎓 woman student: dark skin tone +1F468 200D 1F3EB ; fully-qualified # 👨‍🏫 man teacher +1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻‍🏫 man teacher: light skin tone +1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼‍🏫 man teacher: medium-light skin tone +1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽‍🏫 man teacher: medium skin tone +1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾‍🏫 man teacher: medium-dark skin tone +1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿‍🏫 man teacher: dark skin tone +1F469 200D 1F3EB ; fully-qualified # 👩‍🏫 woman teacher +1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻‍🏫 woman teacher: light skin tone +1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼‍🏫 woman teacher: medium-light skin tone +1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽‍🏫 woman teacher: medium skin tone +1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾‍🏫 woman teacher: medium-dark skin tone +1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿‍🏫 woman teacher: dark skin tone +1F468 200D 2696 FE0F ; fully-qualified # 👨‍⚖️ man judge +1F468 200D 2696 ; non-fully-qualified # 👨‍⚖ man judge +1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻‍⚖️ man judge: light skin tone +1F468 1F3FB 200D 2696 ; non-fully-qualified # 👨🏻‍⚖ man judge: light skin tone +1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼‍⚖️ man judge: medium-light skin tone +1F468 1F3FC 200D 2696 ; non-fully-qualified # 👨🏼‍⚖ man judge: medium-light skin tone +1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽‍⚖️ man judge: medium skin tone +1F468 1F3FD 200D 2696 ; non-fully-qualified # 👨🏽‍⚖ man judge: medium skin tone +1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾‍⚖️ man judge: medium-dark skin tone +1F468 1F3FE 200D 2696 ; non-fully-qualified # 👨🏾‍⚖ man judge: medium-dark skin tone +1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿‍⚖️ man judge: dark skin tone +1F468 1F3FF 200D 2696 ; non-fully-qualified # 👨🏿‍⚖ man judge: dark skin tone +1F469 200D 2696 FE0F ; fully-qualified # 👩‍⚖️ woman judge +1F469 200D 2696 ; non-fully-qualified # 👩‍⚖ woman judge +1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻‍⚖️ woman judge: light skin tone +1F469 1F3FB 200D 2696 ; non-fully-qualified # 👩🏻‍⚖ woman judge: light skin tone +1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼‍⚖️ woman judge: medium-light skin tone +1F469 1F3FC 200D 2696 ; non-fully-qualified # 👩🏼‍⚖ woman judge: medium-light skin tone +1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽‍⚖️ woman judge: medium skin tone +1F469 1F3FD 200D 2696 ; non-fully-qualified # 👩🏽‍⚖ woman judge: medium skin tone +1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾‍⚖️ woman judge: medium-dark skin tone +1F469 1F3FE 200D 2696 ; non-fully-qualified # 👩🏾‍⚖ woman judge: medium-dark skin tone +1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿‍⚖️ woman judge: dark skin tone +1F469 1F3FF 200D 2696 ; non-fully-qualified # 👩🏿‍⚖ woman judge: dark skin tone +1F468 200D 1F33E ; fully-qualified # 👨‍🌾 man farmer +1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻‍🌾 man farmer: light skin tone +1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼‍🌾 man farmer: medium-light skin tone +1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽‍🌾 man farmer: medium skin tone +1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾‍🌾 man farmer: medium-dark skin tone +1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿‍🌾 man farmer: dark skin tone +1F469 200D 1F33E ; fully-qualified # 👩‍🌾 woman farmer +1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻‍🌾 woman farmer: light skin tone +1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼‍🌾 woman farmer: medium-light skin tone +1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽‍🌾 woman farmer: medium skin tone +1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾‍🌾 woman farmer: medium-dark skin tone +1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿‍🌾 woman farmer: dark skin tone +1F468 200D 1F373 ; fully-qualified # 👨‍🍳 man cook +1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻‍🍳 man cook: light skin tone +1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼‍🍳 man cook: medium-light skin tone +1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽‍🍳 man cook: medium skin tone +1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾‍🍳 man cook: medium-dark skin tone +1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿‍🍳 man cook: dark skin tone +1F469 200D 1F373 ; fully-qualified # 👩‍🍳 woman cook +1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻‍🍳 woman cook: light skin tone +1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼‍🍳 woman cook: medium-light skin tone +1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽‍🍳 woman cook: medium skin tone +1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾‍🍳 woman cook: medium-dark skin tone +1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿‍🍳 woman cook: dark skin tone +1F468 200D 1F527 ; fully-qualified # 👨‍🔧 man mechanic +1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻‍🔧 man mechanic: light skin tone +1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼‍🔧 man mechanic: medium-light skin tone +1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽‍🔧 man mechanic: medium skin tone +1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾‍🔧 man mechanic: medium-dark skin tone +1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿‍🔧 man mechanic: dark skin tone +1F469 200D 1F527 ; fully-qualified # 👩‍🔧 woman mechanic +1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻‍🔧 woman mechanic: light skin tone +1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼‍🔧 woman mechanic: medium-light skin tone +1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽‍🔧 woman mechanic: medium skin tone +1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾‍🔧 woman mechanic: medium-dark skin tone +1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿‍🔧 woman mechanic: dark skin tone +1F468 200D 1F3ED ; fully-qualified # 👨‍🏭 man factory worker +1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻‍🏭 man factory worker: light skin tone +1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼‍🏭 man factory worker: medium-light skin tone +1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽‍🏭 man factory worker: medium skin tone +1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾‍🏭 man factory worker: medium-dark skin tone +1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿‍🏭 man factory worker: dark skin tone +1F469 200D 1F3ED ; fully-qualified # 👩‍🏭 woman factory worker +1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻‍🏭 woman factory worker: light skin tone +1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼‍🏭 woman factory worker: medium-light skin tone +1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽‍🏭 woman factory worker: medium skin tone +1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾‍🏭 woman factory worker: medium-dark skin tone +1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿‍🏭 woman factory worker: dark skin tone +1F468 200D 1F4BC ; fully-qualified # 👨‍💼 man office worker +1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻‍💼 man office worker: light skin tone +1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼‍💼 man office worker: medium-light skin tone +1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽‍💼 man office worker: medium skin tone +1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾‍💼 man office worker: medium-dark skin tone +1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿‍💼 man office worker: dark skin tone +1F469 200D 1F4BC ; fully-qualified # 👩‍💼 woman office worker +1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻‍💼 woman office worker: light skin tone +1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼‍💼 woman office worker: medium-light skin tone +1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽‍💼 woman office worker: medium skin tone +1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾‍💼 woman office worker: medium-dark skin tone +1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿‍💼 woman office worker: dark skin tone +1F468 200D 1F52C ; fully-qualified # 👨‍🔬 man scientist +1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻‍🔬 man scientist: light skin tone +1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼‍🔬 man scientist: medium-light skin tone +1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽‍🔬 man scientist: medium skin tone +1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾‍🔬 man scientist: medium-dark skin tone +1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿‍🔬 man scientist: dark skin tone +1F469 200D 1F52C ; fully-qualified # 👩‍🔬 woman scientist +1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻‍🔬 woman scientist: light skin tone +1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼‍🔬 woman scientist: medium-light skin tone +1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽‍🔬 woman scientist: medium skin tone +1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾‍🔬 woman scientist: medium-dark skin tone +1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿‍🔬 woman scientist: dark skin tone +1F468 200D 1F4BB ; fully-qualified # 👨‍💻 man technologist +1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻‍💻 man technologist: light skin tone +1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼‍💻 man technologist: medium-light skin tone +1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽‍💻 man technologist: medium skin tone +1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾‍💻 man technologist: medium-dark skin tone +1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿‍💻 man technologist: dark skin tone +1F469 200D 1F4BB ; fully-qualified # 👩‍💻 woman technologist +1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻‍💻 woman technologist: light skin tone +1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼‍💻 woman technologist: medium-light skin tone +1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽‍💻 woman technologist: medium skin tone +1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾‍💻 woman technologist: medium-dark skin tone +1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿‍💻 woman technologist: dark skin tone +1F468 200D 1F3A4 ; fully-qualified # 👨‍🎤 man singer +1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻‍🎤 man singer: light skin tone +1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼‍🎤 man singer: medium-light skin tone +1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽‍🎤 man singer: medium skin tone +1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾‍🎤 man singer: medium-dark skin tone +1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿‍🎤 man singer: dark skin tone +1F469 200D 1F3A4 ; fully-qualified # 👩‍🎤 woman singer +1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻‍🎤 woman singer: light skin tone +1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼‍🎤 woman singer: medium-light skin tone +1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽‍🎤 woman singer: medium skin tone +1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾‍🎤 woman singer: medium-dark skin tone +1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿‍🎤 woman singer: dark skin tone +1F468 200D 1F3A8 ; fully-qualified # 👨‍🎨 man artist +1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻‍🎨 man artist: light skin tone +1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼‍🎨 man artist: medium-light skin tone +1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽‍🎨 man artist: medium skin tone +1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾‍🎨 man artist: medium-dark skin tone +1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿‍🎨 man artist: dark skin tone +1F469 200D 1F3A8 ; fully-qualified # 👩‍🎨 woman artist +1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻‍🎨 woman artist: light skin tone +1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼‍🎨 woman artist: medium-light skin tone +1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽‍🎨 woman artist: medium skin tone +1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾‍🎨 woman artist: medium-dark skin tone +1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿‍🎨 woman artist: dark skin tone +1F468 200D 2708 FE0F ; fully-qualified # 👨‍✈️ man pilot +1F468 200D 2708 ; non-fully-qualified # 👨‍✈ man pilot +1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻‍✈️ man pilot: light skin tone +1F468 1F3FB 200D 2708 ; non-fully-qualified # 👨🏻‍✈ man pilot: light skin tone +1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼‍✈️ man pilot: medium-light skin tone +1F468 1F3FC 200D 2708 ; non-fully-qualified # 👨🏼‍✈ man pilot: medium-light skin tone +1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽‍✈️ man pilot: medium skin tone +1F468 1F3FD 200D 2708 ; non-fully-qualified # 👨🏽‍✈ man pilot: medium skin tone +1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾‍✈️ man pilot: medium-dark skin tone +1F468 1F3FE 200D 2708 ; non-fully-qualified # 👨🏾‍✈ man pilot: medium-dark skin tone +1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿‍✈️ man pilot: dark skin tone +1F468 1F3FF 200D 2708 ; non-fully-qualified # 👨🏿‍✈ man pilot: dark skin tone +1F469 200D 2708 FE0F ; fully-qualified # 👩‍✈️ woman pilot +1F469 200D 2708 ; non-fully-qualified # 👩‍✈ woman pilot +1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻‍✈️ woman pilot: light skin tone +1F469 1F3FB 200D 2708 ; non-fully-qualified # 👩🏻‍✈ woman pilot: light skin tone +1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼‍✈️ woman pilot: medium-light skin tone +1F469 1F3FC 200D 2708 ; non-fully-qualified # 👩🏼‍✈ woman pilot: medium-light skin tone +1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽‍✈️ woman pilot: medium skin tone +1F469 1F3FD 200D 2708 ; non-fully-qualified # 👩🏽‍✈ woman pilot: medium skin tone +1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾‍✈️ woman pilot: medium-dark skin tone +1F469 1F3FE 200D 2708 ; non-fully-qualified # 👩🏾‍✈ woman pilot: medium-dark skin tone +1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿‍✈️ woman pilot: dark skin tone +1F469 1F3FF 200D 2708 ; non-fully-qualified # 👩🏿‍✈ woman pilot: dark skin tone +1F468 200D 1F680 ; fully-qualified # 👨‍🚀 man astronaut +1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻‍🚀 man astronaut: light skin tone +1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼‍🚀 man astronaut: medium-light skin tone +1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽‍🚀 man astronaut: medium skin tone +1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾‍🚀 man astronaut: medium-dark skin tone +1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿‍🚀 man astronaut: dark skin tone +1F469 200D 1F680 ; fully-qualified # 👩‍🚀 woman astronaut +1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻‍🚀 woman astronaut: light skin tone +1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼‍🚀 woman astronaut: medium-light skin tone +1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽‍🚀 woman astronaut: medium skin tone +1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾‍🚀 woman astronaut: medium-dark skin tone +1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿‍🚀 woman astronaut: dark skin tone +1F468 200D 1F692 ; fully-qualified # 👨‍🚒 man firefighter +1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻‍🚒 man firefighter: light skin tone +1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼‍🚒 man firefighter: medium-light skin tone +1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽‍🚒 man firefighter: medium skin tone +1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾‍🚒 man firefighter: medium-dark skin tone +1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿‍🚒 man firefighter: dark skin tone +1F469 200D 1F692 ; fully-qualified # 👩‍🚒 woman firefighter +1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻‍🚒 woman firefighter: light skin tone +1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼‍🚒 woman firefighter: medium-light skin tone +1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽‍🚒 woman firefighter: medium skin tone +1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾‍🚒 woman firefighter: medium-dark skin tone +1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿‍🚒 woman firefighter: dark skin tone +1F46E ; fully-qualified # 👮 police officer +1F46E 1F3FB ; fully-qualified # 👮🏻 police officer: light skin tone +1F46E 1F3FC ; fully-qualified # 👮🏼 police officer: medium-light skin tone +1F46E 1F3FD ; fully-qualified # 👮🏽 police officer: medium skin tone +1F46E 1F3FE ; fully-qualified # 👮🏾 police officer: medium-dark skin tone +1F46E 1F3FF ; fully-qualified # 👮🏿 police officer: dark skin tone +1F46E 200D 2642 FE0F ; fully-qualified # 👮‍♂️ man police officer +1F46E 200D 2642 ; non-fully-qualified # 👮‍♂ man police officer +1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻‍♂️ man police officer: light skin tone +1F46E 1F3FB 200D 2642 ; non-fully-qualified # 👮🏻‍♂ man police officer: light skin tone +1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼‍♂️ man police officer: medium-light skin tone +1F46E 1F3FC 200D 2642 ; non-fully-qualified # 👮🏼‍♂ man police officer: medium-light skin tone +1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽‍♂️ man police officer: medium skin tone +1F46E 1F3FD 200D 2642 ; non-fully-qualified # 👮🏽‍♂ man police officer: medium skin tone +1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾‍♂️ man police officer: medium-dark skin tone +1F46E 1F3FE 200D 2642 ; non-fully-qualified # 👮🏾‍♂ man police officer: medium-dark skin tone +1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿‍♂️ man police officer: dark skin tone +1F46E 1F3FF 200D 2642 ; non-fully-qualified # 👮🏿‍♂ man police officer: dark skin tone +1F46E 200D 2640 FE0F ; fully-qualified # 👮‍♀️ woman police officer +1F46E 200D 2640 ; non-fully-qualified # 👮‍♀ woman police officer +1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻‍♀️ woman police officer: light skin tone +1F46E 1F3FB 200D 2640 ; non-fully-qualified # 👮🏻‍♀ woman police officer: light skin tone +1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼‍♀️ woman police officer: medium-light skin tone +1F46E 1F3FC 200D 2640 ; non-fully-qualified # 👮🏼‍♀ woman police officer: medium-light skin tone +1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽‍♀️ woman police officer: medium skin tone +1F46E 1F3FD 200D 2640 ; non-fully-qualified # 👮🏽‍♀ woman police officer: medium skin tone +1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾‍♀️ woman police officer: medium-dark skin tone +1F46E 1F3FE 200D 2640 ; non-fully-qualified # 👮🏾‍♀ woman police officer: medium-dark skin tone +1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿‍♀️ woman police officer: dark skin tone +1F46E 1F3FF 200D 2640 ; non-fully-qualified # 👮🏿‍♀ woman police officer: dark skin tone +1F575 FE0F ; fully-qualified # 🕵️ detective +1F575 ; non-fully-qualified # 🕵 detective +1F575 1F3FB ; fully-qualified # 🕵🏻 detective: light skin tone +1F575 1F3FC ; fully-qualified # 🕵🏼 detective: medium-light skin tone +1F575 1F3FD ; fully-qualified # 🕵🏽 detective: medium skin tone +1F575 1F3FE ; fully-qualified # 🕵🏾 detective: medium-dark skin tone +1F575 1F3FF ; fully-qualified # 🕵🏿 detective: dark skin tone +1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ man detective +1F575 200D 2642 FE0F ; non-fully-qualified # 🕵‍♂️ man detective +1F575 FE0F 200D 2642 ; non-fully-qualified # 🕵️‍♂ man detective +1F575 200D 2642 ; non-fully-qualified # 🕵‍♂ man detective +1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ man detective: light skin tone +1F575 1F3FB 200D 2642 ; non-fully-qualified # 🕵🏻‍♂ man detective: light skin tone +1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼‍♂️ man detective: medium-light skin tone +1F575 1F3FC 200D 2642 ; non-fully-qualified # 🕵🏼‍♂ man detective: medium-light skin tone +1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽‍♂️ man detective: medium skin tone +1F575 1F3FD 200D 2642 ; non-fully-qualified # 🕵🏽‍♂ man detective: medium skin tone +1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾‍♂️ man detective: medium-dark skin tone +1F575 1F3FE 200D 2642 ; non-fully-qualified # 🕵🏾‍♂ man detective: medium-dark skin tone +1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿‍♂️ man detective: dark skin tone +1F575 1F3FF 200D 2642 ; non-fully-qualified # 🕵🏿‍♂ man detective: dark skin tone +1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ woman detective +1F575 200D 2640 FE0F ; non-fully-qualified # 🕵‍♀️ woman detective +1F575 FE0F 200D 2640 ; non-fully-qualified # 🕵️‍♀ woman detective +1F575 200D 2640 ; non-fully-qualified # 🕵‍♀ woman detective +1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ woman detective: light skin tone +1F575 1F3FB 200D 2640 ; non-fully-qualified # 🕵🏻‍♀ woman detective: light skin tone +1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼‍♀️ woman detective: medium-light skin tone +1F575 1F3FC 200D 2640 ; non-fully-qualified # 🕵🏼‍♀ woman detective: medium-light skin tone +1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽‍♀️ woman detective: medium skin tone +1F575 1F3FD 200D 2640 ; non-fully-qualified # 🕵🏽‍♀ woman detective: medium skin tone +1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾‍♀️ woman detective: medium-dark skin tone +1F575 1F3FE 200D 2640 ; non-fully-qualified # 🕵🏾‍♀ woman detective: medium-dark skin tone +1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿‍♀️ woman detective: dark skin tone +1F575 1F3FF 200D 2640 ; non-fully-qualified # 🕵🏿‍♀ woman detective: dark skin tone +1F482 ; fully-qualified # 💂 guard +1F482 1F3FB ; fully-qualified # 💂🏻 guard: light skin tone +1F482 1F3FC ; fully-qualified # 💂🏼 guard: medium-light skin tone +1F482 1F3FD ; fully-qualified # 💂🏽 guard: medium skin tone +1F482 1F3FE ; fully-qualified # 💂🏾 guard: medium-dark skin tone +1F482 1F3FF ; fully-qualified # 💂🏿 guard: dark skin tone +1F482 200D 2642 FE0F ; fully-qualified # 💂‍♂️ man guard +1F482 200D 2642 ; non-fully-qualified # 💂‍♂ man guard +1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻‍♂️ man guard: light skin tone +1F482 1F3FB 200D 2642 ; non-fully-qualified # 💂🏻‍♂ man guard: light skin tone +1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼‍♂️ man guard: medium-light skin tone +1F482 1F3FC 200D 2642 ; non-fully-qualified # 💂🏼‍♂ man guard: medium-light skin tone +1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽‍♂️ man guard: medium skin tone +1F482 1F3FD 200D 2642 ; non-fully-qualified # 💂🏽‍♂ man guard: medium skin tone +1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾‍♂️ man guard: medium-dark skin tone +1F482 1F3FE 200D 2642 ; non-fully-qualified # 💂🏾‍♂ man guard: medium-dark skin tone +1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿‍♂️ man guard: dark skin tone +1F482 1F3FF 200D 2642 ; non-fully-qualified # 💂🏿‍♂ man guard: dark skin tone +1F482 200D 2640 FE0F ; fully-qualified # 💂‍♀️ woman guard +1F482 200D 2640 ; non-fully-qualified # 💂‍♀ woman guard +1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻‍♀️ woman guard: light skin tone +1F482 1F3FB 200D 2640 ; non-fully-qualified # 💂🏻‍♀ woman guard: light skin tone +1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼‍♀️ woman guard: medium-light skin tone +1F482 1F3FC 200D 2640 ; non-fully-qualified # 💂🏼‍♀ woman guard: medium-light skin tone +1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽‍♀️ woman guard: medium skin tone +1F482 1F3FD 200D 2640 ; non-fully-qualified # 💂🏽‍♀ woman guard: medium skin tone +1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾‍♀️ woman guard: medium-dark skin tone +1F482 1F3FE 200D 2640 ; non-fully-qualified # 💂🏾‍♀ woman guard: medium-dark skin tone +1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿‍♀️ woman guard: dark skin tone +1F482 1F3FF 200D 2640 ; non-fully-qualified # 💂🏿‍♀ woman guard: dark skin tone +1F477 ; fully-qualified # 👷 construction worker +1F477 1F3FB ; fully-qualified # 👷🏻 construction worker: light skin tone +1F477 1F3FC ; fully-qualified # 👷🏼 construction worker: medium-light skin tone +1F477 1F3FD ; fully-qualified # 👷🏽 construction worker: medium skin tone +1F477 1F3FE ; fully-qualified # 👷🏾 construction worker: medium-dark skin tone +1F477 1F3FF ; fully-qualified # 👷🏿 construction worker: dark skin tone +1F477 200D 2642 FE0F ; fully-qualified # 👷‍♂️ man construction worker +1F477 200D 2642 ; non-fully-qualified # 👷‍♂ man construction worker +1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻‍♂️ man construction worker: light skin tone +1F477 1F3FB 200D 2642 ; non-fully-qualified # 👷🏻‍♂ man construction worker: light skin tone +1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼‍♂️ man construction worker: medium-light skin tone +1F477 1F3FC 200D 2642 ; non-fully-qualified # 👷🏼‍♂ man construction worker: medium-light skin tone +1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽‍♂️ man construction worker: medium skin tone +1F477 1F3FD 200D 2642 ; non-fully-qualified # 👷🏽‍♂ man construction worker: medium skin tone +1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾‍♂️ man construction worker: medium-dark skin tone +1F477 1F3FE 200D 2642 ; non-fully-qualified # 👷🏾‍♂ man construction worker: medium-dark skin tone +1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿‍♂️ man construction worker: dark skin tone +1F477 1F3FF 200D 2642 ; non-fully-qualified # 👷🏿‍♂ man construction worker: dark skin tone +1F477 200D 2640 FE0F ; fully-qualified # 👷‍♀️ woman construction worker +1F477 200D 2640 ; non-fully-qualified # 👷‍♀ woman construction worker +1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻‍♀️ woman construction worker: light skin tone +1F477 1F3FB 200D 2640 ; non-fully-qualified # 👷🏻‍♀ woman construction worker: light skin tone +1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼‍♀️ woman construction worker: medium-light skin tone +1F477 1F3FC 200D 2640 ; non-fully-qualified # 👷🏼‍♀ woman construction worker: medium-light skin tone +1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽‍♀️ woman construction worker: medium skin tone +1F477 1F3FD 200D 2640 ; non-fully-qualified # 👷🏽‍♀ woman construction worker: medium skin tone +1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾‍♀️ woman construction worker: medium-dark skin tone +1F477 1F3FE 200D 2640 ; non-fully-qualified # 👷🏾‍♀ woman construction worker: medium-dark skin tone +1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ woman construction worker: dark skin tone +1F477 1F3FF 200D 2640 ; non-fully-qualified # 👷🏿‍♀ woman construction worker: dark skin tone +1F934 ; fully-qualified # 🤴 prince +1F934 1F3FB ; fully-qualified # 🤴🏻 prince: light skin tone +1F934 1F3FC ; fully-qualified # 🤴🏼 prince: medium-light skin tone +1F934 1F3FD ; fully-qualified # 🤴🏽 prince: medium skin tone +1F934 1F3FE ; fully-qualified # 🤴🏾 prince: medium-dark skin tone +1F934 1F3FF ; fully-qualified # 🤴🏿 prince: dark skin tone +1F478 ; fully-qualified # 👸 princess +1F478 1F3FB ; fully-qualified # 👸🏻 princess: light skin tone +1F478 1F3FC ; fully-qualified # 👸🏼 princess: medium-light skin tone +1F478 1F3FD ; fully-qualified # 👸🏽 princess: medium skin tone +1F478 1F3FE ; fully-qualified # 👸🏾 princess: medium-dark skin tone +1F478 1F3FF ; fully-qualified # 👸🏿 princess: dark skin tone +1F473 ; fully-qualified # 👳 person wearing turban +1F473 1F3FB ; fully-qualified # 👳🏻 person wearing turban: light skin tone +1F473 1F3FC ; fully-qualified # 👳🏼 person wearing turban: medium-light skin tone +1F473 1F3FD ; fully-qualified # 👳🏽 person wearing turban: medium skin tone +1F473 1F3FE ; fully-qualified # 👳🏾 person wearing turban: medium-dark skin tone +1F473 1F3FF ; fully-qualified # 👳🏿 person wearing turban: dark skin tone +1F473 200D 2642 FE0F ; fully-qualified # 👳‍♂️ man wearing turban +1F473 200D 2642 ; non-fully-qualified # 👳‍♂ man wearing turban +1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻‍♂️ man wearing turban: light skin tone +1F473 1F3FB 200D 2642 ; non-fully-qualified # 👳🏻‍♂ man wearing turban: light skin tone +1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼‍♂️ man wearing turban: medium-light skin tone +1F473 1F3FC 200D 2642 ; non-fully-qualified # 👳🏼‍♂ man wearing turban: medium-light skin tone +1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽‍♂️ man wearing turban: medium skin tone +1F473 1F3FD 200D 2642 ; non-fully-qualified # 👳🏽‍♂ man wearing turban: medium skin tone +1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾‍♂️ man wearing turban: medium-dark skin tone +1F473 1F3FE 200D 2642 ; non-fully-qualified # 👳🏾‍♂ man wearing turban: medium-dark skin tone +1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿‍♂️ man wearing turban: dark skin tone +1F473 1F3FF 200D 2642 ; non-fully-qualified # 👳🏿‍♂ man wearing turban: dark skin tone +1F473 200D 2640 FE0F ; fully-qualified # 👳‍♀️ woman wearing turban +1F473 200D 2640 ; non-fully-qualified # 👳‍♀ woman wearing turban +1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻‍♀️ woman wearing turban: light skin tone +1F473 1F3FB 200D 2640 ; non-fully-qualified # 👳🏻‍♀ woman wearing turban: light skin tone +1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼‍♀️ woman wearing turban: medium-light skin tone +1F473 1F3FC 200D 2640 ; non-fully-qualified # 👳🏼‍♀ woman wearing turban: medium-light skin tone +1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽‍♀️ woman wearing turban: medium skin tone +1F473 1F3FD 200D 2640 ; non-fully-qualified # 👳🏽‍♀ woman wearing turban: medium skin tone +1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾‍♀️ woman wearing turban: medium-dark skin tone +1F473 1F3FE 200D 2640 ; non-fully-qualified # 👳🏾‍♀ woman wearing turban: medium-dark skin tone +1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿‍♀️ woman wearing turban: dark skin tone +1F473 1F3FF 200D 2640 ; non-fully-qualified # 👳🏿‍♀ woman wearing turban: dark skin tone +1F472 ; fully-qualified # 👲 man with Chinese cap +1F472 1F3FB ; fully-qualified # 👲🏻 man with Chinese cap: light skin tone +1F472 1F3FC ; fully-qualified # 👲🏼 man with Chinese cap: medium-light skin tone +1F472 1F3FD ; fully-qualified # 👲🏽 man with Chinese cap: medium skin tone +1F472 1F3FE ; fully-qualified # 👲🏾 man with Chinese cap: medium-dark skin tone +1F472 1F3FF ; fully-qualified # 👲🏿 man with Chinese cap: dark skin tone +1F9D5 ; fully-qualified # 🧕 woman with headscarf +1F9D5 1F3FB ; fully-qualified # 🧕🏻 woman with headscarf: light skin tone +1F9D5 1F3FC ; fully-qualified # 🧕🏼 woman with headscarf: medium-light skin tone +1F9D5 1F3FD ; fully-qualified # 🧕🏽 woman with headscarf: medium skin tone +1F9D5 1F3FE ; fully-qualified # 🧕🏾 woman with headscarf: medium-dark skin tone +1F9D5 1F3FF ; fully-qualified # 🧕🏿 woman with headscarf: dark skin tone +1F9D4 ; fully-qualified # 🧔 bearded person +1F9D4 1F3FB ; fully-qualified # 🧔🏻 bearded person: light skin tone +1F9D4 1F3FC ; fully-qualified # 🧔🏼 bearded person: medium-light skin tone +1F9D4 1F3FD ; fully-qualified # 🧔🏽 bearded person: medium skin tone +1F9D4 1F3FE ; fully-qualified # 🧔🏾 bearded person: medium-dark skin tone +1F9D4 1F3FF ; fully-qualified # 🧔🏿 bearded person: dark skin tone +1F471 ; fully-qualified # 👱 blond-haired person +1F471 1F3FB ; fully-qualified # 👱🏻 blond-haired person: light skin tone +1F471 1F3FC ; fully-qualified # 👱🏼 blond-haired person: medium-light skin tone +1F471 1F3FD ; fully-qualified # 👱🏽 blond-haired person: medium skin tone +1F471 1F3FE ; fully-qualified # 👱🏾 blond-haired person: medium-dark skin tone +1F471 1F3FF ; fully-qualified # 👱🏿 blond-haired person: dark skin tone +1F471 200D 2642 FE0F ; fully-qualified # 👱‍♂️ blond-haired man +1F471 200D 2642 ; non-fully-qualified # 👱‍♂ blond-haired man +1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻‍♂️ blond-haired man: light skin tone +1F471 1F3FB 200D 2642 ; non-fully-qualified # 👱🏻‍♂ blond-haired man: light skin tone +1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼‍♂️ blond-haired man: medium-light skin tone +1F471 1F3FC 200D 2642 ; non-fully-qualified # 👱🏼‍♂ blond-haired man: medium-light skin tone +1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽‍♂️ blond-haired man: medium skin tone +1F471 1F3FD 200D 2642 ; non-fully-qualified # 👱🏽‍♂ blond-haired man: medium skin tone +1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾‍♂️ blond-haired man: medium-dark skin tone +1F471 1F3FE 200D 2642 ; non-fully-qualified # 👱🏾‍♂ blond-haired man: medium-dark skin tone +1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿‍♂️ blond-haired man: dark skin tone +1F471 1F3FF 200D 2642 ; non-fully-qualified # 👱🏿‍♂ blond-haired man: dark skin tone +1F471 200D 2640 FE0F ; fully-qualified # 👱‍♀️ blond-haired woman +1F471 200D 2640 ; non-fully-qualified # 👱‍♀ blond-haired woman +1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻‍♀️ blond-haired woman: light skin tone +1F471 1F3FB 200D 2640 ; non-fully-qualified # 👱🏻‍♀ blond-haired woman: light skin tone +1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼‍♀️ blond-haired woman: medium-light skin tone +1F471 1F3FC 200D 2640 ; non-fully-qualified # 👱🏼‍♀ blond-haired woman: medium-light skin tone +1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽‍♀️ blond-haired woman: medium skin tone +1F471 1F3FD 200D 2640 ; non-fully-qualified # 👱🏽‍♀ blond-haired woman: medium skin tone +1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾‍♀️ blond-haired woman: medium-dark skin tone +1F471 1F3FE 200D 2640 ; non-fully-qualified # 👱🏾‍♀ blond-haired woman: medium-dark skin tone +1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿‍♀️ blond-haired woman: dark skin tone +1F471 1F3FF 200D 2640 ; non-fully-qualified # 👱🏿‍♀ blond-haired woman: dark skin tone +1F468 200D 1F9B0 ; fully-qualified # 👨‍🦰 man, red haired +1F468 1F3FB 200D 1F9B0 ; fully-qualified # 👨🏻‍🦰 man, red haired: light skin tone +1F468 1F3FC 200D 1F9B0 ; fully-qualified # 👨🏼‍🦰 man, red haired: medium-light skin tone +1F468 1F3FD 200D 1F9B0 ; fully-qualified # 👨🏽‍🦰 man, red haired: medium skin tone +1F468 1F3FE 200D 1F9B0 ; fully-qualified # 👨🏾‍🦰 man, red haired: medium-dark skin tone +1F468 1F3FF 200D 1F9B0 ; fully-qualified # 👨🏿‍🦰 man, red haired: dark skin tone +1F469 200D 1F9B0 ; fully-qualified # 👩‍🦰 woman, red haired +1F469 1F3FB 200D 1F9B0 ; fully-qualified # 👩🏻‍🦰 woman, red haired: light skin tone +1F469 1F3FC 200D 1F9B0 ; fully-qualified # 👩🏼‍🦰 woman, red haired: medium-light skin tone +1F469 1F3FD 200D 1F9B0 ; fully-qualified # 👩🏽‍🦰 woman, red haired: medium skin tone +1F469 1F3FE 200D 1F9B0 ; fully-qualified # 👩🏾‍🦰 woman, red haired: medium-dark skin tone +1F469 1F3FF 200D 1F9B0 ; fully-qualified # 👩🏿‍🦰 woman, red haired: dark skin tone +1F468 200D 1F9B1 ; fully-qualified # 👨‍🦱 man, curly haired +1F468 1F3FB 200D 1F9B1 ; fully-qualified # 👨🏻‍🦱 man, curly haired: light skin tone +1F468 1F3FC 200D 1F9B1 ; fully-qualified # 👨🏼‍🦱 man, curly haired: medium-light skin tone +1F468 1F3FD 200D 1F9B1 ; fully-qualified # 👨🏽‍🦱 man, curly haired: medium skin tone +1F468 1F3FE 200D 1F9B1 ; fully-qualified # 👨🏾‍🦱 man, curly haired: medium-dark skin tone +1F468 1F3FF 200D 1F9B1 ; fully-qualified # 👨🏿‍🦱 man, curly haired: dark skin tone +1F469 200D 1F9B1 ; fully-qualified # 👩‍🦱 woman, curly haired +1F469 1F3FB 200D 1F9B1 ; fully-qualified # 👩🏻‍🦱 woman, curly haired: light skin tone +1F469 1F3FC 200D 1F9B1 ; fully-qualified # 👩🏼‍🦱 woman, curly haired: medium-light skin tone +1F469 1F3FD 200D 1F9B1 ; fully-qualified # 👩🏽‍🦱 woman, curly haired: medium skin tone +1F469 1F3FE 200D 1F9B1 ; fully-qualified # 👩🏾‍🦱 woman, curly haired: medium-dark skin tone +1F469 1F3FF 200D 1F9B1 ; fully-qualified # 👩🏿‍🦱 woman, curly haired: dark skin tone +1F468 200D 1F9B2 ; fully-qualified # 👨‍🦲 man, bald +1F468 1F3FB 200D 1F9B2 ; fully-qualified # 👨🏻‍🦲 man, bald: light skin tone +1F468 1F3FC 200D 1F9B2 ; fully-qualified # 👨🏼‍🦲 man, bald: medium-light skin tone +1F468 1F3FD 200D 1F9B2 ; fully-qualified # 👨🏽‍🦲 man, bald: medium skin tone +1F468 1F3FE 200D 1F9B2 ; fully-qualified # 👨🏾‍🦲 man, bald: medium-dark skin tone +1F468 1F3FF 200D 1F9B2 ; fully-qualified # 👨🏿‍🦲 man, bald: dark skin tone +1F469 200D 1F9B2 ; fully-qualified # 👩‍🦲 woman, bald +1F469 1F3FB 200D 1F9B2 ; fully-qualified # 👩🏻‍🦲 woman, bald: light skin tone +1F469 1F3FC 200D 1F9B2 ; fully-qualified # 👩🏼‍🦲 woman, bald: medium-light skin tone +1F469 1F3FD 200D 1F9B2 ; fully-qualified # 👩🏽‍🦲 woman, bald: medium skin tone +1F469 1F3FE 200D 1F9B2 ; fully-qualified # 👩🏾‍🦲 woman, bald: medium-dark skin tone +1F469 1F3FF 200D 1F9B2 ; fully-qualified # 👩🏿‍🦲 woman, bald: dark skin tone +1F468 200D 1F9B3 ; fully-qualified # 👨‍🦳 man, white haired +1F468 1F3FB 200D 1F9B3 ; fully-qualified # 👨🏻‍🦳 man, white haired: light skin tone +1F468 1F3FC 200D 1F9B3 ; fully-qualified # 👨🏼‍🦳 man, white haired: medium-light skin tone +1F468 1F3FD 200D 1F9B3 ; fully-qualified # 👨🏽‍🦳 man, white haired: medium skin tone +1F468 1F3FE 200D 1F9B3 ; fully-qualified # 👨🏾‍🦳 man, white haired: medium-dark skin tone +1F468 1F3FF 200D 1F9B3 ; fully-qualified # 👨🏿‍🦳 man, white haired: dark skin tone +1F469 200D 1F9B3 ; fully-qualified # 👩‍🦳 woman, white haired +1F469 1F3FB 200D 1F9B3 ; fully-qualified # 👩🏻‍🦳 woman, white haired: light skin tone +1F469 1F3FC 200D 1F9B3 ; fully-qualified # 👩🏼‍🦳 woman, white haired: medium-light skin tone +1F469 1F3FD 200D 1F9B3 ; fully-qualified # 👩🏽‍🦳 woman, white haired: medium skin tone +1F469 1F3FE 200D 1F9B3 ; fully-qualified # 👩🏾‍🦳 woman, white haired: medium-dark skin tone +1F469 1F3FF 200D 1F9B3 ; fully-qualified # 👩🏿‍🦳 woman, white haired: dark skin tone +1F935 ; fully-qualified # 🤵 man in tuxedo +1F935 1F3FB ; fully-qualified # 🤵🏻 man in tuxedo: light skin tone +1F935 1F3FC ; fully-qualified # 🤵🏼 man in tuxedo: medium-light skin tone +1F935 1F3FD ; fully-qualified # 🤵🏽 man in tuxedo: medium skin tone +1F935 1F3FE ; fully-qualified # 🤵🏾 man in tuxedo: medium-dark skin tone +1F935 1F3FF ; fully-qualified # 🤵🏿 man in tuxedo: dark skin tone +1F470 ; fully-qualified # 👰 bride with veil +1F470 1F3FB ; fully-qualified # 👰🏻 bride with veil: light skin tone +1F470 1F3FC ; fully-qualified # 👰🏼 bride with veil: medium-light skin tone +1F470 1F3FD ; fully-qualified # 👰🏽 bride with veil: medium skin tone +1F470 1F3FE ; fully-qualified # 👰🏾 bride with veil: medium-dark skin tone +1F470 1F3FF ; fully-qualified # 👰🏿 bride with veil: dark skin tone +1F930 ; fully-qualified # 🤰 pregnant woman +1F930 1F3FB ; fully-qualified # 🤰🏻 pregnant woman: light skin tone +1F930 1F3FC ; fully-qualified # 🤰🏼 pregnant woman: medium-light skin tone +1F930 1F3FD ; fully-qualified # 🤰🏽 pregnant woman: medium skin tone +1F930 1F3FE ; fully-qualified # 🤰🏾 pregnant woman: medium-dark skin tone +1F930 1F3FF ; fully-qualified # 🤰🏿 pregnant woman: dark skin tone +1F931 ; fully-qualified # 🤱 breast-feeding +1F931 1F3FB ; fully-qualified # 🤱🏻 breast-feeding: light skin tone +1F931 1F3FC ; fully-qualified # 🤱🏼 breast-feeding: medium-light skin tone +1F931 1F3FD ; fully-qualified # 🤱🏽 breast-feeding: medium skin tone +1F931 1F3FE ; fully-qualified # 🤱🏾 breast-feeding: medium-dark skin tone +1F931 1F3FF ; fully-qualified # 🤱🏿 breast-feeding: dark skin tone + +# subgroup: person-fantasy +1F47C ; fully-qualified # 👼 baby angel +1F47C 1F3FB ; fully-qualified # 👼🏻 baby angel: light skin tone +1F47C 1F3FC ; fully-qualified # 👼🏼 baby angel: medium-light skin tone +1F47C 1F3FD ; fully-qualified # 👼🏽 baby angel: medium skin tone +1F47C 1F3FE ; fully-qualified # 👼🏾 baby angel: medium-dark skin tone +1F47C 1F3FF ; fully-qualified # 👼🏿 baby angel: dark skin tone +1F385 ; fully-qualified # 🎅 Santa Claus +1F385 1F3FB ; fully-qualified # 🎅🏻 Santa Claus: light skin tone +1F385 1F3FC ; fully-qualified # 🎅🏼 Santa Claus: medium-light skin tone +1F385 1F3FD ; fully-qualified # 🎅🏽 Santa Claus: medium skin tone +1F385 1F3FE ; fully-qualified # 🎅🏾 Santa Claus: medium-dark skin tone +1F385 1F3FF ; fully-qualified # 🎅🏿 Santa Claus: dark skin tone +1F936 ; fully-qualified # 🤶 Mrs. Claus +1F936 1F3FB ; fully-qualified # 🤶🏻 Mrs. Claus: light skin tone +1F936 1F3FC ; fully-qualified # 🤶🏼 Mrs. Claus: medium-light skin tone +1F936 1F3FD ; fully-qualified # 🤶🏽 Mrs. Claus: medium skin tone +1F936 1F3FE ; fully-qualified # 🤶🏾 Mrs. Claus: medium-dark skin tone +1F936 1F3FF ; fully-qualified # 🤶🏿 Mrs. Claus: dark skin tone +1F9B8 ; fully-qualified # 🦸 superhero +1F9B8 1F3FB ; fully-qualified # 🦸🏻 superhero: light skin tone +1F9B8 1F3FC ; fully-qualified # 🦸🏼 superhero: medium-light skin tone +1F9B8 1F3FD ; fully-qualified # 🦸🏽 superhero: medium skin tone +1F9B8 1F3FE ; fully-qualified # 🦸🏾 superhero: medium-dark skin tone +1F9B8 1F3FF ; fully-qualified # 🦸🏿 superhero: dark skin tone +1F9B8 200D 2640 FE0F ; fully-qualified # 🦸‍♀️ woman superhero +1F9B8 200D 2640 ; non-fully-qualified # 🦸‍♀ woman superhero +1F9B8 1F3FB 200D 2640 FE0F ; fully-qualified # 🦸🏻‍♀️ woman superhero: light skin tone +1F9B8 1F3FB 200D 2640 ; non-fully-qualified # 🦸🏻‍♀ woman superhero: light skin tone +1F9B8 1F3FC 200D 2640 FE0F ; fully-qualified # 🦸🏼‍♀️ woman superhero: medium-light skin tone +1F9B8 1F3FC 200D 2640 ; non-fully-qualified # 🦸🏼‍♀ woman superhero: medium-light skin tone +1F9B8 1F3FD 200D 2640 FE0F ; fully-qualified # 🦸🏽‍♀️ woman superhero: medium skin tone +1F9B8 1F3FD 200D 2640 ; non-fully-qualified # 🦸🏽‍♀ woman superhero: medium skin tone +1F9B8 1F3FE 200D 2640 FE0F ; fully-qualified # 🦸🏾‍♀️ woman superhero: medium-dark skin tone +1F9B8 1F3FE 200D 2640 ; non-fully-qualified # 🦸🏾‍♀ woman superhero: medium-dark skin tone +1F9B8 1F3FF 200D 2640 FE0F ; fully-qualified # 🦸🏿‍♀️ woman superhero: dark skin tone +1F9B8 1F3FF 200D 2640 ; non-fully-qualified # 🦸🏿‍♀ woman superhero: dark skin tone +1F9B8 200D 2642 FE0F ; fully-qualified # 🦸‍♂️ man superhero +1F9B8 200D 2642 ; non-fully-qualified # 🦸‍♂ man superhero +1F9B8 1F3FB 200D 2642 FE0F ; fully-qualified # 🦸🏻‍♂️ man superhero: light skin tone +1F9B8 1F3FB 200D 2642 ; non-fully-qualified # 🦸🏻‍♂ man superhero: light skin tone +1F9B8 1F3FC 200D 2642 FE0F ; fully-qualified # 🦸🏼‍♂️ man superhero: medium-light skin tone +1F9B8 1F3FC 200D 2642 ; non-fully-qualified # 🦸🏼‍♂ man superhero: medium-light skin tone +1F9B8 1F3FD 200D 2642 FE0F ; fully-qualified # 🦸🏽‍♂️ man superhero: medium skin tone +1F9B8 1F3FD 200D 2642 ; non-fully-qualified # 🦸🏽‍♂ man superhero: medium skin tone +1F9B8 1F3FE 200D 2642 FE0F ; fully-qualified # 🦸🏾‍♂️ man superhero: medium-dark skin tone +1F9B8 1F3FE 200D 2642 ; non-fully-qualified # 🦸🏾‍♂ man superhero: medium-dark skin tone +1F9B8 1F3FF 200D 2642 FE0F ; fully-qualified # 🦸🏿‍♂️ man superhero: dark skin tone +1F9B8 1F3FF 200D 2642 ; non-fully-qualified # 🦸🏿‍♂ man superhero: dark skin tone +1F9B9 ; fully-qualified # 🦹 supervillain +1F9B9 1F3FB ; fully-qualified # 🦹🏻 supervillain: light skin tone +1F9B9 1F3FC ; fully-qualified # 🦹🏼 supervillain: medium-light skin tone +1F9B9 1F3FD ; fully-qualified # 🦹🏽 supervillain: medium skin tone +1F9B9 1F3FE ; fully-qualified # 🦹🏾 supervillain: medium-dark skin tone +1F9B9 1F3FF ; fully-qualified # 🦹🏿 supervillain: dark skin tone +1F9B9 200D 2640 FE0F ; fully-qualified # 🦹‍♀️ woman supervillain +1F9B9 200D 2640 ; non-fully-qualified # 🦹‍♀ woman supervillain +1F9B9 1F3FB 200D 2640 FE0F ; fully-qualified # 🦹🏻‍♀️ woman supervillain: light skin tone +1F9B9 1F3FB 200D 2640 ; non-fully-qualified # 🦹🏻‍♀ woman supervillain: light skin tone +1F9B9 1F3FC 200D 2640 FE0F ; fully-qualified # 🦹🏼‍♀️ woman supervillain: medium-light skin tone +1F9B9 1F3FC 200D 2640 ; non-fully-qualified # 🦹🏼‍♀ woman supervillain: medium-light skin tone +1F9B9 1F3FD 200D 2640 FE0F ; fully-qualified # 🦹🏽‍♀️ woman supervillain: medium skin tone +1F9B9 1F3FD 200D 2640 ; non-fully-qualified # 🦹🏽‍♀ woman supervillain: medium skin tone +1F9B9 1F3FE 200D 2640 FE0F ; fully-qualified # 🦹🏾‍♀️ woman supervillain: medium-dark skin tone +1F9B9 1F3FE 200D 2640 ; non-fully-qualified # 🦹🏾‍♀ woman supervillain: medium-dark skin tone +1F9B9 1F3FF 200D 2640 FE0F ; fully-qualified # 🦹🏿‍♀️ woman supervillain: dark skin tone +1F9B9 1F3FF 200D 2640 ; non-fully-qualified # 🦹🏿‍♀ woman supervillain: dark skin tone +1F9B9 200D 2642 FE0F ; fully-qualified # 🦹‍♂️ man supervillain +1F9B9 200D 2642 ; non-fully-qualified # 🦹‍♂ man supervillain +1F9B9 1F3FB 200D 2642 FE0F ; fully-qualified # 🦹🏻‍♂️ man supervillain: light skin tone +1F9B9 1F3FB 200D 2642 ; non-fully-qualified # 🦹🏻‍♂ man supervillain: light skin tone +1F9B9 1F3FC 200D 2642 FE0F ; fully-qualified # 🦹🏼‍♂️ man supervillain: medium-light skin tone +1F9B9 1F3FC 200D 2642 ; non-fully-qualified # 🦹🏼‍♂ man supervillain: medium-light skin tone +1F9B9 1F3FD 200D 2642 FE0F ; fully-qualified # 🦹🏽‍♂️ man supervillain: medium skin tone +1F9B9 1F3FD 200D 2642 ; non-fully-qualified # 🦹🏽‍♂ man supervillain: medium skin tone +1F9B9 1F3FE 200D 2642 FE0F ; fully-qualified # 🦹🏾‍♂️ man supervillain: medium-dark skin tone +1F9B9 1F3FE 200D 2642 ; non-fully-qualified # 🦹🏾‍♂ man supervillain: medium-dark skin tone +1F9B9 1F3FF 200D 2642 FE0F ; fully-qualified # 🦹🏿‍♂️ man supervillain: dark skin tone +1F9B9 1F3FF 200D 2642 ; non-fully-qualified # 🦹🏿‍♂ man supervillain: dark skin tone +1F9D9 ; fully-qualified # 🧙 mage +1F9D9 1F3FB ; fully-qualified # 🧙🏻 mage: light skin tone +1F9D9 1F3FC ; fully-qualified # 🧙🏼 mage: medium-light skin tone +1F9D9 1F3FD ; fully-qualified # 🧙🏽 mage: medium skin tone +1F9D9 1F3FE ; fully-qualified # 🧙🏾 mage: medium-dark skin tone +1F9D9 1F3FF ; fully-qualified # 🧙🏿 mage: dark skin tone +1F9D9 200D 2640 FE0F ; fully-qualified # 🧙‍♀️ woman mage +1F9D9 200D 2640 ; non-fully-qualified # 🧙‍♀ woman mage +1F9D9 1F3FB 200D 2640 FE0F ; fully-qualified # 🧙🏻‍♀️ woman mage: light skin tone +1F9D9 1F3FB 200D 2640 ; non-fully-qualified # 🧙🏻‍♀ woman mage: light skin tone +1F9D9 1F3FC 200D 2640 FE0F ; fully-qualified # 🧙🏼‍♀️ woman mage: medium-light skin tone +1F9D9 1F3FC 200D 2640 ; non-fully-qualified # 🧙🏼‍♀ woman mage: medium-light skin tone +1F9D9 1F3FD 200D 2640 FE0F ; fully-qualified # 🧙🏽‍♀️ woman mage: medium skin tone +1F9D9 1F3FD 200D 2640 ; non-fully-qualified # 🧙🏽‍♀ woman mage: medium skin tone +1F9D9 1F3FE 200D 2640 FE0F ; fully-qualified # 🧙🏾‍♀️ woman mage: medium-dark skin tone +1F9D9 1F3FE 200D 2640 ; non-fully-qualified # 🧙🏾‍♀ woman mage: medium-dark skin tone +1F9D9 1F3FF 200D 2640 FE0F ; fully-qualified # 🧙🏿‍♀️ woman mage: dark skin tone +1F9D9 1F3FF 200D 2640 ; non-fully-qualified # 🧙🏿‍♀ woman mage: dark skin tone +1F9D9 200D 2642 FE0F ; fully-qualified # 🧙‍♂️ man mage +1F9D9 200D 2642 ; non-fully-qualified # 🧙‍♂ man mage +1F9D9 1F3FB 200D 2642 FE0F ; fully-qualified # 🧙🏻‍♂️ man mage: light skin tone +1F9D9 1F3FB 200D 2642 ; non-fully-qualified # 🧙🏻‍♂ man mage: light skin tone +1F9D9 1F3FC 200D 2642 FE0F ; fully-qualified # 🧙🏼‍♂️ man mage: medium-light skin tone +1F9D9 1F3FC 200D 2642 ; non-fully-qualified # 🧙🏼‍♂ man mage: medium-light skin tone +1F9D9 1F3FD 200D 2642 FE0F ; fully-qualified # 🧙🏽‍♂️ man mage: medium skin tone +1F9D9 1F3FD 200D 2642 ; non-fully-qualified # 🧙🏽‍♂ man mage: medium skin tone +1F9D9 1F3FE 200D 2642 FE0F ; fully-qualified # 🧙🏾‍♂️ man mage: medium-dark skin tone +1F9D9 1F3FE 200D 2642 ; non-fully-qualified # 🧙🏾‍♂ man mage: medium-dark skin tone +1F9D9 1F3FF 200D 2642 FE0F ; fully-qualified # 🧙🏿‍♂️ man mage: dark skin tone +1F9D9 1F3FF 200D 2642 ; non-fully-qualified # 🧙🏿‍♂ man mage: dark skin tone +1F9DA ; fully-qualified # 🧚 fairy +1F9DA 1F3FB ; fully-qualified # 🧚🏻 fairy: light skin tone +1F9DA 1F3FC ; fully-qualified # 🧚🏼 fairy: medium-light skin tone +1F9DA 1F3FD ; fully-qualified # 🧚🏽 fairy: medium skin tone +1F9DA 1F3FE ; fully-qualified # 🧚🏾 fairy: medium-dark skin tone +1F9DA 1F3FF ; fully-qualified # 🧚🏿 fairy: dark skin tone +1F9DA 200D 2640 FE0F ; fully-qualified # 🧚‍♀️ woman fairy +1F9DA 200D 2640 ; non-fully-qualified # 🧚‍♀ woman fairy +1F9DA 1F3FB 200D 2640 FE0F ; fully-qualified # 🧚🏻‍♀️ woman fairy: light skin tone +1F9DA 1F3FB 200D 2640 ; non-fully-qualified # 🧚🏻‍♀ woman fairy: light skin tone +1F9DA 1F3FC 200D 2640 FE0F ; fully-qualified # 🧚🏼‍♀️ woman fairy: medium-light skin tone +1F9DA 1F3FC 200D 2640 ; non-fully-qualified # 🧚🏼‍♀ woman fairy: medium-light skin tone +1F9DA 1F3FD 200D 2640 FE0F ; fully-qualified # 🧚🏽‍♀️ woman fairy: medium skin tone +1F9DA 1F3FD 200D 2640 ; non-fully-qualified # 🧚🏽‍♀ woman fairy: medium skin tone +1F9DA 1F3FE 200D 2640 FE0F ; fully-qualified # 🧚🏾‍♀️ woman fairy: medium-dark skin tone +1F9DA 1F3FE 200D 2640 ; non-fully-qualified # 🧚🏾‍♀ woman fairy: medium-dark skin tone +1F9DA 1F3FF 200D 2640 FE0F ; fully-qualified # 🧚🏿‍♀️ woman fairy: dark skin tone +1F9DA 1F3FF 200D 2640 ; non-fully-qualified # 🧚🏿‍♀ woman fairy: dark skin tone +1F9DA 200D 2642 FE0F ; fully-qualified # 🧚‍♂️ man fairy +1F9DA 200D 2642 ; non-fully-qualified # 🧚‍♂ man fairy +1F9DA 1F3FB 200D 2642 FE0F ; fully-qualified # 🧚🏻‍♂️ man fairy: light skin tone +1F9DA 1F3FB 200D 2642 ; non-fully-qualified # 🧚🏻‍♂ man fairy: light skin tone +1F9DA 1F3FC 200D 2642 FE0F ; fully-qualified # 🧚🏼‍♂️ man fairy: medium-light skin tone +1F9DA 1F3FC 200D 2642 ; non-fully-qualified # 🧚🏼‍♂ man fairy: medium-light skin tone +1F9DA 1F3FD 200D 2642 FE0F ; fully-qualified # 🧚🏽‍♂️ man fairy: medium skin tone +1F9DA 1F3FD 200D 2642 ; non-fully-qualified # 🧚🏽‍♂ man fairy: medium skin tone +1F9DA 1F3FE 200D 2642 FE0F ; fully-qualified # 🧚🏾‍♂️ man fairy: medium-dark skin tone +1F9DA 1F3FE 200D 2642 ; non-fully-qualified # 🧚🏾‍♂ man fairy: medium-dark skin tone +1F9DA 1F3FF 200D 2642 FE0F ; fully-qualified # 🧚🏿‍♂️ man fairy: dark skin tone +1F9DA 1F3FF 200D 2642 ; non-fully-qualified # 🧚🏿‍♂ man fairy: dark skin tone +1F9DB ; fully-qualified # 🧛 vampire +1F9DB 1F3FB ; fully-qualified # 🧛🏻 vampire: light skin tone +1F9DB 1F3FC ; fully-qualified # 🧛🏼 vampire: medium-light skin tone +1F9DB 1F3FD ; fully-qualified # 🧛🏽 vampire: medium skin tone +1F9DB 1F3FE ; fully-qualified # 🧛🏾 vampire: medium-dark skin tone +1F9DB 1F3FF ; fully-qualified # 🧛🏿 vampire: dark skin tone +1F9DB 200D 2640 FE0F ; fully-qualified # 🧛‍♀️ woman vampire +1F9DB 200D 2640 ; non-fully-qualified # 🧛‍♀ woman vampire +1F9DB 1F3FB 200D 2640 FE0F ; fully-qualified # 🧛🏻‍♀️ woman vampire: light skin tone +1F9DB 1F3FB 200D 2640 ; non-fully-qualified # 🧛🏻‍♀ woman vampire: light skin tone +1F9DB 1F3FC 200D 2640 FE0F ; fully-qualified # 🧛🏼‍♀️ woman vampire: medium-light skin tone +1F9DB 1F3FC 200D 2640 ; non-fully-qualified # 🧛🏼‍♀ woman vampire: medium-light skin tone +1F9DB 1F3FD 200D 2640 FE0F ; fully-qualified # 🧛🏽‍♀️ woman vampire: medium skin tone +1F9DB 1F3FD 200D 2640 ; non-fully-qualified # 🧛🏽‍♀ woman vampire: medium skin tone +1F9DB 1F3FE 200D 2640 FE0F ; fully-qualified # 🧛🏾‍♀️ woman vampire: medium-dark skin tone +1F9DB 1F3FE 200D 2640 ; non-fully-qualified # 🧛🏾‍♀ woman vampire: medium-dark skin tone +1F9DB 1F3FF 200D 2640 FE0F ; fully-qualified # 🧛🏿‍♀️ woman vampire: dark skin tone +1F9DB 1F3FF 200D 2640 ; non-fully-qualified # 🧛🏿‍♀ woman vampire: dark skin tone +1F9DB 200D 2642 FE0F ; fully-qualified # 🧛‍♂️ man vampire +1F9DB 200D 2642 ; non-fully-qualified # 🧛‍♂ man vampire +1F9DB 1F3FB 200D 2642 FE0F ; fully-qualified # 🧛🏻‍♂️ man vampire: light skin tone +1F9DB 1F3FB 200D 2642 ; non-fully-qualified # 🧛🏻‍♂ man vampire: light skin tone +1F9DB 1F3FC 200D 2642 FE0F ; fully-qualified # 🧛🏼‍♂️ man vampire: medium-light skin tone +1F9DB 1F3FC 200D 2642 ; non-fully-qualified # 🧛🏼‍♂ man vampire: medium-light skin tone +1F9DB 1F3FD 200D 2642 FE0F ; fully-qualified # 🧛🏽‍♂️ man vampire: medium skin tone +1F9DB 1F3FD 200D 2642 ; non-fully-qualified # 🧛🏽‍♂ man vampire: medium skin tone +1F9DB 1F3FE 200D 2642 FE0F ; fully-qualified # 🧛🏾‍♂️ man vampire: medium-dark skin tone +1F9DB 1F3FE 200D 2642 ; non-fully-qualified # 🧛🏾‍♂ man vampire: medium-dark skin tone +1F9DB 1F3FF 200D 2642 FE0F ; fully-qualified # 🧛🏿‍♂️ man vampire: dark skin tone +1F9DB 1F3FF 200D 2642 ; non-fully-qualified # 🧛🏿‍♂ man vampire: dark skin tone +1F9DC ; fully-qualified # 🧜 merperson +1F9DC 1F3FB ; fully-qualified # 🧜🏻 merperson: light skin tone +1F9DC 1F3FC ; fully-qualified # 🧜🏼 merperson: medium-light skin tone +1F9DC 1F3FD ; fully-qualified # 🧜🏽 merperson: medium skin tone +1F9DC 1F3FE ; fully-qualified # 🧜🏾 merperson: medium-dark skin tone +1F9DC 1F3FF ; fully-qualified # 🧜🏿 merperson: dark skin tone +1F9DC 200D 2640 FE0F ; fully-qualified # 🧜‍♀️ mermaid +1F9DC 200D 2640 ; non-fully-qualified # 🧜‍♀ mermaid +1F9DC 1F3FB 200D 2640 FE0F ; fully-qualified # 🧜🏻‍♀️ mermaid: light skin tone +1F9DC 1F3FB 200D 2640 ; non-fully-qualified # 🧜🏻‍♀ mermaid: light skin tone +1F9DC 1F3FC 200D 2640 FE0F ; fully-qualified # 🧜🏼‍♀️ mermaid: medium-light skin tone +1F9DC 1F3FC 200D 2640 ; non-fully-qualified # 🧜🏼‍♀ mermaid: medium-light skin tone +1F9DC 1F3FD 200D 2640 FE0F ; fully-qualified # 🧜🏽‍♀️ mermaid: medium skin tone +1F9DC 1F3FD 200D 2640 ; non-fully-qualified # 🧜🏽‍♀ mermaid: medium skin tone +1F9DC 1F3FE 200D 2640 FE0F ; fully-qualified # 🧜🏾‍♀️ mermaid: medium-dark skin tone +1F9DC 1F3FE 200D 2640 ; non-fully-qualified # 🧜🏾‍♀ mermaid: medium-dark skin tone +1F9DC 1F3FF 200D 2640 FE0F ; fully-qualified # 🧜🏿‍♀️ mermaid: dark skin tone +1F9DC 1F3FF 200D 2640 ; non-fully-qualified # 🧜🏿‍♀ mermaid: dark skin tone +1F9DC 200D 2642 FE0F ; fully-qualified # 🧜‍♂️ merman +1F9DC 200D 2642 ; non-fully-qualified # 🧜‍♂ merman +1F9DC 1F3FB 200D 2642 FE0F ; fully-qualified # 🧜🏻‍♂️ merman: light skin tone +1F9DC 1F3FB 200D 2642 ; non-fully-qualified # 🧜🏻‍♂ merman: light skin tone +1F9DC 1F3FC 200D 2642 FE0F ; fully-qualified # 🧜🏼‍♂️ merman: medium-light skin tone +1F9DC 1F3FC 200D 2642 ; non-fully-qualified # 🧜🏼‍♂ merman: medium-light skin tone +1F9DC 1F3FD 200D 2642 FE0F ; fully-qualified # 🧜🏽‍♂️ merman: medium skin tone +1F9DC 1F3FD 200D 2642 ; non-fully-qualified # 🧜🏽‍♂ merman: medium skin tone +1F9DC 1F3FE 200D 2642 FE0F ; fully-qualified # 🧜🏾‍♂️ merman: medium-dark skin tone +1F9DC 1F3FE 200D 2642 ; non-fully-qualified # 🧜🏾‍♂ merman: medium-dark skin tone +1F9DC 1F3FF 200D 2642 FE0F ; fully-qualified # 🧜🏿‍♂️ merman: dark skin tone +1F9DC 1F3FF 200D 2642 ; non-fully-qualified # 🧜🏿‍♂ merman: dark skin tone +1F9DD ; fully-qualified # 🧝 elf +1F9DD 1F3FB ; fully-qualified # 🧝🏻 elf: light skin tone +1F9DD 1F3FC ; fully-qualified # 🧝🏼 elf: medium-light skin tone +1F9DD 1F3FD ; fully-qualified # 🧝🏽 elf: medium skin tone +1F9DD 1F3FE ; fully-qualified # 🧝🏾 elf: medium-dark skin tone +1F9DD 1F3FF ; fully-qualified # 🧝🏿 elf: dark skin tone +1F9DD 200D 2640 FE0F ; fully-qualified # 🧝‍♀️ woman elf +1F9DD 200D 2640 ; non-fully-qualified # 🧝‍♀ woman elf +1F9DD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧝🏻‍♀️ woman elf: light skin tone +1F9DD 1F3FB 200D 2640 ; non-fully-qualified # 🧝🏻‍♀ woman elf: light skin tone +1F9DD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧝🏼‍♀️ woman elf: medium-light skin tone +1F9DD 1F3FC 200D 2640 ; non-fully-qualified # 🧝🏼‍♀ woman elf: medium-light skin tone +1F9DD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧝🏽‍♀️ woman elf: medium skin tone +1F9DD 1F3FD 200D 2640 ; non-fully-qualified # 🧝🏽‍♀ woman elf: medium skin tone +1F9DD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧝🏾‍♀️ woman elf: medium-dark skin tone +1F9DD 1F3FE 200D 2640 ; non-fully-qualified # 🧝🏾‍♀ woman elf: medium-dark skin tone +1F9DD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧝🏿‍♀️ woman elf: dark skin tone +1F9DD 1F3FF 200D 2640 ; non-fully-qualified # 🧝🏿‍♀ woman elf: dark skin tone +1F9DD 200D 2642 FE0F ; fully-qualified # 🧝‍♂️ man elf +1F9DD 200D 2642 ; non-fully-qualified # 🧝‍♂ man elf +1F9DD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧝🏻‍♂️ man elf: light skin tone +1F9DD 1F3FB 200D 2642 ; non-fully-qualified # 🧝🏻‍♂ man elf: light skin tone +1F9DD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧝🏼‍♂️ man elf: medium-light skin tone +1F9DD 1F3FC 200D 2642 ; non-fully-qualified # 🧝🏼‍♂ man elf: medium-light skin tone +1F9DD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧝🏽‍♂️ man elf: medium skin tone +1F9DD 1F3FD 200D 2642 ; non-fully-qualified # 🧝🏽‍♂ man elf: medium skin tone +1F9DD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧝🏾‍♂️ man elf: medium-dark skin tone +1F9DD 1F3FE 200D 2642 ; non-fully-qualified # 🧝🏾‍♂ man elf: medium-dark skin tone +1F9DD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧝🏿‍♂️ man elf: dark skin tone +1F9DD 1F3FF 200D 2642 ; non-fully-qualified # 🧝🏿‍♂ man elf: dark skin tone +1F9DE ; fully-qualified # 🧞 genie +1F9DE 200D 2640 FE0F ; fully-qualified # 🧞‍♀️ woman genie +1F9DE 200D 2640 ; non-fully-qualified # 🧞‍♀ woman genie +1F9DE 200D 2642 FE0F ; fully-qualified # 🧞‍♂️ man genie +1F9DE 200D 2642 ; non-fully-qualified # 🧞‍♂ man genie +1F9DF ; fully-qualified # 🧟 zombie +1F9DF 200D 2640 FE0F ; fully-qualified # 🧟‍♀️ woman zombie +1F9DF 200D 2640 ; non-fully-qualified # 🧟‍♀ woman zombie +1F9DF 200D 2642 FE0F ; fully-qualified # 🧟‍♂️ man zombie +1F9DF 200D 2642 ; non-fully-qualified # 🧟‍♂ man zombie + +# subgroup: person-gesture +1F64D ; fully-qualified # 🙍 person frowning +1F64D 1F3FB ; fully-qualified # 🙍🏻 person frowning: light skin tone +1F64D 1F3FC ; fully-qualified # 🙍🏼 person frowning: medium-light skin tone +1F64D 1F3FD ; fully-qualified # 🙍🏽 person frowning: medium skin tone +1F64D 1F3FE ; fully-qualified # 🙍🏾 person frowning: medium-dark skin tone +1F64D 1F3FF ; fully-qualified # 🙍🏿 person frowning: dark skin tone +1F64D 200D 2642 FE0F ; fully-qualified # 🙍‍♂️ man frowning +1F64D 200D 2642 ; non-fully-qualified # 🙍‍♂ man frowning +1F64D 1F3FB 200D 2642 FE0F ; fully-qualified # 🙍🏻‍♂️ man frowning: light skin tone +1F64D 1F3FB 200D 2642 ; non-fully-qualified # 🙍🏻‍♂ man frowning: light skin tone +1F64D 1F3FC 200D 2642 FE0F ; fully-qualified # 🙍🏼‍♂️ man frowning: medium-light skin tone +1F64D 1F3FC 200D 2642 ; non-fully-qualified # 🙍🏼‍♂ man frowning: medium-light skin tone +1F64D 1F3FD 200D 2642 FE0F ; fully-qualified # 🙍🏽‍♂️ man frowning: medium skin tone +1F64D 1F3FD 200D 2642 ; non-fully-qualified # 🙍🏽‍♂ man frowning: medium skin tone +1F64D 1F3FE 200D 2642 FE0F ; fully-qualified # 🙍🏾‍♂️ man frowning: medium-dark skin tone +1F64D 1F3FE 200D 2642 ; non-fully-qualified # 🙍🏾‍♂ man frowning: medium-dark skin tone +1F64D 1F3FF 200D 2642 FE0F ; fully-qualified # 🙍🏿‍♂️ man frowning: dark skin tone +1F64D 1F3FF 200D 2642 ; non-fully-qualified # 🙍🏿‍♂ man frowning: dark skin tone +1F64D 200D 2640 FE0F ; fully-qualified # 🙍‍♀️ woman frowning +1F64D 200D 2640 ; non-fully-qualified # 🙍‍♀ woman frowning +1F64D 1F3FB 200D 2640 FE0F ; fully-qualified # 🙍🏻‍♀️ woman frowning: light skin tone +1F64D 1F3FB 200D 2640 ; non-fully-qualified # 🙍🏻‍♀ woman frowning: light skin tone +1F64D 1F3FC 200D 2640 FE0F ; fully-qualified # 🙍🏼‍♀️ woman frowning: medium-light skin tone +1F64D 1F3FC 200D 2640 ; non-fully-qualified # 🙍🏼‍♀ woman frowning: medium-light skin tone +1F64D 1F3FD 200D 2640 FE0F ; fully-qualified # 🙍🏽‍♀️ woman frowning: medium skin tone +1F64D 1F3FD 200D 2640 ; non-fully-qualified # 🙍🏽‍♀ woman frowning: medium skin tone +1F64D 1F3FE 200D 2640 FE0F ; fully-qualified # 🙍🏾‍♀️ woman frowning: medium-dark skin tone +1F64D 1F3FE 200D 2640 ; non-fully-qualified # 🙍🏾‍♀ woman frowning: medium-dark skin tone +1F64D 1F3FF 200D 2640 FE0F ; fully-qualified # 🙍🏿‍♀️ woman frowning: dark skin tone +1F64D 1F3FF 200D 2640 ; non-fully-qualified # 🙍🏿‍♀ woman frowning: dark skin tone +1F64E ; fully-qualified # 🙎 person pouting +1F64E 1F3FB ; fully-qualified # 🙎🏻 person pouting: light skin tone +1F64E 1F3FC ; fully-qualified # 🙎🏼 person pouting: medium-light skin tone +1F64E 1F3FD ; fully-qualified # 🙎🏽 person pouting: medium skin tone +1F64E 1F3FE ; fully-qualified # 🙎🏾 person pouting: medium-dark skin tone +1F64E 1F3FF ; fully-qualified # 🙎🏿 person pouting: dark skin tone +1F64E 200D 2642 FE0F ; fully-qualified # 🙎‍♂️ man pouting +1F64E 200D 2642 ; non-fully-qualified # 🙎‍♂ man pouting +1F64E 1F3FB 200D 2642 FE0F ; fully-qualified # 🙎🏻‍♂️ man pouting: light skin tone +1F64E 1F3FB 200D 2642 ; non-fully-qualified # 🙎🏻‍♂ man pouting: light skin tone +1F64E 1F3FC 200D 2642 FE0F ; fully-qualified # 🙎🏼‍♂️ man pouting: medium-light skin tone +1F64E 1F3FC 200D 2642 ; non-fully-qualified # 🙎🏼‍♂ man pouting: medium-light skin tone +1F64E 1F3FD 200D 2642 FE0F ; fully-qualified # 🙎🏽‍♂️ man pouting: medium skin tone +1F64E 1F3FD 200D 2642 ; non-fully-qualified # 🙎🏽‍♂ man pouting: medium skin tone +1F64E 1F3FE 200D 2642 FE0F ; fully-qualified # 🙎🏾‍♂️ man pouting: medium-dark skin tone +1F64E 1F3FE 200D 2642 ; non-fully-qualified # 🙎🏾‍♂ man pouting: medium-dark skin tone +1F64E 1F3FF 200D 2642 FE0F ; fully-qualified # 🙎🏿‍♂️ man pouting: dark skin tone +1F64E 1F3FF 200D 2642 ; non-fully-qualified # 🙎🏿‍♂ man pouting: dark skin tone +1F64E 200D 2640 FE0F ; fully-qualified # 🙎‍♀️ woman pouting +1F64E 200D 2640 ; non-fully-qualified # 🙎‍♀ woman pouting +1F64E 1F3FB 200D 2640 FE0F ; fully-qualified # 🙎🏻‍♀️ woman pouting: light skin tone +1F64E 1F3FB 200D 2640 ; non-fully-qualified # 🙎🏻‍♀ woman pouting: light skin tone +1F64E 1F3FC 200D 2640 FE0F ; fully-qualified # 🙎🏼‍♀️ woman pouting: medium-light skin tone +1F64E 1F3FC 200D 2640 ; non-fully-qualified # 🙎🏼‍♀ woman pouting: medium-light skin tone +1F64E 1F3FD 200D 2640 FE0F ; fully-qualified # 🙎🏽‍♀️ woman pouting: medium skin tone +1F64E 1F3FD 200D 2640 ; non-fully-qualified # 🙎🏽‍♀ woman pouting: medium skin tone +1F64E 1F3FE 200D 2640 FE0F ; fully-qualified # 🙎🏾‍♀️ woman pouting: medium-dark skin tone +1F64E 1F3FE 200D 2640 ; non-fully-qualified # 🙎🏾‍♀ woman pouting: medium-dark skin tone +1F64E 1F3FF 200D 2640 FE0F ; fully-qualified # 🙎🏿‍♀️ woman pouting: dark skin tone +1F64E 1F3FF 200D 2640 ; non-fully-qualified # 🙎🏿‍♀ woman pouting: dark skin tone +1F645 ; fully-qualified # 🙅 person gesturing NO +1F645 1F3FB ; fully-qualified # 🙅🏻 person gesturing NO: light skin tone +1F645 1F3FC ; fully-qualified # 🙅🏼 person gesturing NO: medium-light skin tone +1F645 1F3FD ; fully-qualified # 🙅🏽 person gesturing NO: medium skin tone +1F645 1F3FE ; fully-qualified # 🙅🏾 person gesturing NO: medium-dark skin tone +1F645 1F3FF ; fully-qualified # 🙅🏿 person gesturing NO: dark skin tone +1F645 200D 2642 FE0F ; fully-qualified # 🙅‍♂️ man gesturing NO +1F645 200D 2642 ; non-fully-qualified # 🙅‍♂ man gesturing NO +1F645 1F3FB 200D 2642 FE0F ; fully-qualified # 🙅🏻‍♂️ man gesturing NO: light skin tone +1F645 1F3FB 200D 2642 ; non-fully-qualified # 🙅🏻‍♂ man gesturing NO: light skin tone +1F645 1F3FC 200D 2642 FE0F ; fully-qualified # 🙅🏼‍♂️ man gesturing NO: medium-light skin tone +1F645 1F3FC 200D 2642 ; non-fully-qualified # 🙅🏼‍♂ man gesturing NO: medium-light skin tone +1F645 1F3FD 200D 2642 FE0F ; fully-qualified # 🙅🏽‍♂️ man gesturing NO: medium skin tone +1F645 1F3FD 200D 2642 ; non-fully-qualified # 🙅🏽‍♂ man gesturing NO: medium skin tone +1F645 1F3FE 200D 2642 FE0F ; fully-qualified # 🙅🏾‍♂️ man gesturing NO: medium-dark skin tone +1F645 1F3FE 200D 2642 ; non-fully-qualified # 🙅🏾‍♂ man gesturing NO: medium-dark skin tone +1F645 1F3FF 200D 2642 FE0F ; fully-qualified # 🙅🏿‍♂️ man gesturing NO: dark skin tone +1F645 1F3FF 200D 2642 ; non-fully-qualified # 🙅🏿‍♂ man gesturing NO: dark skin tone +1F645 200D 2640 FE0F ; fully-qualified # 🙅‍♀️ woman gesturing NO +1F645 200D 2640 ; non-fully-qualified # 🙅‍♀ woman gesturing NO +1F645 1F3FB 200D 2640 FE0F ; fully-qualified # 🙅🏻‍♀️ woman gesturing NO: light skin tone +1F645 1F3FB 200D 2640 ; non-fully-qualified # 🙅🏻‍♀ woman gesturing NO: light skin tone +1F645 1F3FC 200D 2640 FE0F ; fully-qualified # 🙅🏼‍♀️ woman gesturing NO: medium-light skin tone +1F645 1F3FC 200D 2640 ; non-fully-qualified # 🙅🏼‍♀ woman gesturing NO: medium-light skin tone +1F645 1F3FD 200D 2640 FE0F ; fully-qualified # 🙅🏽‍♀️ woman gesturing NO: medium skin tone +1F645 1F3FD 200D 2640 ; non-fully-qualified # 🙅🏽‍♀ woman gesturing NO: medium skin tone +1F645 1F3FE 200D 2640 FE0F ; fully-qualified # 🙅🏾‍♀️ woman gesturing NO: medium-dark skin tone +1F645 1F3FE 200D 2640 ; non-fully-qualified # 🙅🏾‍♀ woman gesturing NO: medium-dark skin tone +1F645 1F3FF 200D 2640 FE0F ; fully-qualified # 🙅🏿‍♀️ woman gesturing NO: dark skin tone +1F645 1F3FF 200D 2640 ; non-fully-qualified # 🙅🏿‍♀ woman gesturing NO: dark skin tone +1F646 ; fully-qualified # 🙆 person gesturing OK +1F646 1F3FB ; fully-qualified # 🙆🏻 person gesturing OK: light skin tone +1F646 1F3FC ; fully-qualified # 🙆🏼 person gesturing OK: medium-light skin tone +1F646 1F3FD ; fully-qualified # 🙆🏽 person gesturing OK: medium skin tone +1F646 1F3FE ; fully-qualified # 🙆🏾 person gesturing OK: medium-dark skin tone +1F646 1F3FF ; fully-qualified # 🙆🏿 person gesturing OK: dark skin tone +1F646 200D 2642 FE0F ; fully-qualified # 🙆‍♂️ man gesturing OK +1F646 200D 2642 ; non-fully-qualified # 🙆‍♂ man gesturing OK +1F646 1F3FB 200D 2642 FE0F ; fully-qualified # 🙆🏻‍♂️ man gesturing OK: light skin tone +1F646 1F3FB 200D 2642 ; non-fully-qualified # 🙆🏻‍♂ man gesturing OK: light skin tone +1F646 1F3FC 200D 2642 FE0F ; fully-qualified # 🙆🏼‍♂️ man gesturing OK: medium-light skin tone +1F646 1F3FC 200D 2642 ; non-fully-qualified # 🙆🏼‍♂ man gesturing OK: medium-light skin tone +1F646 1F3FD 200D 2642 FE0F ; fully-qualified # 🙆🏽‍♂️ man gesturing OK: medium skin tone +1F646 1F3FD 200D 2642 ; non-fully-qualified # 🙆🏽‍♂ man gesturing OK: medium skin tone +1F646 1F3FE 200D 2642 FE0F ; fully-qualified # 🙆🏾‍♂️ man gesturing OK: medium-dark skin tone +1F646 1F3FE 200D 2642 ; non-fully-qualified # 🙆🏾‍♂ man gesturing OK: medium-dark skin tone +1F646 1F3FF 200D 2642 FE0F ; fully-qualified # 🙆🏿‍♂️ man gesturing OK: dark skin tone +1F646 1F3FF 200D 2642 ; non-fully-qualified # 🙆🏿‍♂ man gesturing OK: dark skin tone +1F646 200D 2640 FE0F ; fully-qualified # 🙆‍♀️ woman gesturing OK +1F646 200D 2640 ; non-fully-qualified # 🙆‍♀ woman gesturing OK +1F646 1F3FB 200D 2640 FE0F ; fully-qualified # 🙆🏻‍♀️ woman gesturing OK: light skin tone +1F646 1F3FB 200D 2640 ; non-fully-qualified # 🙆🏻‍♀ woman gesturing OK: light skin tone +1F646 1F3FC 200D 2640 FE0F ; fully-qualified # 🙆🏼‍♀️ woman gesturing OK: medium-light skin tone +1F646 1F3FC 200D 2640 ; non-fully-qualified # 🙆🏼‍♀ woman gesturing OK: medium-light skin tone +1F646 1F3FD 200D 2640 FE0F ; fully-qualified # 🙆🏽‍♀️ woman gesturing OK: medium skin tone +1F646 1F3FD 200D 2640 ; non-fully-qualified # 🙆🏽‍♀ woman gesturing OK: medium skin tone +1F646 1F3FE 200D 2640 FE0F ; fully-qualified # 🙆🏾‍♀️ woman gesturing OK: medium-dark skin tone +1F646 1F3FE 200D 2640 ; non-fully-qualified # 🙆🏾‍♀ woman gesturing OK: medium-dark skin tone +1F646 1F3FF 200D 2640 FE0F ; fully-qualified # 🙆🏿‍♀️ woman gesturing OK: dark skin tone +1F646 1F3FF 200D 2640 ; non-fully-qualified # 🙆🏿‍♀ woman gesturing OK: dark skin tone +1F481 ; fully-qualified # 💁 person tipping hand +1F481 1F3FB ; fully-qualified # 💁🏻 person tipping hand: light skin tone +1F481 1F3FC ; fully-qualified # 💁🏼 person tipping hand: medium-light skin tone +1F481 1F3FD ; fully-qualified # 💁🏽 person tipping hand: medium skin tone +1F481 1F3FE ; fully-qualified # 💁🏾 person tipping hand: medium-dark skin tone +1F481 1F3FF ; fully-qualified # 💁🏿 person tipping hand: dark skin tone +1F481 200D 2642 FE0F ; fully-qualified # 💁‍♂️ man tipping hand +1F481 200D 2642 ; non-fully-qualified # 💁‍♂ man tipping hand +1F481 1F3FB 200D 2642 FE0F ; fully-qualified # 💁🏻‍♂️ man tipping hand: light skin tone +1F481 1F3FB 200D 2642 ; non-fully-qualified # 💁🏻‍♂ man tipping hand: light skin tone +1F481 1F3FC 200D 2642 FE0F ; fully-qualified # 💁🏼‍♂️ man tipping hand: medium-light skin tone +1F481 1F3FC 200D 2642 ; non-fully-qualified # 💁🏼‍♂ man tipping hand: medium-light skin tone +1F481 1F3FD 200D 2642 FE0F ; fully-qualified # 💁🏽‍♂️ man tipping hand: medium skin tone +1F481 1F3FD 200D 2642 ; non-fully-qualified # 💁🏽‍♂ man tipping hand: medium skin tone +1F481 1F3FE 200D 2642 FE0F ; fully-qualified # 💁🏾‍♂️ man tipping hand: medium-dark skin tone +1F481 1F3FE 200D 2642 ; non-fully-qualified # 💁🏾‍♂ man tipping hand: medium-dark skin tone +1F481 1F3FF 200D 2642 FE0F ; fully-qualified # 💁🏿‍♂️ man tipping hand: dark skin tone +1F481 1F3FF 200D 2642 ; non-fully-qualified # 💁🏿‍♂ man tipping hand: dark skin tone +1F481 200D 2640 FE0F ; fully-qualified # 💁‍♀️ woman tipping hand +1F481 200D 2640 ; non-fully-qualified # 💁‍♀ woman tipping hand +1F481 1F3FB 200D 2640 FE0F ; fully-qualified # 💁🏻‍♀️ woman tipping hand: light skin tone +1F481 1F3FB 200D 2640 ; non-fully-qualified # 💁🏻‍♀ woman tipping hand: light skin tone +1F481 1F3FC 200D 2640 FE0F ; fully-qualified # 💁🏼‍♀️ woman tipping hand: medium-light skin tone +1F481 1F3FC 200D 2640 ; non-fully-qualified # 💁🏼‍♀ woman tipping hand: medium-light skin tone +1F481 1F3FD 200D 2640 FE0F ; fully-qualified # 💁🏽‍♀️ woman tipping hand: medium skin tone +1F481 1F3FD 200D 2640 ; non-fully-qualified # 💁🏽‍♀ woman tipping hand: medium skin tone +1F481 1F3FE 200D 2640 FE0F ; fully-qualified # 💁🏾‍♀️ woman tipping hand: medium-dark skin tone +1F481 1F3FE 200D 2640 ; non-fully-qualified # 💁🏾‍♀ woman tipping hand: medium-dark skin tone +1F481 1F3FF 200D 2640 FE0F ; fully-qualified # 💁🏿‍♀️ woman tipping hand: dark skin tone +1F481 1F3FF 200D 2640 ; non-fully-qualified # 💁🏿‍♀ woman tipping hand: dark skin tone +1F64B ; fully-qualified # 🙋 person raising hand +1F64B 1F3FB ; fully-qualified # 🙋🏻 person raising hand: light skin tone +1F64B 1F3FC ; fully-qualified # 🙋🏼 person raising hand: medium-light skin tone +1F64B 1F3FD ; fully-qualified # 🙋🏽 person raising hand: medium skin tone +1F64B 1F3FE ; fully-qualified # 🙋🏾 person raising hand: medium-dark skin tone +1F64B 1F3FF ; fully-qualified # 🙋🏿 person raising hand: dark skin tone +1F64B 200D 2642 FE0F ; fully-qualified # 🙋‍♂️ man raising hand +1F64B 200D 2642 ; non-fully-qualified # 🙋‍♂ man raising hand +1F64B 1F3FB 200D 2642 FE0F ; fully-qualified # 🙋🏻‍♂️ man raising hand: light skin tone +1F64B 1F3FB 200D 2642 ; non-fully-qualified # 🙋🏻‍♂ man raising hand: light skin tone +1F64B 1F3FC 200D 2642 FE0F ; fully-qualified # 🙋🏼‍♂️ man raising hand: medium-light skin tone +1F64B 1F3FC 200D 2642 ; non-fully-qualified # 🙋🏼‍♂ man raising hand: medium-light skin tone +1F64B 1F3FD 200D 2642 FE0F ; fully-qualified # 🙋🏽‍♂️ man raising hand: medium skin tone +1F64B 1F3FD 200D 2642 ; non-fully-qualified # 🙋🏽‍♂ man raising hand: medium skin tone +1F64B 1F3FE 200D 2642 FE0F ; fully-qualified # 🙋🏾‍♂️ man raising hand: medium-dark skin tone +1F64B 1F3FE 200D 2642 ; non-fully-qualified # 🙋🏾‍♂ man raising hand: medium-dark skin tone +1F64B 1F3FF 200D 2642 FE0F ; fully-qualified # 🙋🏿‍♂️ man raising hand: dark skin tone +1F64B 1F3FF 200D 2642 ; non-fully-qualified # 🙋🏿‍♂ man raising hand: dark skin tone +1F64B 200D 2640 FE0F ; fully-qualified # 🙋‍♀️ woman raising hand +1F64B 200D 2640 ; non-fully-qualified # 🙋‍♀ woman raising hand +1F64B 1F3FB 200D 2640 FE0F ; fully-qualified # 🙋🏻‍♀️ woman raising hand: light skin tone +1F64B 1F3FB 200D 2640 ; non-fully-qualified # 🙋🏻‍♀ woman raising hand: light skin tone +1F64B 1F3FC 200D 2640 FE0F ; fully-qualified # 🙋🏼‍♀️ woman raising hand: medium-light skin tone +1F64B 1F3FC 200D 2640 ; non-fully-qualified # 🙋🏼‍♀ woman raising hand: medium-light skin tone +1F64B 1F3FD 200D 2640 FE0F ; fully-qualified # 🙋🏽‍♀️ woman raising hand: medium skin tone +1F64B 1F3FD 200D 2640 ; non-fully-qualified # 🙋🏽‍♀ woman raising hand: medium skin tone +1F64B 1F3FE 200D 2640 FE0F ; fully-qualified # 🙋🏾‍♀️ woman raising hand: medium-dark skin tone +1F64B 1F3FE 200D 2640 ; non-fully-qualified # 🙋🏾‍♀ woman raising hand: medium-dark skin tone +1F64B 1F3FF 200D 2640 FE0F ; fully-qualified # 🙋🏿‍♀️ woman raising hand: dark skin tone +1F64B 1F3FF 200D 2640 ; non-fully-qualified # 🙋🏿‍♀ woman raising hand: dark skin tone +1F647 ; fully-qualified # 🙇 person bowing +1F647 1F3FB ; fully-qualified # 🙇🏻 person bowing: light skin tone +1F647 1F3FC ; fully-qualified # 🙇🏼 person bowing: medium-light skin tone +1F647 1F3FD ; fully-qualified # 🙇🏽 person bowing: medium skin tone +1F647 1F3FE ; fully-qualified # 🙇🏾 person bowing: medium-dark skin tone +1F647 1F3FF ; fully-qualified # 🙇🏿 person bowing: dark skin tone +1F647 200D 2642 FE0F ; fully-qualified # 🙇‍♂️ man bowing +1F647 200D 2642 ; non-fully-qualified # 🙇‍♂ man bowing +1F647 1F3FB 200D 2642 FE0F ; fully-qualified # 🙇🏻‍♂️ man bowing: light skin tone +1F647 1F3FB 200D 2642 ; non-fully-qualified # 🙇🏻‍♂ man bowing: light skin tone +1F647 1F3FC 200D 2642 FE0F ; fully-qualified # 🙇🏼‍♂️ man bowing: medium-light skin tone +1F647 1F3FC 200D 2642 ; non-fully-qualified # 🙇🏼‍♂ man bowing: medium-light skin tone +1F647 1F3FD 200D 2642 FE0F ; fully-qualified # 🙇🏽‍♂️ man bowing: medium skin tone +1F647 1F3FD 200D 2642 ; non-fully-qualified # 🙇🏽‍♂ man bowing: medium skin tone +1F647 1F3FE 200D 2642 FE0F ; fully-qualified # 🙇🏾‍♂️ man bowing: medium-dark skin tone +1F647 1F3FE 200D 2642 ; non-fully-qualified # 🙇🏾‍♂ man bowing: medium-dark skin tone +1F647 1F3FF 200D 2642 FE0F ; fully-qualified # 🙇🏿‍♂️ man bowing: dark skin tone +1F647 1F3FF 200D 2642 ; non-fully-qualified # 🙇🏿‍♂ man bowing: dark skin tone +1F647 200D 2640 FE0F ; fully-qualified # 🙇‍♀️ woman bowing +1F647 200D 2640 ; non-fully-qualified # 🙇‍♀ woman bowing +1F647 1F3FB 200D 2640 FE0F ; fully-qualified # 🙇🏻‍♀️ woman bowing: light skin tone +1F647 1F3FB 200D 2640 ; non-fully-qualified # 🙇🏻‍♀ woman bowing: light skin tone +1F647 1F3FC 200D 2640 FE0F ; fully-qualified # 🙇🏼‍♀️ woman bowing: medium-light skin tone +1F647 1F3FC 200D 2640 ; non-fully-qualified # 🙇🏼‍♀ woman bowing: medium-light skin tone +1F647 1F3FD 200D 2640 FE0F ; fully-qualified # 🙇🏽‍♀️ woman bowing: medium skin tone +1F647 1F3FD 200D 2640 ; non-fully-qualified # 🙇🏽‍♀ woman bowing: medium skin tone +1F647 1F3FE 200D 2640 FE0F ; fully-qualified # 🙇🏾‍♀️ woman bowing: medium-dark skin tone +1F647 1F3FE 200D 2640 ; non-fully-qualified # 🙇🏾‍♀ woman bowing: medium-dark skin tone +1F647 1F3FF 200D 2640 FE0F ; fully-qualified # 🙇🏿‍♀️ woman bowing: dark skin tone +1F647 1F3FF 200D 2640 ; non-fully-qualified # 🙇🏿‍♀ woman bowing: dark skin tone +1F926 ; fully-qualified # 🤦 person facepalming +1F926 1F3FB ; fully-qualified # 🤦🏻 person facepalming: light skin tone +1F926 1F3FC ; fully-qualified # 🤦🏼 person facepalming: medium-light skin tone +1F926 1F3FD ; fully-qualified # 🤦🏽 person facepalming: medium skin tone +1F926 1F3FE ; fully-qualified # 🤦🏾 person facepalming: medium-dark skin tone +1F926 1F3FF ; fully-qualified # 🤦🏿 person facepalming: dark skin tone +1F926 200D 2642 FE0F ; fully-qualified # 🤦‍♂️ man facepalming +1F926 200D 2642 ; non-fully-qualified # 🤦‍♂ man facepalming +1F926 1F3FB 200D 2642 FE0F ; fully-qualified # 🤦🏻‍♂️ man facepalming: light skin tone +1F926 1F3FB 200D 2642 ; non-fully-qualified # 🤦🏻‍♂ man facepalming: light skin tone +1F926 1F3FC 200D 2642 FE0F ; fully-qualified # 🤦🏼‍♂️ man facepalming: medium-light skin tone +1F926 1F3FC 200D 2642 ; non-fully-qualified # 🤦🏼‍♂ man facepalming: medium-light skin tone +1F926 1F3FD 200D 2642 FE0F ; fully-qualified # 🤦🏽‍♂️ man facepalming: medium skin tone +1F926 1F3FD 200D 2642 ; non-fully-qualified # 🤦🏽‍♂ man facepalming: medium skin tone +1F926 1F3FE 200D 2642 FE0F ; fully-qualified # 🤦🏾‍♂️ man facepalming: medium-dark skin tone +1F926 1F3FE 200D 2642 ; non-fully-qualified # 🤦🏾‍♂ man facepalming: medium-dark skin tone +1F926 1F3FF 200D 2642 FE0F ; fully-qualified # 🤦🏿‍♂️ man facepalming: dark skin tone +1F926 1F3FF 200D 2642 ; non-fully-qualified # 🤦🏿‍♂ man facepalming: dark skin tone +1F926 200D 2640 FE0F ; fully-qualified # 🤦‍♀️ woman facepalming +1F926 200D 2640 ; non-fully-qualified # 🤦‍♀ woman facepalming +1F926 1F3FB 200D 2640 FE0F ; fully-qualified # 🤦🏻‍♀️ woman facepalming: light skin tone +1F926 1F3FB 200D 2640 ; non-fully-qualified # 🤦🏻‍♀ woman facepalming: light skin tone +1F926 1F3FC 200D 2640 FE0F ; fully-qualified # 🤦🏼‍♀️ woman facepalming: medium-light skin tone +1F926 1F3FC 200D 2640 ; non-fully-qualified # 🤦🏼‍♀ woman facepalming: medium-light skin tone +1F926 1F3FD 200D 2640 FE0F ; fully-qualified # 🤦🏽‍♀️ woman facepalming: medium skin tone +1F926 1F3FD 200D 2640 ; non-fully-qualified # 🤦🏽‍♀ woman facepalming: medium skin tone +1F926 1F3FE 200D 2640 FE0F ; fully-qualified # 🤦🏾‍♀️ woman facepalming: medium-dark skin tone +1F926 1F3FE 200D 2640 ; non-fully-qualified # 🤦🏾‍♀ woman facepalming: medium-dark skin tone +1F926 1F3FF 200D 2640 FE0F ; fully-qualified # 🤦🏿‍♀️ woman facepalming: dark skin tone +1F926 1F3FF 200D 2640 ; non-fully-qualified # 🤦🏿‍♀ woman facepalming: dark skin tone +1F937 ; fully-qualified # 🤷 person shrugging +1F937 1F3FB ; fully-qualified # 🤷🏻 person shrugging: light skin tone +1F937 1F3FC ; fully-qualified # 🤷🏼 person shrugging: medium-light skin tone +1F937 1F3FD ; fully-qualified # 🤷🏽 person shrugging: medium skin tone +1F937 1F3FE ; fully-qualified # 🤷🏾 person shrugging: medium-dark skin tone +1F937 1F3FF ; fully-qualified # 🤷🏿 person shrugging: dark skin tone +1F937 200D 2642 FE0F ; fully-qualified # 🤷‍♂️ man shrugging +1F937 200D 2642 ; non-fully-qualified # 🤷‍♂ man shrugging +1F937 1F3FB 200D 2642 FE0F ; fully-qualified # 🤷🏻‍♂️ man shrugging: light skin tone +1F937 1F3FB 200D 2642 ; non-fully-qualified # 🤷🏻‍♂ man shrugging: light skin tone +1F937 1F3FC 200D 2642 FE0F ; fully-qualified # 🤷🏼‍♂️ man shrugging: medium-light skin tone +1F937 1F3FC 200D 2642 ; non-fully-qualified # 🤷🏼‍♂ man shrugging: medium-light skin tone +1F937 1F3FD 200D 2642 FE0F ; fully-qualified # 🤷🏽‍♂️ man shrugging: medium skin tone +1F937 1F3FD 200D 2642 ; non-fully-qualified # 🤷🏽‍♂ man shrugging: medium skin tone +1F937 1F3FE 200D 2642 FE0F ; fully-qualified # 🤷🏾‍♂️ man shrugging: medium-dark skin tone +1F937 1F3FE 200D 2642 ; non-fully-qualified # 🤷🏾‍♂ man shrugging: medium-dark skin tone +1F937 1F3FF 200D 2642 FE0F ; fully-qualified # 🤷🏿‍♂️ man shrugging: dark skin tone +1F937 1F3FF 200D 2642 ; non-fully-qualified # 🤷🏿‍♂ man shrugging: dark skin tone +1F937 200D 2640 FE0F ; fully-qualified # 🤷‍♀️ woman shrugging +1F937 200D 2640 ; non-fully-qualified # 🤷‍♀ woman shrugging +1F937 1F3FB 200D 2640 FE0F ; fully-qualified # 🤷🏻‍♀️ woman shrugging: light skin tone +1F937 1F3FB 200D 2640 ; non-fully-qualified # 🤷🏻‍♀ woman shrugging: light skin tone +1F937 1F3FC 200D 2640 FE0F ; fully-qualified # 🤷🏼‍♀️ woman shrugging: medium-light skin tone +1F937 1F3FC 200D 2640 ; non-fully-qualified # 🤷🏼‍♀ woman shrugging: medium-light skin tone +1F937 1F3FD 200D 2640 FE0F ; fully-qualified # 🤷🏽‍♀️ woman shrugging: medium skin tone +1F937 1F3FD 200D 2640 ; non-fully-qualified # 🤷🏽‍♀ woman shrugging: medium skin tone +1F937 1F3FE 200D 2640 FE0F ; fully-qualified # 🤷🏾‍♀️ woman shrugging: medium-dark skin tone +1F937 1F3FE 200D 2640 ; non-fully-qualified # 🤷🏾‍♀ woman shrugging: medium-dark skin tone +1F937 1F3FF 200D 2640 FE0F ; fully-qualified # 🤷🏿‍♀️ woman shrugging: dark skin tone +1F937 1F3FF 200D 2640 ; non-fully-qualified # 🤷🏿‍♀ woman shrugging: dark skin tone + +# subgroup: person-activity +1F486 ; fully-qualified # 💆 person getting massage +1F486 1F3FB ; fully-qualified # 💆🏻 person getting massage: light skin tone +1F486 1F3FC ; fully-qualified # 💆🏼 person getting massage: medium-light skin tone +1F486 1F3FD ; fully-qualified # 💆🏽 person getting massage: medium skin tone +1F486 1F3FE ; fully-qualified # 💆🏾 person getting massage: medium-dark skin tone +1F486 1F3FF ; fully-qualified # 💆🏿 person getting massage: dark skin tone +1F486 200D 2642 FE0F ; fully-qualified # 💆‍♂️ man getting massage +1F486 200D 2642 ; non-fully-qualified # 💆‍♂ man getting massage +1F486 1F3FB 200D 2642 FE0F ; fully-qualified # 💆🏻‍♂️ man getting massage: light skin tone +1F486 1F3FB 200D 2642 ; non-fully-qualified # 💆🏻‍♂ man getting massage: light skin tone +1F486 1F3FC 200D 2642 FE0F ; fully-qualified # 💆🏼‍♂️ man getting massage: medium-light skin tone +1F486 1F3FC 200D 2642 ; non-fully-qualified # 💆🏼‍♂ man getting massage: medium-light skin tone +1F486 1F3FD 200D 2642 FE0F ; fully-qualified # 💆🏽‍♂️ man getting massage: medium skin tone +1F486 1F3FD 200D 2642 ; non-fully-qualified # 💆🏽‍♂ man getting massage: medium skin tone +1F486 1F3FE 200D 2642 FE0F ; fully-qualified # 💆🏾‍♂️ man getting massage: medium-dark skin tone +1F486 1F3FE 200D 2642 ; non-fully-qualified # 💆🏾‍♂ man getting massage: medium-dark skin tone +1F486 1F3FF 200D 2642 FE0F ; fully-qualified # 💆🏿‍♂️ man getting massage: dark skin tone +1F486 1F3FF 200D 2642 ; non-fully-qualified # 💆🏿‍♂ man getting massage: dark skin tone +1F486 200D 2640 FE0F ; fully-qualified # 💆‍♀️ woman getting massage +1F486 200D 2640 ; non-fully-qualified # 💆‍♀ woman getting massage +1F486 1F3FB 200D 2640 FE0F ; fully-qualified # 💆🏻‍♀️ woman getting massage: light skin tone +1F486 1F3FB 200D 2640 ; non-fully-qualified # 💆🏻‍♀ woman getting massage: light skin tone +1F486 1F3FC 200D 2640 FE0F ; fully-qualified # 💆🏼‍♀️ woman getting massage: medium-light skin tone +1F486 1F3FC 200D 2640 ; non-fully-qualified # 💆🏼‍♀ woman getting massage: medium-light skin tone +1F486 1F3FD 200D 2640 FE0F ; fully-qualified # 💆🏽‍♀️ woman getting massage: medium skin tone +1F486 1F3FD 200D 2640 ; non-fully-qualified # 💆🏽‍♀ woman getting massage: medium skin tone +1F486 1F3FE 200D 2640 FE0F ; fully-qualified # 💆🏾‍♀️ woman getting massage: medium-dark skin tone +1F486 1F3FE 200D 2640 ; non-fully-qualified # 💆🏾‍♀ woman getting massage: medium-dark skin tone +1F486 1F3FF 200D 2640 FE0F ; fully-qualified # 💆🏿‍♀️ woman getting massage: dark skin tone +1F486 1F3FF 200D 2640 ; non-fully-qualified # 💆🏿‍♀ woman getting massage: dark skin tone +1F487 ; fully-qualified # 💇 person getting haircut +1F487 1F3FB ; fully-qualified # 💇🏻 person getting haircut: light skin tone +1F487 1F3FC ; fully-qualified # 💇🏼 person getting haircut: medium-light skin tone +1F487 1F3FD ; fully-qualified # 💇🏽 person getting haircut: medium skin tone +1F487 1F3FE ; fully-qualified # 💇🏾 person getting haircut: medium-dark skin tone +1F487 1F3FF ; fully-qualified # 💇🏿 person getting haircut: dark skin tone +1F487 200D 2642 FE0F ; fully-qualified # 💇‍♂️ man getting haircut +1F487 200D 2642 ; non-fully-qualified # 💇‍♂ man getting haircut +1F487 1F3FB 200D 2642 FE0F ; fully-qualified # 💇🏻‍♂️ man getting haircut: light skin tone +1F487 1F3FB 200D 2642 ; non-fully-qualified # 💇🏻‍♂ man getting haircut: light skin tone +1F487 1F3FC 200D 2642 FE0F ; fully-qualified # 💇🏼‍♂️ man getting haircut: medium-light skin tone +1F487 1F3FC 200D 2642 ; non-fully-qualified # 💇🏼‍♂ man getting haircut: medium-light skin tone +1F487 1F3FD 200D 2642 FE0F ; fully-qualified # 💇🏽‍♂️ man getting haircut: medium skin tone +1F487 1F3FD 200D 2642 ; non-fully-qualified # 💇🏽‍♂ man getting haircut: medium skin tone +1F487 1F3FE 200D 2642 FE0F ; fully-qualified # 💇🏾‍♂️ man getting haircut: medium-dark skin tone +1F487 1F3FE 200D 2642 ; non-fully-qualified # 💇🏾‍♂ man getting haircut: medium-dark skin tone +1F487 1F3FF 200D 2642 FE0F ; fully-qualified # 💇🏿‍♂️ man getting haircut: dark skin tone +1F487 1F3FF 200D 2642 ; non-fully-qualified # 💇🏿‍♂ man getting haircut: dark skin tone +1F487 200D 2640 FE0F ; fully-qualified # 💇‍♀️ woman getting haircut +1F487 200D 2640 ; non-fully-qualified # 💇‍♀ woman getting haircut +1F487 1F3FB 200D 2640 FE0F ; fully-qualified # 💇🏻‍♀️ woman getting haircut: light skin tone +1F487 1F3FB 200D 2640 ; non-fully-qualified # 💇🏻‍♀ woman getting haircut: light skin tone +1F487 1F3FC 200D 2640 FE0F ; fully-qualified # 💇🏼‍♀️ woman getting haircut: medium-light skin tone +1F487 1F3FC 200D 2640 ; non-fully-qualified # 💇🏼‍♀ woman getting haircut: medium-light skin tone +1F487 1F3FD 200D 2640 FE0F ; fully-qualified # 💇🏽‍♀️ woman getting haircut: medium skin tone +1F487 1F3FD 200D 2640 ; non-fully-qualified # 💇🏽‍♀ woman getting haircut: medium skin tone +1F487 1F3FE 200D 2640 FE0F ; fully-qualified # 💇🏾‍♀️ woman getting haircut: medium-dark skin tone +1F487 1F3FE 200D 2640 ; non-fully-qualified # 💇🏾‍♀ woman getting haircut: medium-dark skin tone +1F487 1F3FF 200D 2640 FE0F ; fully-qualified # 💇🏿‍♀️ woman getting haircut: dark skin tone +1F487 1F3FF 200D 2640 ; non-fully-qualified # 💇🏿‍♀ woman getting haircut: dark skin tone +1F6B6 ; fully-qualified # 🚶 person walking +1F6B6 1F3FB ; fully-qualified # 🚶🏻 person walking: light skin tone +1F6B6 1F3FC ; fully-qualified # 🚶🏼 person walking: medium-light skin tone +1F6B6 1F3FD ; fully-qualified # 🚶🏽 person walking: medium skin tone +1F6B6 1F3FE ; fully-qualified # 🚶🏾 person walking: medium-dark skin tone +1F6B6 1F3FF ; fully-qualified # 🚶🏿 person walking: dark skin tone +1F6B6 200D 2642 FE0F ; fully-qualified # 🚶‍♂️ man walking +1F6B6 200D 2642 ; non-fully-qualified # 🚶‍♂ man walking +1F6B6 1F3FB 200D 2642 FE0F ; fully-qualified # 🚶🏻‍♂️ man walking: light skin tone +1F6B6 1F3FB 200D 2642 ; non-fully-qualified # 🚶🏻‍♂ man walking: light skin tone +1F6B6 1F3FC 200D 2642 FE0F ; fully-qualified # 🚶🏼‍♂️ man walking: medium-light skin tone +1F6B6 1F3FC 200D 2642 ; non-fully-qualified # 🚶🏼‍♂ man walking: medium-light skin tone +1F6B6 1F3FD 200D 2642 FE0F ; fully-qualified # 🚶🏽‍♂️ man walking: medium skin tone +1F6B6 1F3FD 200D 2642 ; non-fully-qualified # 🚶🏽‍♂ man walking: medium skin tone +1F6B6 1F3FE 200D 2642 FE0F ; fully-qualified # 🚶🏾‍♂️ man walking: medium-dark skin tone +1F6B6 1F3FE 200D 2642 ; non-fully-qualified # 🚶🏾‍♂ man walking: medium-dark skin tone +1F6B6 1F3FF 200D 2642 FE0F ; fully-qualified # 🚶🏿‍♂️ man walking: dark skin tone +1F6B6 1F3FF 200D 2642 ; non-fully-qualified # 🚶🏿‍♂ man walking: dark skin tone +1F6B6 200D 2640 FE0F ; fully-qualified # 🚶‍♀️ woman walking +1F6B6 200D 2640 ; non-fully-qualified # 🚶‍♀ woman walking +1F6B6 1F3FB 200D 2640 FE0F ; fully-qualified # 🚶🏻‍♀️ woman walking: light skin tone +1F6B6 1F3FB 200D 2640 ; non-fully-qualified # 🚶🏻‍♀ woman walking: light skin tone +1F6B6 1F3FC 200D 2640 FE0F ; fully-qualified # 🚶🏼‍♀️ woman walking: medium-light skin tone +1F6B6 1F3FC 200D 2640 ; non-fully-qualified # 🚶🏼‍♀ woman walking: medium-light skin tone +1F6B6 1F3FD 200D 2640 FE0F ; fully-qualified # 🚶🏽‍♀️ woman walking: medium skin tone +1F6B6 1F3FD 200D 2640 ; non-fully-qualified # 🚶🏽‍♀ woman walking: medium skin tone +1F6B6 1F3FE 200D 2640 FE0F ; fully-qualified # 🚶🏾‍♀️ woman walking: medium-dark skin tone +1F6B6 1F3FE 200D 2640 ; non-fully-qualified # 🚶🏾‍♀ woman walking: medium-dark skin tone +1F6B6 1F3FF 200D 2640 FE0F ; fully-qualified # 🚶🏿‍♀️ woman walking: dark skin tone +1F6B6 1F3FF 200D 2640 ; non-fully-qualified # 🚶🏿‍♀ woman walking: dark skin tone +1F3C3 ; fully-qualified # 🏃 person running +1F3C3 1F3FB ; fully-qualified # 🏃🏻 person running: light skin tone +1F3C3 1F3FC ; fully-qualified # 🏃🏼 person running: medium-light skin tone +1F3C3 1F3FD ; fully-qualified # 🏃🏽 person running: medium skin tone +1F3C3 1F3FE ; fully-qualified # 🏃🏾 person running: medium-dark skin tone +1F3C3 1F3FF ; fully-qualified # 🏃🏿 person running: dark skin tone +1F3C3 200D 2642 FE0F ; fully-qualified # 🏃‍♂️ man running +1F3C3 200D 2642 ; non-fully-qualified # 🏃‍♂ man running +1F3C3 1F3FB 200D 2642 FE0F ; fully-qualified # 🏃🏻‍♂️ man running: light skin tone +1F3C3 1F3FB 200D 2642 ; non-fully-qualified # 🏃🏻‍♂ man running: light skin tone +1F3C3 1F3FC 200D 2642 FE0F ; fully-qualified # 🏃🏼‍♂️ man running: medium-light skin tone +1F3C3 1F3FC 200D 2642 ; non-fully-qualified # 🏃🏼‍♂ man running: medium-light skin tone +1F3C3 1F3FD 200D 2642 FE0F ; fully-qualified # 🏃🏽‍♂️ man running: medium skin tone +1F3C3 1F3FD 200D 2642 ; non-fully-qualified # 🏃🏽‍♂ man running: medium skin tone +1F3C3 1F3FE 200D 2642 FE0F ; fully-qualified # 🏃🏾‍♂️ man running: medium-dark skin tone +1F3C3 1F3FE 200D 2642 ; non-fully-qualified # 🏃🏾‍♂ man running: medium-dark skin tone +1F3C3 1F3FF 200D 2642 FE0F ; fully-qualified # 🏃🏿‍♂️ man running: dark skin tone +1F3C3 1F3FF 200D 2642 ; non-fully-qualified # 🏃🏿‍♂ man running: dark skin tone +1F3C3 200D 2640 FE0F ; fully-qualified # 🏃‍♀️ woman running +1F3C3 200D 2640 ; non-fully-qualified # 🏃‍♀ woman running +1F3C3 1F3FB 200D 2640 FE0F ; fully-qualified # 🏃🏻‍♀️ woman running: light skin tone +1F3C3 1F3FB 200D 2640 ; non-fully-qualified # 🏃🏻‍♀ woman running: light skin tone +1F3C3 1F3FC 200D 2640 FE0F ; fully-qualified # 🏃🏼‍♀️ woman running: medium-light skin tone +1F3C3 1F3FC 200D 2640 ; non-fully-qualified # 🏃🏼‍♀ woman running: medium-light skin tone +1F3C3 1F3FD 200D 2640 FE0F ; fully-qualified # 🏃🏽‍♀️ woman running: medium skin tone +1F3C3 1F3FD 200D 2640 ; non-fully-qualified # 🏃🏽‍♀ woman running: medium skin tone +1F3C3 1F3FE 200D 2640 FE0F ; fully-qualified # 🏃🏾‍♀️ woman running: medium-dark skin tone +1F3C3 1F3FE 200D 2640 ; non-fully-qualified # 🏃🏾‍♀ woman running: medium-dark skin tone +1F3C3 1F3FF 200D 2640 FE0F ; fully-qualified # 🏃🏿‍♀️ woman running: dark skin tone +1F3C3 1F3FF 200D 2640 ; non-fully-qualified # 🏃🏿‍♀ woman running: dark skin tone +1F483 ; fully-qualified # 💃 woman dancing +1F483 1F3FB ; fully-qualified # 💃🏻 woman dancing: light skin tone +1F483 1F3FC ; fully-qualified # 💃🏼 woman dancing: medium-light skin tone +1F483 1F3FD ; fully-qualified # 💃🏽 woman dancing: medium skin tone +1F483 1F3FE ; fully-qualified # 💃🏾 woman dancing: medium-dark skin tone +1F483 1F3FF ; fully-qualified # 💃🏿 woman dancing: dark skin tone +1F57A ; fully-qualified # 🕺 man dancing +1F57A 1F3FB ; fully-qualified # 🕺🏻 man dancing: light skin tone +1F57A 1F3FC ; fully-qualified # 🕺🏼 man dancing: medium-light skin tone +1F57A 1F3FD ; fully-qualified # 🕺🏽 man dancing: medium skin tone +1F57A 1F3FE ; fully-qualified # 🕺🏾 man dancing: medium-dark skin tone +1F57A 1F3FF ; fully-qualified # 🕺🏿 man dancing: dark skin tone +1F46F ; fully-qualified # 👯 people with bunny ears +1F46F 200D 2642 FE0F ; fully-qualified # 👯‍♂️ men with bunny ears +1F46F 200D 2642 ; non-fully-qualified # 👯‍♂ men with bunny ears +1F46F 200D 2640 FE0F ; fully-qualified # 👯‍♀️ women with bunny ears +1F46F 200D 2640 ; non-fully-qualified # 👯‍♀ women with bunny ears +1F9D6 ; fully-qualified # 🧖 person in steamy room +1F9D6 1F3FB ; fully-qualified # 🧖🏻 person in steamy room: light skin tone +1F9D6 1F3FC ; fully-qualified # 🧖🏼 person in steamy room: medium-light skin tone +1F9D6 1F3FD ; fully-qualified # 🧖🏽 person in steamy room: medium skin tone +1F9D6 1F3FE ; fully-qualified # 🧖🏾 person in steamy room: medium-dark skin tone +1F9D6 1F3FF ; fully-qualified # 🧖🏿 person in steamy room: dark skin tone +1F9D6 200D 2640 FE0F ; fully-qualified # 🧖‍♀️ woman in steamy room +1F9D6 200D 2640 ; non-fully-qualified # 🧖‍♀ woman in steamy room +1F9D6 1F3FB 200D 2640 FE0F ; fully-qualified # 🧖🏻‍♀️ woman in steamy room: light skin tone +1F9D6 1F3FB 200D 2640 ; non-fully-qualified # 🧖🏻‍♀ woman in steamy room: light skin tone +1F9D6 1F3FC 200D 2640 FE0F ; fully-qualified # 🧖🏼‍♀️ woman in steamy room: medium-light skin tone +1F9D6 1F3FC 200D 2640 ; non-fully-qualified # 🧖🏼‍♀ woman in steamy room: medium-light skin tone +1F9D6 1F3FD 200D 2640 FE0F ; fully-qualified # 🧖🏽‍♀️ woman in steamy room: medium skin tone +1F9D6 1F3FD 200D 2640 ; non-fully-qualified # 🧖🏽‍♀ woman in steamy room: medium skin tone +1F9D6 1F3FE 200D 2640 FE0F ; fully-qualified # 🧖🏾‍♀️ woman in steamy room: medium-dark skin tone +1F9D6 1F3FE 200D 2640 ; non-fully-qualified # 🧖🏾‍♀ woman in steamy room: medium-dark skin tone +1F9D6 1F3FF 200D 2640 FE0F ; fully-qualified # 🧖🏿‍♀️ woman in steamy room: dark skin tone +1F9D6 1F3FF 200D 2640 ; non-fully-qualified # 🧖🏿‍♀ woman in steamy room: dark skin tone +1F9D6 200D 2642 FE0F ; fully-qualified # 🧖‍♂️ man in steamy room +1F9D6 200D 2642 ; non-fully-qualified # 🧖‍♂ man in steamy room +1F9D6 1F3FB 200D 2642 FE0F ; fully-qualified # 🧖🏻‍♂️ man in steamy room: light skin tone +1F9D6 1F3FB 200D 2642 ; non-fully-qualified # 🧖🏻‍♂ man in steamy room: light skin tone +1F9D6 1F3FC 200D 2642 FE0F ; fully-qualified # 🧖🏼‍♂️ man in steamy room: medium-light skin tone +1F9D6 1F3FC 200D 2642 ; non-fully-qualified # 🧖🏼‍♂ man in steamy room: medium-light skin tone +1F9D6 1F3FD 200D 2642 FE0F ; fully-qualified # 🧖🏽‍♂️ man in steamy room: medium skin tone +1F9D6 1F3FD 200D 2642 ; non-fully-qualified # 🧖🏽‍♂ man in steamy room: medium skin tone +1F9D6 1F3FE 200D 2642 FE0F ; fully-qualified # 🧖🏾‍♂️ man in steamy room: medium-dark skin tone +1F9D6 1F3FE 200D 2642 ; non-fully-qualified # 🧖🏾‍♂ man in steamy room: medium-dark skin tone +1F9D6 1F3FF 200D 2642 FE0F ; fully-qualified # 🧖🏿‍♂️ man in steamy room: dark skin tone +1F9D6 1F3FF 200D 2642 ; non-fully-qualified # 🧖🏿‍♂ man in steamy room: dark skin tone +1F9D7 ; fully-qualified # 🧗 person climbing +1F9D7 1F3FB ; fully-qualified # 🧗🏻 person climbing: light skin tone +1F9D7 1F3FC ; fully-qualified # 🧗🏼 person climbing: medium-light skin tone +1F9D7 1F3FD ; fully-qualified # 🧗🏽 person climbing: medium skin tone +1F9D7 1F3FE ; fully-qualified # 🧗🏾 person climbing: medium-dark skin tone +1F9D7 1F3FF ; fully-qualified # 🧗🏿 person climbing: dark skin tone +1F9D7 200D 2640 FE0F ; fully-qualified # 🧗‍♀️ woman climbing +1F9D7 200D 2640 ; non-fully-qualified # 🧗‍♀ woman climbing +1F9D7 1F3FB 200D 2640 FE0F ; fully-qualified # 🧗🏻‍♀️ woman climbing: light skin tone +1F9D7 1F3FB 200D 2640 ; non-fully-qualified # 🧗🏻‍♀ woman climbing: light skin tone +1F9D7 1F3FC 200D 2640 FE0F ; fully-qualified # 🧗🏼‍♀️ woman climbing: medium-light skin tone +1F9D7 1F3FC 200D 2640 ; non-fully-qualified # 🧗🏼‍♀ woman climbing: medium-light skin tone +1F9D7 1F3FD 200D 2640 FE0F ; fully-qualified # 🧗🏽‍♀️ woman climbing: medium skin tone +1F9D7 1F3FD 200D 2640 ; non-fully-qualified # 🧗🏽‍♀ woman climbing: medium skin tone +1F9D7 1F3FE 200D 2640 FE0F ; fully-qualified # 🧗🏾‍♀️ woman climbing: medium-dark skin tone +1F9D7 1F3FE 200D 2640 ; non-fully-qualified # 🧗🏾‍♀ woman climbing: medium-dark skin tone +1F9D7 1F3FF 200D 2640 FE0F ; fully-qualified # 🧗🏿‍♀️ woman climbing: dark skin tone +1F9D7 1F3FF 200D 2640 ; non-fully-qualified # 🧗🏿‍♀ woman climbing: dark skin tone +1F9D7 200D 2642 FE0F ; fully-qualified # 🧗‍♂️ man climbing +1F9D7 200D 2642 ; non-fully-qualified # 🧗‍♂ man climbing +1F9D7 1F3FB 200D 2642 FE0F ; fully-qualified # 🧗🏻‍♂️ man climbing: light skin tone +1F9D7 1F3FB 200D 2642 ; non-fully-qualified # 🧗🏻‍♂ man climbing: light skin tone +1F9D7 1F3FC 200D 2642 FE0F ; fully-qualified # 🧗🏼‍♂️ man climbing: medium-light skin tone +1F9D7 1F3FC 200D 2642 ; non-fully-qualified # 🧗🏼‍♂ man climbing: medium-light skin tone +1F9D7 1F3FD 200D 2642 FE0F ; fully-qualified # 🧗🏽‍♂️ man climbing: medium skin tone +1F9D7 1F3FD 200D 2642 ; non-fully-qualified # 🧗🏽‍♂ man climbing: medium skin tone +1F9D7 1F3FE 200D 2642 FE0F ; fully-qualified # 🧗🏾‍♂️ man climbing: medium-dark skin tone +1F9D7 1F3FE 200D 2642 ; non-fully-qualified # 🧗🏾‍♂ man climbing: medium-dark skin tone +1F9D7 1F3FF 200D 2642 FE0F ; fully-qualified # 🧗🏿‍♂️ man climbing: dark skin tone +1F9D7 1F3FF 200D 2642 ; non-fully-qualified # 🧗🏿‍♂ man climbing: dark skin tone +1F9D8 ; fully-qualified # 🧘 person in lotus position +1F9D8 1F3FB ; fully-qualified # 🧘🏻 person in lotus position: light skin tone +1F9D8 1F3FC ; fully-qualified # 🧘🏼 person in lotus position: medium-light skin tone +1F9D8 1F3FD ; fully-qualified # 🧘🏽 person in lotus position: medium skin tone +1F9D8 1F3FE ; fully-qualified # 🧘🏾 person in lotus position: medium-dark skin tone +1F9D8 1F3FF ; fully-qualified # 🧘🏿 person in lotus position: dark skin tone +1F9D8 200D 2640 FE0F ; fully-qualified # 🧘‍♀️ woman in lotus position +1F9D8 200D 2640 ; non-fully-qualified # 🧘‍♀ woman in lotus position +1F9D8 1F3FB 200D 2640 FE0F ; fully-qualified # 🧘🏻‍♀️ woman in lotus position: light skin tone +1F9D8 1F3FB 200D 2640 ; non-fully-qualified # 🧘🏻‍♀ woman in lotus position: light skin tone +1F9D8 1F3FC 200D 2640 FE0F ; fully-qualified # 🧘🏼‍♀️ woman in lotus position: medium-light skin tone +1F9D8 1F3FC 200D 2640 ; non-fully-qualified # 🧘🏼‍♀ woman in lotus position: medium-light skin tone +1F9D8 1F3FD 200D 2640 FE0F ; fully-qualified # 🧘🏽‍♀️ woman in lotus position: medium skin tone +1F9D8 1F3FD 200D 2640 ; non-fully-qualified # 🧘🏽‍♀ woman in lotus position: medium skin tone +1F9D8 1F3FE 200D 2640 FE0F ; fully-qualified # 🧘🏾‍♀️ woman in lotus position: medium-dark skin tone +1F9D8 1F3FE 200D 2640 ; non-fully-qualified # 🧘🏾‍♀ woman in lotus position: medium-dark skin tone +1F9D8 1F3FF 200D 2640 FE0F ; fully-qualified # 🧘🏿‍♀️ woman in lotus position: dark skin tone +1F9D8 1F3FF 200D 2640 ; non-fully-qualified # 🧘🏿‍♀ woman in lotus position: dark skin tone +1F9D8 200D 2642 FE0F ; fully-qualified # 🧘‍♂️ man in lotus position +1F9D8 200D 2642 ; non-fully-qualified # 🧘‍♂ man in lotus position +1F9D8 1F3FB 200D 2642 FE0F ; fully-qualified # 🧘🏻‍♂️ man in lotus position: light skin tone +1F9D8 1F3FB 200D 2642 ; non-fully-qualified # 🧘🏻‍♂ man in lotus position: light skin tone +1F9D8 1F3FC 200D 2642 FE0F ; fully-qualified # 🧘🏼‍♂️ man in lotus position: medium-light skin tone +1F9D8 1F3FC 200D 2642 ; non-fully-qualified # 🧘🏼‍♂ man in lotus position: medium-light skin tone +1F9D8 1F3FD 200D 2642 FE0F ; fully-qualified # 🧘🏽‍♂️ man in lotus position: medium skin tone +1F9D8 1F3FD 200D 2642 ; non-fully-qualified # 🧘🏽‍♂ man in lotus position: medium skin tone +1F9D8 1F3FE 200D 2642 FE0F ; fully-qualified # 🧘🏾‍♂️ man in lotus position: medium-dark skin tone +1F9D8 1F3FE 200D 2642 ; non-fully-qualified # 🧘🏾‍♂ man in lotus position: medium-dark skin tone +1F9D8 1F3FF 200D 2642 FE0F ; fully-qualified # 🧘🏿‍♂️ man in lotus position: dark skin tone +1F9D8 1F3FF 200D 2642 ; non-fully-qualified # 🧘🏿‍♂ man in lotus position: dark skin tone +1F6C0 ; fully-qualified # 🛀 person taking bath +1F6C0 1F3FB ; fully-qualified # 🛀🏻 person taking bath: light skin tone +1F6C0 1F3FC ; fully-qualified # 🛀🏼 person taking bath: medium-light skin tone +1F6C0 1F3FD ; fully-qualified # 🛀🏽 person taking bath: medium skin tone +1F6C0 1F3FE ; fully-qualified # 🛀🏾 person taking bath: medium-dark skin tone +1F6C0 1F3FF ; fully-qualified # 🛀🏿 person taking bath: dark skin tone +1F6CC ; fully-qualified # 🛌 person in bed +1F6CC 1F3FB ; fully-qualified # 🛌🏻 person in bed: light skin tone +1F6CC 1F3FC ; fully-qualified # 🛌🏼 person in bed: medium-light skin tone +1F6CC 1F3FD ; fully-qualified # 🛌🏽 person in bed: medium skin tone +1F6CC 1F3FE ; fully-qualified # 🛌🏾 person in bed: medium-dark skin tone +1F6CC 1F3FF ; fully-qualified # 🛌🏿 person in bed: dark skin tone +1F574 FE0F ; fully-qualified # 🕴️ man in suit levitating +1F574 ; non-fully-qualified # 🕴 man in suit levitating +1F574 1F3FB ; fully-qualified # 🕴🏻 man in suit levitating: light skin tone +1F574 1F3FC ; fully-qualified # 🕴🏼 man in suit levitating: medium-light skin tone +1F574 1F3FD ; fully-qualified # 🕴🏽 man in suit levitating: medium skin tone +1F574 1F3FE ; fully-qualified # 🕴🏾 man in suit levitating: medium-dark skin tone +1F574 1F3FF ; fully-qualified # 🕴🏿 man in suit levitating: dark skin tone +1F5E3 FE0F ; fully-qualified # 🗣️ speaking head +1F5E3 ; non-fully-qualified # 🗣 speaking head +1F464 ; fully-qualified # 👤 bust in silhouette +1F465 ; fully-qualified # 👥 busts in silhouette + +# subgroup: person-sport +1F93A ; fully-qualified # 🤺 person fencing +1F3C7 ; fully-qualified # 🏇 horse racing +1F3C7 1F3FB ; fully-qualified # 🏇🏻 horse racing: light skin tone +1F3C7 1F3FC ; fully-qualified # 🏇🏼 horse racing: medium-light skin tone +1F3C7 1F3FD ; fully-qualified # 🏇🏽 horse racing: medium skin tone +1F3C7 1F3FE ; fully-qualified # 🏇🏾 horse racing: medium-dark skin tone +1F3C7 1F3FF ; fully-qualified # 🏇🏿 horse racing: dark skin tone +26F7 FE0F ; fully-qualified # ⛷️ skier +26F7 ; non-fully-qualified # ⛷ skier +1F3C2 ; fully-qualified # 🏂 snowboarder +1F3C2 1F3FB ; fully-qualified # 🏂🏻 snowboarder: light skin tone +1F3C2 1F3FC ; fully-qualified # 🏂🏼 snowboarder: medium-light skin tone +1F3C2 1F3FD ; fully-qualified # 🏂🏽 snowboarder: medium skin tone +1F3C2 1F3FE ; fully-qualified # 🏂🏾 snowboarder: medium-dark skin tone +1F3C2 1F3FF ; fully-qualified # 🏂🏿 snowboarder: dark skin tone +1F3CC FE0F ; fully-qualified # 🏌️ person golfing +1F3CC ; non-fully-qualified # 🏌 person golfing +1F3CC 1F3FB ; fully-qualified # 🏌🏻 person golfing: light skin tone +1F3CC 1F3FC ; fully-qualified # 🏌🏼 person golfing: medium-light skin tone +1F3CC 1F3FD ; fully-qualified # 🏌🏽 person golfing: medium skin tone +1F3CC 1F3FE ; fully-qualified # 🏌🏾 person golfing: medium-dark skin tone +1F3CC 1F3FF ; fully-qualified # 🏌🏿 person golfing: dark skin tone +1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ man golfing +1F3CC 200D 2642 FE0F ; non-fully-qualified # 🏌‍♂️ man golfing +1F3CC FE0F 200D 2642 ; non-fully-qualified # 🏌️‍♂ man golfing +1F3CC 200D 2642 ; non-fully-qualified # 🏌‍♂ man golfing +1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ man golfing: light skin tone +1F3CC 1F3FB 200D 2642 ; non-fully-qualified # 🏌🏻‍♂ man golfing: light skin tone +1F3CC 1F3FC 200D 2642 FE0F ; fully-qualified # 🏌🏼‍♂️ man golfing: medium-light skin tone +1F3CC 1F3FC 200D 2642 ; non-fully-qualified # 🏌🏼‍♂ man golfing: medium-light skin tone +1F3CC 1F3FD 200D 2642 FE0F ; fully-qualified # 🏌🏽‍♂️ man golfing: medium skin tone +1F3CC 1F3FD 200D 2642 ; non-fully-qualified # 🏌🏽‍♂ man golfing: medium skin tone +1F3CC 1F3FE 200D 2642 FE0F ; fully-qualified # 🏌🏾‍♂️ man golfing: medium-dark skin tone +1F3CC 1F3FE 200D 2642 ; non-fully-qualified # 🏌🏾‍♂ man golfing: medium-dark skin tone +1F3CC 1F3FF 200D 2642 FE0F ; fully-qualified # 🏌🏿‍♂️ man golfing: dark skin tone +1F3CC 1F3FF 200D 2642 ; non-fully-qualified # 🏌🏿‍♂ man golfing: dark skin tone +1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ woman golfing +1F3CC 200D 2640 FE0F ; non-fully-qualified # 🏌‍♀️ woman golfing +1F3CC FE0F 200D 2640 ; non-fully-qualified # 🏌️‍♀ woman golfing +1F3CC 200D 2640 ; non-fully-qualified # 🏌‍♀ woman golfing +1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ woman golfing: light skin tone +1F3CC 1F3FB 200D 2640 ; non-fully-qualified # 🏌🏻‍♀ woman golfing: light skin tone +1F3CC 1F3FC 200D 2640 FE0F ; fully-qualified # 🏌🏼‍♀️ woman golfing: medium-light skin tone +1F3CC 1F3FC 200D 2640 ; non-fully-qualified # 🏌🏼‍♀ woman golfing: medium-light skin tone +1F3CC 1F3FD 200D 2640 FE0F ; fully-qualified # 🏌🏽‍♀️ woman golfing: medium skin tone +1F3CC 1F3FD 200D 2640 ; non-fully-qualified # 🏌🏽‍♀ woman golfing: medium skin tone +1F3CC 1F3FE 200D 2640 FE0F ; fully-qualified # 🏌🏾‍♀️ woman golfing: medium-dark skin tone +1F3CC 1F3FE 200D 2640 ; non-fully-qualified # 🏌🏾‍♀ woman golfing: medium-dark skin tone +1F3CC 1F3FF 200D 2640 FE0F ; fully-qualified # 🏌🏿‍♀️ woman golfing: dark skin tone +1F3CC 1F3FF 200D 2640 ; non-fully-qualified # 🏌🏿‍♀ woman golfing: dark skin tone +1F3C4 ; fully-qualified # 🏄 person surfing +1F3C4 1F3FB ; fully-qualified # 🏄🏻 person surfing: light skin tone +1F3C4 1F3FC ; fully-qualified # 🏄🏼 person surfing: medium-light skin tone +1F3C4 1F3FD ; fully-qualified # 🏄🏽 person surfing: medium skin tone +1F3C4 1F3FE ; fully-qualified # 🏄🏾 person surfing: medium-dark skin tone +1F3C4 1F3FF ; fully-qualified # 🏄🏿 person surfing: dark skin tone +1F3C4 200D 2642 FE0F ; fully-qualified # 🏄‍♂️ man surfing +1F3C4 200D 2642 ; non-fully-qualified # 🏄‍♂ man surfing +1F3C4 1F3FB 200D 2642 FE0F ; fully-qualified # 🏄🏻‍♂️ man surfing: light skin tone +1F3C4 1F3FB 200D 2642 ; non-fully-qualified # 🏄🏻‍♂ man surfing: light skin tone +1F3C4 1F3FC 200D 2642 FE0F ; fully-qualified # 🏄🏼‍♂️ man surfing: medium-light skin tone +1F3C4 1F3FC 200D 2642 ; non-fully-qualified # 🏄🏼‍♂ man surfing: medium-light skin tone +1F3C4 1F3FD 200D 2642 FE0F ; fully-qualified # 🏄🏽‍♂️ man surfing: medium skin tone +1F3C4 1F3FD 200D 2642 ; non-fully-qualified # 🏄🏽‍♂ man surfing: medium skin tone +1F3C4 1F3FE 200D 2642 FE0F ; fully-qualified # 🏄🏾‍♂️ man surfing: medium-dark skin tone +1F3C4 1F3FE 200D 2642 ; non-fully-qualified # 🏄🏾‍♂ man surfing: medium-dark skin tone +1F3C4 1F3FF 200D 2642 FE0F ; fully-qualified # 🏄🏿‍♂️ man surfing: dark skin tone +1F3C4 1F3FF 200D 2642 ; non-fully-qualified # 🏄🏿‍♂ man surfing: dark skin tone +1F3C4 200D 2640 FE0F ; fully-qualified # 🏄‍♀️ woman surfing +1F3C4 200D 2640 ; non-fully-qualified # 🏄‍♀ woman surfing +1F3C4 1F3FB 200D 2640 FE0F ; fully-qualified # 🏄🏻‍♀️ woman surfing: light skin tone +1F3C4 1F3FB 200D 2640 ; non-fully-qualified # 🏄🏻‍♀ woman surfing: light skin tone +1F3C4 1F3FC 200D 2640 FE0F ; fully-qualified # 🏄🏼‍♀️ woman surfing: medium-light skin tone +1F3C4 1F3FC 200D 2640 ; non-fully-qualified # 🏄🏼‍♀ woman surfing: medium-light skin tone +1F3C4 1F3FD 200D 2640 FE0F ; fully-qualified # 🏄🏽‍♀️ woman surfing: medium skin tone +1F3C4 1F3FD 200D 2640 ; non-fully-qualified # 🏄🏽‍♀ woman surfing: medium skin tone +1F3C4 1F3FE 200D 2640 FE0F ; fully-qualified # 🏄🏾‍♀️ woman surfing: medium-dark skin tone +1F3C4 1F3FE 200D 2640 ; non-fully-qualified # 🏄🏾‍♀ woman surfing: medium-dark skin tone +1F3C4 1F3FF 200D 2640 FE0F ; fully-qualified # 🏄🏿‍♀️ woman surfing: dark skin tone +1F3C4 1F3FF 200D 2640 ; non-fully-qualified # 🏄🏿‍♀ woman surfing: dark skin tone +1F6A3 ; fully-qualified # 🚣 person rowing boat +1F6A3 1F3FB ; fully-qualified # 🚣🏻 person rowing boat: light skin tone +1F6A3 1F3FC ; fully-qualified # 🚣🏼 person rowing boat: medium-light skin tone +1F6A3 1F3FD ; fully-qualified # 🚣🏽 person rowing boat: medium skin tone +1F6A3 1F3FE ; fully-qualified # 🚣🏾 person rowing boat: medium-dark skin tone +1F6A3 1F3FF ; fully-qualified # 🚣🏿 person rowing boat: dark skin tone +1F6A3 200D 2642 FE0F ; fully-qualified # 🚣‍♂️ man rowing boat +1F6A3 200D 2642 ; non-fully-qualified # 🚣‍♂ man rowing boat +1F6A3 1F3FB 200D 2642 FE0F ; fully-qualified # 🚣🏻‍♂️ man rowing boat: light skin tone +1F6A3 1F3FB 200D 2642 ; non-fully-qualified # 🚣🏻‍♂ man rowing boat: light skin tone +1F6A3 1F3FC 200D 2642 FE0F ; fully-qualified # 🚣🏼‍♂️ man rowing boat: medium-light skin tone +1F6A3 1F3FC 200D 2642 ; non-fully-qualified # 🚣🏼‍♂ man rowing boat: medium-light skin tone +1F6A3 1F3FD 200D 2642 FE0F ; fully-qualified # 🚣🏽‍♂️ man rowing boat: medium skin tone +1F6A3 1F3FD 200D 2642 ; non-fully-qualified # 🚣🏽‍♂ man rowing boat: medium skin tone +1F6A3 1F3FE 200D 2642 FE0F ; fully-qualified # 🚣🏾‍♂️ man rowing boat: medium-dark skin tone +1F6A3 1F3FE 200D 2642 ; non-fully-qualified # 🚣🏾‍♂ man rowing boat: medium-dark skin tone +1F6A3 1F3FF 200D 2642 FE0F ; fully-qualified # 🚣🏿‍♂️ man rowing boat: dark skin tone +1F6A3 1F3FF 200D 2642 ; non-fully-qualified # 🚣🏿‍♂ man rowing boat: dark skin tone +1F6A3 200D 2640 FE0F ; fully-qualified # 🚣‍♀️ woman rowing boat +1F6A3 200D 2640 ; non-fully-qualified # 🚣‍♀ woman rowing boat +1F6A3 1F3FB 200D 2640 FE0F ; fully-qualified # 🚣🏻‍♀️ woman rowing boat: light skin tone +1F6A3 1F3FB 200D 2640 ; non-fully-qualified # 🚣🏻‍♀ woman rowing boat: light skin tone +1F6A3 1F3FC 200D 2640 FE0F ; fully-qualified # 🚣🏼‍♀️ woman rowing boat: medium-light skin tone +1F6A3 1F3FC 200D 2640 ; non-fully-qualified # 🚣🏼‍♀ woman rowing boat: medium-light skin tone +1F6A3 1F3FD 200D 2640 FE0F ; fully-qualified # 🚣🏽‍♀️ woman rowing boat: medium skin tone +1F6A3 1F3FD 200D 2640 ; non-fully-qualified # 🚣🏽‍♀ woman rowing boat: medium skin tone +1F6A3 1F3FE 200D 2640 FE0F ; fully-qualified # 🚣🏾‍♀️ woman rowing boat: medium-dark skin tone +1F6A3 1F3FE 200D 2640 ; non-fully-qualified # 🚣🏾‍♀ woman rowing boat: medium-dark skin tone +1F6A3 1F3FF 200D 2640 FE0F ; fully-qualified # 🚣🏿‍♀️ woman rowing boat: dark skin tone +1F6A3 1F3FF 200D 2640 ; non-fully-qualified # 🚣🏿‍♀ woman rowing boat: dark skin tone +1F3CA ; fully-qualified # 🏊 person swimming +1F3CA 1F3FB ; fully-qualified # 🏊🏻 person swimming: light skin tone +1F3CA 1F3FC ; fully-qualified # 🏊🏼 person swimming: medium-light skin tone +1F3CA 1F3FD ; fully-qualified # 🏊🏽 person swimming: medium skin tone +1F3CA 1F3FE ; fully-qualified # 🏊🏾 person swimming: medium-dark skin tone +1F3CA 1F3FF ; fully-qualified # 🏊🏿 person swimming: dark skin tone +1F3CA 200D 2642 FE0F ; fully-qualified # 🏊‍♂️ man swimming +1F3CA 200D 2642 ; non-fully-qualified # 🏊‍♂ man swimming +1F3CA 1F3FB 200D 2642 FE0F ; fully-qualified # 🏊🏻‍♂️ man swimming: light skin tone +1F3CA 1F3FB 200D 2642 ; non-fully-qualified # 🏊🏻‍♂ man swimming: light skin tone +1F3CA 1F3FC 200D 2642 FE0F ; fully-qualified # 🏊🏼‍♂️ man swimming: medium-light skin tone +1F3CA 1F3FC 200D 2642 ; non-fully-qualified # 🏊🏼‍♂ man swimming: medium-light skin tone +1F3CA 1F3FD 200D 2642 FE0F ; fully-qualified # 🏊🏽‍♂️ man swimming: medium skin tone +1F3CA 1F3FD 200D 2642 ; non-fully-qualified # 🏊🏽‍♂ man swimming: medium skin tone +1F3CA 1F3FE 200D 2642 FE0F ; fully-qualified # 🏊🏾‍♂️ man swimming: medium-dark skin tone +1F3CA 1F3FE 200D 2642 ; non-fully-qualified # 🏊🏾‍♂ man swimming: medium-dark skin tone +1F3CA 1F3FF 200D 2642 FE0F ; fully-qualified # 🏊🏿‍♂️ man swimming: dark skin tone +1F3CA 1F3FF 200D 2642 ; non-fully-qualified # 🏊🏿‍♂ man swimming: dark skin tone +1F3CA 200D 2640 FE0F ; fully-qualified # 🏊‍♀️ woman swimming +1F3CA 200D 2640 ; non-fully-qualified # 🏊‍♀ woman swimming +1F3CA 1F3FB 200D 2640 FE0F ; fully-qualified # 🏊🏻‍♀️ woman swimming: light skin tone +1F3CA 1F3FB 200D 2640 ; non-fully-qualified # 🏊🏻‍♀ woman swimming: light skin tone +1F3CA 1F3FC 200D 2640 FE0F ; fully-qualified # 🏊🏼‍♀️ woman swimming: medium-light skin tone +1F3CA 1F3FC 200D 2640 ; non-fully-qualified # 🏊🏼‍♀ woman swimming: medium-light skin tone +1F3CA 1F3FD 200D 2640 FE0F ; fully-qualified # 🏊🏽‍♀️ woman swimming: medium skin tone +1F3CA 1F3FD 200D 2640 ; non-fully-qualified # 🏊🏽‍♀ woman swimming: medium skin tone +1F3CA 1F3FE 200D 2640 FE0F ; fully-qualified # 🏊🏾‍♀️ woman swimming: medium-dark skin tone +1F3CA 1F3FE 200D 2640 ; non-fully-qualified # 🏊🏾‍♀ woman swimming: medium-dark skin tone +1F3CA 1F3FF 200D 2640 FE0F ; fully-qualified # 🏊🏿‍♀️ woman swimming: dark skin tone +1F3CA 1F3FF 200D 2640 ; non-fully-qualified # 🏊🏿‍♀ woman swimming: dark skin tone +26F9 FE0F ; fully-qualified # ⛹️ person bouncing ball +26F9 ; non-fully-qualified # ⛹ person bouncing ball +26F9 1F3FB ; fully-qualified # ⛹🏻 person bouncing ball: light skin tone +26F9 1F3FC ; fully-qualified # ⛹🏼 person bouncing ball: medium-light skin tone +26F9 1F3FD ; fully-qualified # ⛹🏽 person bouncing ball: medium skin tone +26F9 1F3FE ; fully-qualified # ⛹🏾 person bouncing ball: medium-dark skin tone +26F9 1F3FF ; fully-qualified # ⛹🏿 person bouncing ball: dark skin tone +26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ man bouncing ball +26F9 200D 2642 FE0F ; non-fully-qualified # ⛹‍♂️ man bouncing ball +26F9 FE0F 200D 2642 ; non-fully-qualified # ⛹️‍♂ man bouncing ball +26F9 200D 2642 ; non-fully-qualified # ⛹‍♂ man bouncing ball +26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ man bouncing ball: light skin tone +26F9 1F3FB 200D 2642 ; non-fully-qualified # ⛹🏻‍♂ man bouncing ball: light skin tone +26F9 1F3FC 200D 2642 FE0F ; fully-qualified # ⛹🏼‍♂️ man bouncing ball: medium-light skin tone +26F9 1F3FC 200D 2642 ; non-fully-qualified # ⛹🏼‍♂ man bouncing ball: medium-light skin tone +26F9 1F3FD 200D 2642 FE0F ; fully-qualified # ⛹🏽‍♂️ man bouncing ball: medium skin tone +26F9 1F3FD 200D 2642 ; non-fully-qualified # ⛹🏽‍♂ man bouncing ball: medium skin tone +26F9 1F3FE 200D 2642 FE0F ; fully-qualified # ⛹🏾‍♂️ man bouncing ball: medium-dark skin tone +26F9 1F3FE 200D 2642 ; non-fully-qualified # ⛹🏾‍♂ man bouncing ball: medium-dark skin tone +26F9 1F3FF 200D 2642 FE0F ; fully-qualified # ⛹🏿‍♂️ man bouncing ball: dark skin tone +26F9 1F3FF 200D 2642 ; non-fully-qualified # ⛹🏿‍♂ man bouncing ball: dark skin tone +26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ woman bouncing ball +26F9 200D 2640 FE0F ; non-fully-qualified # ⛹‍♀️ woman bouncing ball +26F9 FE0F 200D 2640 ; non-fully-qualified # ⛹️‍♀ woman bouncing ball +26F9 200D 2640 ; non-fully-qualified # ⛹‍♀ woman bouncing ball +26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ woman bouncing ball: light skin tone +26F9 1F3FB 200D 2640 ; non-fully-qualified # ⛹🏻‍♀ woman bouncing ball: light skin tone +26F9 1F3FC 200D 2640 FE0F ; fully-qualified # ⛹🏼‍♀️ woman bouncing ball: medium-light skin tone +26F9 1F3FC 200D 2640 ; non-fully-qualified # ⛹🏼‍♀ woman bouncing ball: medium-light skin tone +26F9 1F3FD 200D 2640 FE0F ; fully-qualified # ⛹🏽‍♀️ woman bouncing ball: medium skin tone +26F9 1F3FD 200D 2640 ; non-fully-qualified # ⛹🏽‍♀ woman bouncing ball: medium skin tone +26F9 1F3FE 200D 2640 FE0F ; fully-qualified # ⛹🏾‍♀️ woman bouncing ball: medium-dark skin tone +26F9 1F3FE 200D 2640 ; non-fully-qualified # ⛹🏾‍♀ woman bouncing ball: medium-dark skin tone +26F9 1F3FF 200D 2640 FE0F ; fully-qualified # ⛹🏿‍♀️ woman bouncing ball: dark skin tone +26F9 1F3FF 200D 2640 ; non-fully-qualified # ⛹🏿‍♀ woman bouncing ball: dark skin tone +1F3CB FE0F ; fully-qualified # 🏋️ person lifting weights +1F3CB ; non-fully-qualified # 🏋 person lifting weights +1F3CB 1F3FB ; fully-qualified # 🏋🏻 person lifting weights: light skin tone +1F3CB 1F3FC ; fully-qualified # 🏋🏼 person lifting weights: medium-light skin tone +1F3CB 1F3FD ; fully-qualified # 🏋🏽 person lifting weights: medium skin tone +1F3CB 1F3FE ; fully-qualified # 🏋🏾 person lifting weights: medium-dark skin tone +1F3CB 1F3FF ; fully-qualified # 🏋🏿 person lifting weights: dark skin tone +1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ man lifting weights +1F3CB 200D 2642 FE0F ; non-fully-qualified # 🏋‍♂️ man lifting weights +1F3CB FE0F 200D 2642 ; non-fully-qualified # 🏋️‍♂ man lifting weights +1F3CB 200D 2642 ; non-fully-qualified # 🏋‍♂ man lifting weights +1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ man lifting weights: light skin tone +1F3CB 1F3FB 200D 2642 ; non-fully-qualified # 🏋🏻‍♂ man lifting weights: light skin tone +1F3CB 1F3FC 200D 2642 FE0F ; fully-qualified # 🏋🏼‍♂️ man lifting weights: medium-light skin tone +1F3CB 1F3FC 200D 2642 ; non-fully-qualified # 🏋🏼‍♂ man lifting weights: medium-light skin tone +1F3CB 1F3FD 200D 2642 FE0F ; fully-qualified # 🏋🏽‍♂️ man lifting weights: medium skin tone +1F3CB 1F3FD 200D 2642 ; non-fully-qualified # 🏋🏽‍♂ man lifting weights: medium skin tone +1F3CB 1F3FE 200D 2642 FE0F ; fully-qualified # 🏋🏾‍♂️ man lifting weights: medium-dark skin tone +1F3CB 1F3FE 200D 2642 ; non-fully-qualified # 🏋🏾‍♂ man lifting weights: medium-dark skin tone +1F3CB 1F3FF 200D 2642 FE0F ; fully-qualified # 🏋🏿‍♂️ man lifting weights: dark skin tone +1F3CB 1F3FF 200D 2642 ; non-fully-qualified # 🏋🏿‍♂ man lifting weights: dark skin tone +1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ woman lifting weights +1F3CB 200D 2640 FE0F ; non-fully-qualified # 🏋‍♀️ woman lifting weights +1F3CB FE0F 200D 2640 ; non-fully-qualified # 🏋️‍♀ woman lifting weights +1F3CB 200D 2640 ; non-fully-qualified # 🏋‍♀ woman lifting weights +1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ woman lifting weights: light skin tone +1F3CB 1F3FB 200D 2640 ; non-fully-qualified # 🏋🏻‍♀ woman lifting weights: light skin tone +1F3CB 1F3FC 200D 2640 FE0F ; fully-qualified # 🏋🏼‍♀️ woman lifting weights: medium-light skin tone +1F3CB 1F3FC 200D 2640 ; non-fully-qualified # 🏋🏼‍♀ woman lifting weights: medium-light skin tone +1F3CB 1F3FD 200D 2640 FE0F ; fully-qualified # 🏋🏽‍♀️ woman lifting weights: medium skin tone +1F3CB 1F3FD 200D 2640 ; non-fully-qualified # 🏋🏽‍♀ woman lifting weights: medium skin tone +1F3CB 1F3FE 200D 2640 FE0F ; fully-qualified # 🏋🏾‍♀️ woman lifting weights: medium-dark skin tone +1F3CB 1F3FE 200D 2640 ; non-fully-qualified # 🏋🏾‍♀ woman lifting weights: medium-dark skin tone +1F3CB 1F3FF 200D 2640 FE0F ; fully-qualified # 🏋🏿‍♀️ woman lifting weights: dark skin tone +1F3CB 1F3FF 200D 2640 ; non-fully-qualified # 🏋🏿‍♀ woman lifting weights: dark skin tone +1F6B4 ; fully-qualified # 🚴 person biking +1F6B4 1F3FB ; fully-qualified # 🚴🏻 person biking: light skin tone +1F6B4 1F3FC ; fully-qualified # 🚴🏼 person biking: medium-light skin tone +1F6B4 1F3FD ; fully-qualified # 🚴🏽 person biking: medium skin tone +1F6B4 1F3FE ; fully-qualified # 🚴🏾 person biking: medium-dark skin tone +1F6B4 1F3FF ; fully-qualified # 🚴🏿 person biking: dark skin tone +1F6B4 200D 2642 FE0F ; fully-qualified # 🚴‍♂️ man biking +1F6B4 200D 2642 ; non-fully-qualified # 🚴‍♂ man biking +1F6B4 1F3FB 200D 2642 FE0F ; fully-qualified # 🚴🏻‍♂️ man biking: light skin tone +1F6B4 1F3FB 200D 2642 ; non-fully-qualified # 🚴🏻‍♂ man biking: light skin tone +1F6B4 1F3FC 200D 2642 FE0F ; fully-qualified # 🚴🏼‍♂️ man biking: medium-light skin tone +1F6B4 1F3FC 200D 2642 ; non-fully-qualified # 🚴🏼‍♂ man biking: medium-light skin tone +1F6B4 1F3FD 200D 2642 FE0F ; fully-qualified # 🚴🏽‍♂️ man biking: medium skin tone +1F6B4 1F3FD 200D 2642 ; non-fully-qualified # 🚴🏽‍♂ man biking: medium skin tone +1F6B4 1F3FE 200D 2642 FE0F ; fully-qualified # 🚴🏾‍♂️ man biking: medium-dark skin tone +1F6B4 1F3FE 200D 2642 ; non-fully-qualified # 🚴🏾‍♂ man biking: medium-dark skin tone +1F6B4 1F3FF 200D 2642 FE0F ; fully-qualified # 🚴🏿‍♂️ man biking: dark skin tone +1F6B4 1F3FF 200D 2642 ; non-fully-qualified # 🚴🏿‍♂ man biking: dark skin tone +1F6B4 200D 2640 FE0F ; fully-qualified # 🚴‍♀️ woman biking +1F6B4 200D 2640 ; non-fully-qualified # 🚴‍♀ woman biking +1F6B4 1F3FB 200D 2640 FE0F ; fully-qualified # 🚴🏻‍♀️ woman biking: light skin tone +1F6B4 1F3FB 200D 2640 ; non-fully-qualified # 🚴🏻‍♀ woman biking: light skin tone +1F6B4 1F3FC 200D 2640 FE0F ; fully-qualified # 🚴🏼‍♀️ woman biking: medium-light skin tone +1F6B4 1F3FC 200D 2640 ; non-fully-qualified # 🚴🏼‍♀ woman biking: medium-light skin tone +1F6B4 1F3FD 200D 2640 FE0F ; fully-qualified # 🚴🏽‍♀️ woman biking: medium skin tone +1F6B4 1F3FD 200D 2640 ; non-fully-qualified # 🚴🏽‍♀ woman biking: medium skin tone +1F6B4 1F3FE 200D 2640 FE0F ; fully-qualified # 🚴🏾‍♀️ woman biking: medium-dark skin tone +1F6B4 1F3FE 200D 2640 ; non-fully-qualified # 🚴🏾‍♀ woman biking: medium-dark skin tone +1F6B4 1F3FF 200D 2640 FE0F ; fully-qualified # 🚴🏿‍♀️ woman biking: dark skin tone +1F6B4 1F3FF 200D 2640 ; non-fully-qualified # 🚴🏿‍♀ woman biking: dark skin tone +1F6B5 ; fully-qualified # 🚵 person mountain biking +1F6B5 1F3FB ; fully-qualified # 🚵🏻 person mountain biking: light skin tone +1F6B5 1F3FC ; fully-qualified # 🚵🏼 person mountain biking: medium-light skin tone +1F6B5 1F3FD ; fully-qualified # 🚵🏽 person mountain biking: medium skin tone +1F6B5 1F3FE ; fully-qualified # 🚵🏾 person mountain biking: medium-dark skin tone +1F6B5 1F3FF ; fully-qualified # 🚵🏿 person mountain biking: dark skin tone +1F6B5 200D 2642 FE0F ; fully-qualified # 🚵‍♂️ man mountain biking +1F6B5 200D 2642 ; non-fully-qualified # 🚵‍♂ man mountain biking +1F6B5 1F3FB 200D 2642 FE0F ; fully-qualified # 🚵🏻‍♂️ man mountain biking: light skin tone +1F6B5 1F3FB 200D 2642 ; non-fully-qualified # 🚵🏻‍♂ man mountain biking: light skin tone +1F6B5 1F3FC 200D 2642 FE0F ; fully-qualified # 🚵🏼‍♂️ man mountain biking: medium-light skin tone +1F6B5 1F3FC 200D 2642 ; non-fully-qualified # 🚵🏼‍♂ man mountain biking: medium-light skin tone +1F6B5 1F3FD 200D 2642 FE0F ; fully-qualified # 🚵🏽‍♂️ man mountain biking: medium skin tone +1F6B5 1F3FD 200D 2642 ; non-fully-qualified # 🚵🏽‍♂ man mountain biking: medium skin tone +1F6B5 1F3FE 200D 2642 FE0F ; fully-qualified # 🚵🏾‍♂️ man mountain biking: medium-dark skin tone +1F6B5 1F3FE 200D 2642 ; non-fully-qualified # 🚵🏾‍♂ man mountain biking: medium-dark skin tone +1F6B5 1F3FF 200D 2642 FE0F ; fully-qualified # 🚵🏿‍♂️ man mountain biking: dark skin tone +1F6B5 1F3FF 200D 2642 ; non-fully-qualified # 🚵🏿‍♂ man mountain biking: dark skin tone +1F6B5 200D 2640 FE0F ; fully-qualified # 🚵‍♀️ woman mountain biking +1F6B5 200D 2640 ; non-fully-qualified # 🚵‍♀ woman mountain biking +1F6B5 1F3FB 200D 2640 FE0F ; fully-qualified # 🚵🏻‍♀️ woman mountain biking: light skin tone +1F6B5 1F3FB 200D 2640 ; non-fully-qualified # 🚵🏻‍♀ woman mountain biking: light skin tone +1F6B5 1F3FC 200D 2640 FE0F ; fully-qualified # 🚵🏼‍♀️ woman mountain biking: medium-light skin tone +1F6B5 1F3FC 200D 2640 ; non-fully-qualified # 🚵🏼‍♀ woman mountain biking: medium-light skin tone +1F6B5 1F3FD 200D 2640 FE0F ; fully-qualified # 🚵🏽‍♀️ woman mountain biking: medium skin tone +1F6B5 1F3FD 200D 2640 ; non-fully-qualified # 🚵🏽‍♀ woman mountain biking: medium skin tone +1F6B5 1F3FE 200D 2640 FE0F ; fully-qualified # 🚵🏾‍♀️ woman mountain biking: medium-dark skin tone +1F6B5 1F3FE 200D 2640 ; non-fully-qualified # 🚵🏾‍♀ woman mountain biking: medium-dark skin tone +1F6B5 1F3FF 200D 2640 FE0F ; fully-qualified # 🚵🏿‍♀️ woman mountain biking: dark skin tone +1F6B5 1F3FF 200D 2640 ; non-fully-qualified # 🚵🏿‍♀ woman mountain biking: dark skin tone +1F3CE FE0F ; fully-qualified # 🏎️ racing car +1F3CE ; non-fully-qualified # 🏎 racing car +1F3CD FE0F ; fully-qualified # 🏍️ motorcycle +1F3CD ; non-fully-qualified # 🏍 motorcycle +1F938 ; fully-qualified # 🤸 person cartwheeling +1F938 1F3FB ; fully-qualified # 🤸🏻 person cartwheeling: light skin tone +1F938 1F3FC ; fully-qualified # 🤸🏼 person cartwheeling: medium-light skin tone +1F938 1F3FD ; fully-qualified # 🤸🏽 person cartwheeling: medium skin tone +1F938 1F3FE ; fully-qualified # 🤸🏾 person cartwheeling: medium-dark skin tone +1F938 1F3FF ; fully-qualified # 🤸🏿 person cartwheeling: dark skin tone +1F938 200D 2642 FE0F ; fully-qualified # 🤸‍♂️ man cartwheeling +1F938 200D 2642 ; non-fully-qualified # 🤸‍♂ man cartwheeling +1F938 1F3FB 200D 2642 FE0F ; fully-qualified # 🤸🏻‍♂️ man cartwheeling: light skin tone +1F938 1F3FB 200D 2642 ; non-fully-qualified # 🤸🏻‍♂ man cartwheeling: light skin tone +1F938 1F3FC 200D 2642 FE0F ; fully-qualified # 🤸🏼‍♂️ man cartwheeling: medium-light skin tone +1F938 1F3FC 200D 2642 ; non-fully-qualified # 🤸🏼‍♂ man cartwheeling: medium-light skin tone +1F938 1F3FD 200D 2642 FE0F ; fully-qualified # 🤸🏽‍♂️ man cartwheeling: medium skin tone +1F938 1F3FD 200D 2642 ; non-fully-qualified # 🤸🏽‍♂ man cartwheeling: medium skin tone +1F938 1F3FE 200D 2642 FE0F ; fully-qualified # 🤸🏾‍♂️ man cartwheeling: medium-dark skin tone +1F938 1F3FE 200D 2642 ; non-fully-qualified # 🤸🏾‍♂ man cartwheeling: medium-dark skin tone +1F938 1F3FF 200D 2642 FE0F ; fully-qualified # 🤸🏿‍♂️ man cartwheeling: dark skin tone +1F938 1F3FF 200D 2642 ; non-fully-qualified # 🤸🏿‍♂ man cartwheeling: dark skin tone +1F938 200D 2640 FE0F ; fully-qualified # 🤸‍♀️ woman cartwheeling +1F938 200D 2640 ; non-fully-qualified # 🤸‍♀ woman cartwheeling +1F938 1F3FB 200D 2640 FE0F ; fully-qualified # 🤸🏻‍♀️ woman cartwheeling: light skin tone +1F938 1F3FB 200D 2640 ; non-fully-qualified # 🤸🏻‍♀ woman cartwheeling: light skin tone +1F938 1F3FC 200D 2640 FE0F ; fully-qualified # 🤸🏼‍♀️ woman cartwheeling: medium-light skin tone +1F938 1F3FC 200D 2640 ; non-fully-qualified # 🤸🏼‍♀ woman cartwheeling: medium-light skin tone +1F938 1F3FD 200D 2640 FE0F ; fully-qualified # 🤸🏽‍♀️ woman cartwheeling: medium skin tone +1F938 1F3FD 200D 2640 ; non-fully-qualified # 🤸🏽‍♀ woman cartwheeling: medium skin tone +1F938 1F3FE 200D 2640 FE0F ; fully-qualified # 🤸🏾‍♀️ woman cartwheeling: medium-dark skin tone +1F938 1F3FE 200D 2640 ; non-fully-qualified # 🤸🏾‍♀ woman cartwheeling: medium-dark skin tone +1F938 1F3FF 200D 2640 FE0F ; fully-qualified # 🤸🏿‍♀️ woman cartwheeling: dark skin tone +1F938 1F3FF 200D 2640 ; non-fully-qualified # 🤸🏿‍♀ woman cartwheeling: dark skin tone +1F93C ; fully-qualified # 🤼 people wrestling +1F93C 200D 2642 FE0F ; fully-qualified # 🤼‍♂️ men wrestling +1F93C 200D 2642 ; non-fully-qualified # 🤼‍♂ men wrestling +1F93C 200D 2640 FE0F ; fully-qualified # 🤼‍♀️ women wrestling +1F93C 200D 2640 ; non-fully-qualified # 🤼‍♀ women wrestling +1F93D ; fully-qualified # 🤽 person playing water polo +1F93D 1F3FB ; fully-qualified # 🤽🏻 person playing water polo: light skin tone +1F93D 1F3FC ; fully-qualified # 🤽🏼 person playing water polo: medium-light skin tone +1F93D 1F3FD ; fully-qualified # 🤽🏽 person playing water polo: medium skin tone +1F93D 1F3FE ; fully-qualified # 🤽🏾 person playing water polo: medium-dark skin tone +1F93D 1F3FF ; fully-qualified # 🤽🏿 person playing water polo: dark skin tone +1F93D 200D 2642 FE0F ; fully-qualified # 🤽‍♂️ man playing water polo +1F93D 200D 2642 ; non-fully-qualified # 🤽‍♂ man playing water polo +1F93D 1F3FB 200D 2642 FE0F ; fully-qualified # 🤽🏻‍♂️ man playing water polo: light skin tone +1F93D 1F3FB 200D 2642 ; non-fully-qualified # 🤽🏻‍♂ man playing water polo: light skin tone +1F93D 1F3FC 200D 2642 FE0F ; fully-qualified # 🤽🏼‍♂️ man playing water polo: medium-light skin tone +1F93D 1F3FC 200D 2642 ; non-fully-qualified # 🤽🏼‍♂ man playing water polo: medium-light skin tone +1F93D 1F3FD 200D 2642 FE0F ; fully-qualified # 🤽🏽‍♂️ man playing water polo: medium skin tone +1F93D 1F3FD 200D 2642 ; non-fully-qualified # 🤽🏽‍♂ man playing water polo: medium skin tone +1F93D 1F3FE 200D 2642 FE0F ; fully-qualified # 🤽🏾‍♂️ man playing water polo: medium-dark skin tone +1F93D 1F3FE 200D 2642 ; non-fully-qualified # 🤽🏾‍♂ man playing water polo: medium-dark skin tone +1F93D 1F3FF 200D 2642 FE0F ; fully-qualified # 🤽🏿‍♂️ man playing water polo: dark skin tone +1F93D 1F3FF 200D 2642 ; non-fully-qualified # 🤽🏿‍♂ man playing water polo: dark skin tone +1F93D 200D 2640 FE0F ; fully-qualified # 🤽‍♀️ woman playing water polo +1F93D 200D 2640 ; non-fully-qualified # 🤽‍♀ woman playing water polo +1F93D 1F3FB 200D 2640 FE0F ; fully-qualified # 🤽🏻‍♀️ woman playing water polo: light skin tone +1F93D 1F3FB 200D 2640 ; non-fully-qualified # 🤽🏻‍♀ woman playing water polo: light skin tone +1F93D 1F3FC 200D 2640 FE0F ; fully-qualified # 🤽🏼‍♀️ woman playing water polo: medium-light skin tone +1F93D 1F3FC 200D 2640 ; non-fully-qualified # 🤽🏼‍♀ woman playing water polo: medium-light skin tone +1F93D 1F3FD 200D 2640 FE0F ; fully-qualified # 🤽🏽‍♀️ woman playing water polo: medium skin tone +1F93D 1F3FD 200D 2640 ; non-fully-qualified # 🤽🏽‍♀ woman playing water polo: medium skin tone +1F93D 1F3FE 200D 2640 FE0F ; fully-qualified # 🤽🏾‍♀️ woman playing water polo: medium-dark skin tone +1F93D 1F3FE 200D 2640 ; non-fully-qualified # 🤽🏾‍♀ woman playing water polo: medium-dark skin tone +1F93D 1F3FF 200D 2640 FE0F ; fully-qualified # 🤽🏿‍♀️ woman playing water polo: dark skin tone +1F93D 1F3FF 200D 2640 ; non-fully-qualified # 🤽🏿‍♀ woman playing water polo: dark skin tone +1F93E ; fully-qualified # 🤾 person playing handball +1F93E 1F3FB ; fully-qualified # 🤾🏻 person playing handball: light skin tone +1F93E 1F3FC ; fully-qualified # 🤾🏼 person playing handball: medium-light skin tone +1F93E 1F3FD ; fully-qualified # 🤾🏽 person playing handball: medium skin tone +1F93E 1F3FE ; fully-qualified # 🤾🏾 person playing handball: medium-dark skin tone +1F93E 1F3FF ; fully-qualified # 🤾🏿 person playing handball: dark skin tone +1F93E 200D 2642 FE0F ; fully-qualified # 🤾‍♂️ man playing handball +1F93E 200D 2642 ; non-fully-qualified # 🤾‍♂ man playing handball +1F93E 1F3FB 200D 2642 FE0F ; fully-qualified # 🤾🏻‍♂️ man playing handball: light skin tone +1F93E 1F3FB 200D 2642 ; non-fully-qualified # 🤾🏻‍♂ man playing handball: light skin tone +1F93E 1F3FC 200D 2642 FE0F ; fully-qualified # 🤾🏼‍♂️ man playing handball: medium-light skin tone +1F93E 1F3FC 200D 2642 ; non-fully-qualified # 🤾🏼‍♂ man playing handball: medium-light skin tone +1F93E 1F3FD 200D 2642 FE0F ; fully-qualified # 🤾🏽‍♂️ man playing handball: medium skin tone +1F93E 1F3FD 200D 2642 ; non-fully-qualified # 🤾🏽‍♂ man playing handball: medium skin tone +1F93E 1F3FE 200D 2642 FE0F ; fully-qualified # 🤾🏾‍♂️ man playing handball: medium-dark skin tone +1F93E 1F3FE 200D 2642 ; non-fully-qualified # 🤾🏾‍♂ man playing handball: medium-dark skin tone +1F93E 1F3FF 200D 2642 FE0F ; fully-qualified # 🤾🏿‍♂️ man playing handball: dark skin tone +1F93E 1F3FF 200D 2642 ; non-fully-qualified # 🤾🏿‍♂ man playing handball: dark skin tone +1F93E 200D 2640 FE0F ; fully-qualified # 🤾‍♀️ woman playing handball +1F93E 200D 2640 ; non-fully-qualified # 🤾‍♀ woman playing handball +1F93E 1F3FB 200D 2640 FE0F ; fully-qualified # 🤾🏻‍♀️ woman playing handball: light skin tone +1F93E 1F3FB 200D 2640 ; non-fully-qualified # 🤾🏻‍♀ woman playing handball: light skin tone +1F93E 1F3FC 200D 2640 FE0F ; fully-qualified # 🤾🏼‍♀️ woman playing handball: medium-light skin tone +1F93E 1F3FC 200D 2640 ; non-fully-qualified # 🤾🏼‍♀ woman playing handball: medium-light skin tone +1F93E 1F3FD 200D 2640 FE0F ; fully-qualified # 🤾🏽‍♀️ woman playing handball: medium skin tone +1F93E 1F3FD 200D 2640 ; non-fully-qualified # 🤾🏽‍♀ woman playing handball: medium skin tone +1F93E 1F3FE 200D 2640 FE0F ; fully-qualified # 🤾🏾‍♀️ woman playing handball: medium-dark skin tone +1F93E 1F3FE 200D 2640 ; non-fully-qualified # 🤾🏾‍♀ woman playing handball: medium-dark skin tone +1F93E 1F3FF 200D 2640 FE0F ; fully-qualified # 🤾🏿‍♀️ woman playing handball: dark skin tone +1F93E 1F3FF 200D 2640 ; non-fully-qualified # 🤾🏿‍♀ woman playing handball: dark skin tone +1F939 ; fully-qualified # 🤹 person juggling +1F939 1F3FB ; fully-qualified # 🤹🏻 person juggling: light skin tone +1F939 1F3FC ; fully-qualified # 🤹🏼 person juggling: medium-light skin tone +1F939 1F3FD ; fully-qualified # 🤹🏽 person juggling: medium skin tone +1F939 1F3FE ; fully-qualified # 🤹🏾 person juggling: medium-dark skin tone +1F939 1F3FF ; fully-qualified # 🤹🏿 person juggling: dark skin tone +1F939 200D 2642 FE0F ; fully-qualified # 🤹‍♂️ man juggling +1F939 200D 2642 ; non-fully-qualified # 🤹‍♂ man juggling +1F939 1F3FB 200D 2642 FE0F ; fully-qualified # 🤹🏻‍♂️ man juggling: light skin tone +1F939 1F3FB 200D 2642 ; non-fully-qualified # 🤹🏻‍♂ man juggling: light skin tone +1F939 1F3FC 200D 2642 FE0F ; fully-qualified # 🤹🏼‍♂️ man juggling: medium-light skin tone +1F939 1F3FC 200D 2642 ; non-fully-qualified # 🤹🏼‍♂ man juggling: medium-light skin tone +1F939 1F3FD 200D 2642 FE0F ; fully-qualified # 🤹🏽‍♂️ man juggling: medium skin tone +1F939 1F3FD 200D 2642 ; non-fully-qualified # 🤹🏽‍♂ man juggling: medium skin tone +1F939 1F3FE 200D 2642 FE0F ; fully-qualified # 🤹🏾‍♂️ man juggling: medium-dark skin tone +1F939 1F3FE 200D 2642 ; non-fully-qualified # 🤹🏾‍♂ man juggling: medium-dark skin tone +1F939 1F3FF 200D 2642 FE0F ; fully-qualified # 🤹🏿‍♂️ man juggling: dark skin tone +1F939 1F3FF 200D 2642 ; non-fully-qualified # 🤹🏿‍♂ man juggling: dark skin tone +1F939 200D 2640 FE0F ; fully-qualified # 🤹‍♀️ woman juggling +1F939 200D 2640 ; non-fully-qualified # 🤹‍♀ woman juggling +1F939 1F3FB 200D 2640 FE0F ; fully-qualified # 🤹🏻‍♀️ woman juggling: light skin tone +1F939 1F3FB 200D 2640 ; non-fully-qualified # 🤹🏻‍♀ woman juggling: light skin tone +1F939 1F3FC 200D 2640 FE0F ; fully-qualified # 🤹🏼‍♀️ woman juggling: medium-light skin tone +1F939 1F3FC 200D 2640 ; non-fully-qualified # 🤹🏼‍♀ woman juggling: medium-light skin tone +1F939 1F3FD 200D 2640 FE0F ; fully-qualified # 🤹🏽‍♀️ woman juggling: medium skin tone +1F939 1F3FD 200D 2640 ; non-fully-qualified # 🤹🏽‍♀ woman juggling: medium skin tone +1F939 1F3FE 200D 2640 FE0F ; fully-qualified # 🤹🏾‍♀️ woman juggling: medium-dark skin tone +1F939 1F3FE 200D 2640 ; non-fully-qualified # 🤹🏾‍♀ woman juggling: medium-dark skin tone +1F939 1F3FF 200D 2640 FE0F ; fully-qualified # 🤹🏿‍♀️ woman juggling: dark skin tone +1F939 1F3FF 200D 2640 ; non-fully-qualified # 🤹🏿‍♀ woman juggling: dark skin tone + +# subgroup: family +1F46B ; fully-qualified # 👫 man and woman holding hands +1F46C ; fully-qualified # 👬 two men holding hands +1F46D ; fully-qualified # 👭 two women holding hands +1F48F ; fully-qualified # 💏 kiss +1F469 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👩‍❤️‍💋‍👨 kiss: woman, man +1F469 200D 2764 200D 1F48B 200D 1F468 ; non-fully-qualified # 👩‍❤‍💋‍👨 kiss: woman, man +1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👨‍❤️‍💋‍👨 kiss: man, man +1F468 200D 2764 200D 1F48B 200D 1F468 ; non-fully-qualified # 👨‍❤‍💋‍👨 kiss: man, man +1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 ; fully-qualified # 👩‍❤️‍💋‍👩 kiss: woman, woman +1F469 200D 2764 200D 1F48B 200D 1F469 ; non-fully-qualified # 👩‍❤‍💋‍👩 kiss: woman, woman +1F491 ; fully-qualified # 💑 couple with heart +1F469 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👩‍❤️‍👨 couple with heart: woman, man +1F469 200D 2764 200D 1F468 ; non-fully-qualified # 👩‍❤‍👨 couple with heart: woman, man +1F468 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👨‍❤️‍👨 couple with heart: man, man +1F468 200D 2764 200D 1F468 ; non-fully-qualified # 👨‍❤‍👨 couple with heart: man, man +1F469 200D 2764 FE0F 200D 1F469 ; fully-qualified # 👩‍❤️‍👩 couple with heart: woman, woman +1F469 200D 2764 200D 1F469 ; non-fully-qualified # 👩‍❤‍👩 couple with heart: woman, woman +1F46A ; fully-qualified # 👪 family +1F468 200D 1F469 200D 1F466 ; fully-qualified # 👨‍👩‍👦 family: man, woman, boy +1F468 200D 1F469 200D 1F467 ; fully-qualified # 👨‍👩‍👧 family: man, woman, girl +1F468 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👩‍👧‍👦 family: man, woman, girl, boy +1F468 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👩‍👦‍👦 family: man, woman, boy, boy +1F468 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👩‍👧‍👧 family: man, woman, girl, girl +1F468 200D 1F468 200D 1F466 ; fully-qualified # 👨‍👨‍👦 family: man, man, boy +1F468 200D 1F468 200D 1F467 ; fully-qualified # 👨‍👨‍👧 family: man, man, girl +1F468 200D 1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👨‍👧‍👦 family: man, man, girl, boy +1F468 200D 1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👨‍👦‍👦 family: man, man, boy, boy +1F468 200D 1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👨‍👧‍👧 family: man, man, girl, girl +1F469 200D 1F469 200D 1F466 ; fully-qualified # 👩‍👩‍👦 family: woman, woman, boy +1F469 200D 1F469 200D 1F467 ; fully-qualified # 👩‍👩‍👧 family: woman, woman, girl +1F469 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👩‍👧‍👦 family: woman, woman, girl, boy +1F469 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👩‍👦‍👦 family: woman, woman, boy, boy +1F469 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👩‍👧‍👧 family: woman, woman, girl, girl +1F468 200D 1F466 ; fully-qualified # 👨‍👦 family: man, boy +1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👦‍👦 family: man, boy, boy +1F468 200D 1F467 ; fully-qualified # 👨‍👧 family: man, girl +1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👧‍👦 family: man, girl, boy +1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👧‍👧 family: man, girl, girl +1F469 200D 1F466 ; fully-qualified # 👩‍👦 family: woman, boy +1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👦‍👦 family: woman, boy, boy +1F469 200D 1F467 ; fully-qualified # 👩‍👧 family: woman, girl +1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👧‍👦 family: woman, girl, boy +1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👧‍👧 family: woman, girl, girl + +# subgroup: body +1F933 ; fully-qualified # 🤳 selfie +1F933 1F3FB ; fully-qualified # 🤳🏻 selfie: light skin tone +1F933 1F3FC ; fully-qualified # 🤳🏼 selfie: medium-light skin tone +1F933 1F3FD ; fully-qualified # 🤳🏽 selfie: medium skin tone +1F933 1F3FE ; fully-qualified # 🤳🏾 selfie: medium-dark skin tone +1F933 1F3FF ; fully-qualified # 🤳🏿 selfie: dark skin tone +1F4AA ; fully-qualified # 💪 flexed biceps +1F4AA 1F3FB ; fully-qualified # 💪🏻 flexed biceps: light skin tone +1F4AA 1F3FC ; fully-qualified # 💪🏼 flexed biceps: medium-light skin tone +1F4AA 1F3FD ; fully-qualified # 💪🏽 flexed biceps: medium skin tone +1F4AA 1F3FE ; fully-qualified # 💪🏾 flexed biceps: medium-dark skin tone +1F4AA 1F3FF ; fully-qualified # 💪🏿 flexed biceps: dark skin tone +1F9B5 ; fully-qualified # 🦵 leg +1F9B5 1F3FB ; fully-qualified # 🦵🏻 leg: light skin tone +1F9B5 1F3FC ; fully-qualified # 🦵🏼 leg: medium-light skin tone +1F9B5 1F3FD ; fully-qualified # 🦵🏽 leg: medium skin tone +1F9B5 1F3FE ; fully-qualified # 🦵🏾 leg: medium-dark skin tone +1F9B5 1F3FF ; fully-qualified # 🦵🏿 leg: dark skin tone +1F9B6 ; fully-qualified # 🦶 foot +1F9B6 1F3FB ; fully-qualified # 🦶🏻 foot: light skin tone +1F9B6 1F3FC ; fully-qualified # 🦶🏼 foot: medium-light skin tone +1F9B6 1F3FD ; fully-qualified # 🦶🏽 foot: medium skin tone +1F9B6 1F3FE ; fully-qualified # 🦶🏾 foot: medium-dark skin tone +1F9B6 1F3FF ; fully-qualified # 🦶🏿 foot: dark skin tone +1F448 ; fully-qualified # 👈 backhand index pointing left +1F448 1F3FB ; fully-qualified # 👈🏻 backhand index pointing left: light skin tone +1F448 1F3FC ; fully-qualified # 👈🏼 backhand index pointing left: medium-light skin tone +1F448 1F3FD ; fully-qualified # 👈🏽 backhand index pointing left: medium skin tone +1F448 1F3FE ; fully-qualified # 👈🏾 backhand index pointing left: medium-dark skin tone +1F448 1F3FF ; fully-qualified # 👈🏿 backhand index pointing left: dark skin tone +1F449 ; fully-qualified # 👉 backhand index pointing right +1F449 1F3FB ; fully-qualified # 👉🏻 backhand index pointing right: light skin tone +1F449 1F3FC ; fully-qualified # 👉🏼 backhand index pointing right: medium-light skin tone +1F449 1F3FD ; fully-qualified # 👉🏽 backhand index pointing right: medium skin tone +1F449 1F3FE ; fully-qualified # 👉🏾 backhand index pointing right: medium-dark skin tone +1F449 1F3FF ; fully-qualified # 👉🏿 backhand index pointing right: dark skin tone +261D FE0F ; fully-qualified # ☝️ index pointing up +261D ; non-fully-qualified # ☝ index pointing up +261D 1F3FB ; fully-qualified # ☝🏻 index pointing up: light skin tone +261D 1F3FC ; fully-qualified # ☝🏼 index pointing up: medium-light skin tone +261D 1F3FD ; fully-qualified # ☝🏽 index pointing up: medium skin tone +261D 1F3FE ; fully-qualified # ☝🏾 index pointing up: medium-dark skin tone +261D 1F3FF ; fully-qualified # ☝🏿 index pointing up: dark skin tone +1F446 ; fully-qualified # 👆 backhand index pointing up +1F446 1F3FB ; fully-qualified # 👆🏻 backhand index pointing up: light skin tone +1F446 1F3FC ; fully-qualified # 👆🏼 backhand index pointing up: medium-light skin tone +1F446 1F3FD ; fully-qualified # 👆🏽 backhand index pointing up: medium skin tone +1F446 1F3FE ; fully-qualified # 👆🏾 backhand index pointing up: medium-dark skin tone +1F446 1F3FF ; fully-qualified # 👆🏿 backhand index pointing up: dark skin tone +1F595 ; fully-qualified # 🖕 middle finger +1F595 1F3FB ; fully-qualified # 🖕🏻 middle finger: light skin tone +1F595 1F3FC ; fully-qualified # 🖕🏼 middle finger: medium-light skin tone +1F595 1F3FD ; fully-qualified # 🖕🏽 middle finger: medium skin tone +1F595 1F3FE ; fully-qualified # 🖕🏾 middle finger: medium-dark skin tone +1F595 1F3FF ; fully-qualified # 🖕🏿 middle finger: dark skin tone +1F447 ; fully-qualified # 👇 backhand index pointing down +1F447 1F3FB ; fully-qualified # 👇🏻 backhand index pointing down: light skin tone +1F447 1F3FC ; fully-qualified # 👇🏼 backhand index pointing down: medium-light skin tone +1F447 1F3FD ; fully-qualified # 👇🏽 backhand index pointing down: medium skin tone +1F447 1F3FE ; fully-qualified # 👇🏾 backhand index pointing down: medium-dark skin tone +1F447 1F3FF ; fully-qualified # 👇🏿 backhand index pointing down: dark skin tone +270C FE0F ; fully-qualified # ✌️ victory hand +270C ; non-fully-qualified # ✌ victory hand +270C 1F3FB ; fully-qualified # ✌🏻 victory hand: light skin tone +270C 1F3FC ; fully-qualified # ✌🏼 victory hand: medium-light skin tone +270C 1F3FD ; fully-qualified # ✌🏽 victory hand: medium skin tone +270C 1F3FE ; fully-qualified # ✌🏾 victory hand: medium-dark skin tone +270C 1F3FF ; fully-qualified # ✌🏿 victory hand: dark skin tone +1F91E ; fully-qualified # 🤞 crossed fingers +1F91E 1F3FB ; fully-qualified # 🤞🏻 crossed fingers: light skin tone +1F91E 1F3FC ; fully-qualified # 🤞🏼 crossed fingers: medium-light skin tone +1F91E 1F3FD ; fully-qualified # 🤞🏽 crossed fingers: medium skin tone +1F91E 1F3FE ; fully-qualified # 🤞🏾 crossed fingers: medium-dark skin tone +1F91E 1F3FF ; fully-qualified # 🤞🏿 crossed fingers: dark skin tone +1F596 ; fully-qualified # 🖖 vulcan salute +1F596 1F3FB ; fully-qualified # 🖖🏻 vulcan salute: light skin tone +1F596 1F3FC ; fully-qualified # 🖖🏼 vulcan salute: medium-light skin tone +1F596 1F3FD ; fully-qualified # 🖖🏽 vulcan salute: medium skin tone +1F596 1F3FE ; fully-qualified # 🖖🏾 vulcan salute: medium-dark skin tone +1F596 1F3FF ; fully-qualified # 🖖🏿 vulcan salute: dark skin tone +1F918 ; fully-qualified # 🤘 sign of the horns +1F918 1F3FB ; fully-qualified # 🤘🏻 sign of the horns: light skin tone +1F918 1F3FC ; fully-qualified # 🤘🏼 sign of the horns: medium-light skin tone +1F918 1F3FD ; fully-qualified # 🤘🏽 sign of the horns: medium skin tone +1F918 1F3FE ; fully-qualified # 🤘🏾 sign of the horns: medium-dark skin tone +1F918 1F3FF ; fully-qualified # 🤘🏿 sign of the horns: dark skin tone +1F919 ; fully-qualified # 🤙 call me hand +1F919 1F3FB ; fully-qualified # 🤙🏻 call me hand: light skin tone +1F919 1F3FC ; fully-qualified # 🤙🏼 call me hand: medium-light skin tone +1F919 1F3FD ; fully-qualified # 🤙🏽 call me hand: medium skin tone +1F919 1F3FE ; fully-qualified # 🤙🏾 call me hand: medium-dark skin tone +1F919 1F3FF ; fully-qualified # 🤙🏿 call me hand: dark skin tone +1F590 FE0F ; fully-qualified # 🖐️ hand with fingers splayed +1F590 ; non-fully-qualified # 🖐 hand with fingers splayed +1F590 1F3FB ; fully-qualified # 🖐🏻 hand with fingers splayed: light skin tone +1F590 1F3FC ; fully-qualified # 🖐🏼 hand with fingers splayed: medium-light skin tone +1F590 1F3FD ; fully-qualified # 🖐🏽 hand with fingers splayed: medium skin tone +1F590 1F3FE ; fully-qualified # 🖐🏾 hand with fingers splayed: medium-dark skin tone +1F590 1F3FF ; fully-qualified # 🖐🏿 hand with fingers splayed: dark skin tone +270B ; fully-qualified # ✋ raised hand +270B 1F3FB ; fully-qualified # ✋🏻 raised hand: light skin tone +270B 1F3FC ; fully-qualified # ✋🏼 raised hand: medium-light skin tone +270B 1F3FD ; fully-qualified # ✋🏽 raised hand: medium skin tone +270B 1F3FE ; fully-qualified # ✋🏾 raised hand: medium-dark skin tone +270B 1F3FF ; fully-qualified # ✋🏿 raised hand: dark skin tone +1F44C ; fully-qualified # 👌 OK hand +1F44C 1F3FB ; fully-qualified # 👌🏻 OK hand: light skin tone +1F44C 1F3FC ; fully-qualified # 👌🏼 OK hand: medium-light skin tone +1F44C 1F3FD ; fully-qualified # 👌🏽 OK hand: medium skin tone +1F44C 1F3FE ; fully-qualified # 👌🏾 OK hand: medium-dark skin tone +1F44C 1F3FF ; fully-qualified # 👌🏿 OK hand: dark skin tone +1F44D ; fully-qualified # 👍 thumbs up +1F44D 1F3FB ; fully-qualified # 👍🏻 thumbs up: light skin tone +1F44D 1F3FC ; fully-qualified # 👍🏼 thumbs up: medium-light skin tone +1F44D 1F3FD ; fully-qualified # 👍🏽 thumbs up: medium skin tone +1F44D 1F3FE ; fully-qualified # 👍🏾 thumbs up: medium-dark skin tone +1F44D 1F3FF ; fully-qualified # 👍🏿 thumbs up: dark skin tone +1F44E ; fully-qualified # 👎 thumbs down +1F44E 1F3FB ; fully-qualified # 👎🏻 thumbs down: light skin tone +1F44E 1F3FC ; fully-qualified # 👎🏼 thumbs down: medium-light skin tone +1F44E 1F3FD ; fully-qualified # 👎🏽 thumbs down: medium skin tone +1F44E 1F3FE ; fully-qualified # 👎🏾 thumbs down: medium-dark skin tone +1F44E 1F3FF ; fully-qualified # 👎🏿 thumbs down: dark skin tone +270A ; fully-qualified # ✊ raised fist +270A 1F3FB ; fully-qualified # ✊🏻 raised fist: light skin tone +270A 1F3FC ; fully-qualified # ✊🏼 raised fist: medium-light skin tone +270A 1F3FD ; fully-qualified # ✊🏽 raised fist: medium skin tone +270A 1F3FE ; fully-qualified # ✊🏾 raised fist: medium-dark skin tone +270A 1F3FF ; fully-qualified # ✊🏿 raised fist: dark skin tone +1F44A ; fully-qualified # 👊 oncoming fist +1F44A 1F3FB ; fully-qualified # 👊🏻 oncoming fist: light skin tone +1F44A 1F3FC ; fully-qualified # 👊🏼 oncoming fist: medium-light skin tone +1F44A 1F3FD ; fully-qualified # 👊🏽 oncoming fist: medium skin tone +1F44A 1F3FE ; fully-qualified # 👊🏾 oncoming fist: medium-dark skin tone +1F44A 1F3FF ; fully-qualified # 👊🏿 oncoming fist: dark skin tone +1F91B ; fully-qualified # 🤛 left-facing fist +1F91B 1F3FB ; fully-qualified # 🤛🏻 left-facing fist: light skin tone +1F91B 1F3FC ; fully-qualified # 🤛🏼 left-facing fist: medium-light skin tone +1F91B 1F3FD ; fully-qualified # 🤛🏽 left-facing fist: medium skin tone +1F91B 1F3FE ; fully-qualified # 🤛🏾 left-facing fist: medium-dark skin tone +1F91B 1F3FF ; fully-qualified # 🤛🏿 left-facing fist: dark skin tone +1F91C ; fully-qualified # 🤜 right-facing fist +1F91C 1F3FB ; fully-qualified # 🤜🏻 right-facing fist: light skin tone +1F91C 1F3FC ; fully-qualified # 🤜🏼 right-facing fist: medium-light skin tone +1F91C 1F3FD ; fully-qualified # 🤜🏽 right-facing fist: medium skin tone +1F91C 1F3FE ; fully-qualified # 🤜🏾 right-facing fist: medium-dark skin tone +1F91C 1F3FF ; fully-qualified # 🤜🏿 right-facing fist: dark skin tone +1F91A ; fully-qualified # 🤚 raised back of hand +1F91A 1F3FB ; fully-qualified # 🤚🏻 raised back of hand: light skin tone +1F91A 1F3FC ; fully-qualified # 🤚🏼 raised back of hand: medium-light skin tone +1F91A 1F3FD ; fully-qualified # 🤚🏽 raised back of hand: medium skin tone +1F91A 1F3FE ; fully-qualified # 🤚🏾 raised back of hand: medium-dark skin tone +1F91A 1F3FF ; fully-qualified # 🤚🏿 raised back of hand: dark skin tone +1F44B ; fully-qualified # 👋 waving hand +1F44B 1F3FB ; fully-qualified # 👋🏻 waving hand: light skin tone +1F44B 1F3FC ; fully-qualified # 👋🏼 waving hand: medium-light skin tone +1F44B 1F3FD ; fully-qualified # 👋🏽 waving hand: medium skin tone +1F44B 1F3FE ; fully-qualified # 👋🏾 waving hand: medium-dark skin tone +1F44B 1F3FF ; fully-qualified # 👋🏿 waving hand: dark skin tone +1F91F ; fully-qualified # 🤟 love-you gesture +1F91F 1F3FB ; fully-qualified # 🤟🏻 love-you gesture: light skin tone +1F91F 1F3FC ; fully-qualified # 🤟🏼 love-you gesture: medium-light skin tone +1F91F 1F3FD ; fully-qualified # 🤟🏽 love-you gesture: medium skin tone +1F91F 1F3FE ; fully-qualified # 🤟🏾 love-you gesture: medium-dark skin tone +1F91F 1F3FF ; fully-qualified # 🤟🏿 love-you gesture: dark skin tone +270D FE0F ; fully-qualified # ✍️ writing hand +270D ; non-fully-qualified # ✍ writing hand +270D 1F3FB ; fully-qualified # ✍🏻 writing hand: light skin tone +270D 1F3FC ; fully-qualified # ✍🏼 writing hand: medium-light skin tone +270D 1F3FD ; fully-qualified # ✍🏽 writing hand: medium skin tone +270D 1F3FE ; fully-qualified # ✍🏾 writing hand: medium-dark skin tone +270D 1F3FF ; fully-qualified # ✍🏿 writing hand: dark skin tone +1F44F ; fully-qualified # 👏 clapping hands +1F44F 1F3FB ; fully-qualified # 👏🏻 clapping hands: light skin tone +1F44F 1F3FC ; fully-qualified # 👏🏼 clapping hands: medium-light skin tone +1F44F 1F3FD ; fully-qualified # 👏🏽 clapping hands: medium skin tone +1F44F 1F3FE ; fully-qualified # 👏🏾 clapping hands: medium-dark skin tone +1F44F 1F3FF ; fully-qualified # 👏🏿 clapping hands: dark skin tone +1F450 ; fully-qualified # 👐 open hands +1F450 1F3FB ; fully-qualified # 👐🏻 open hands: light skin tone +1F450 1F3FC ; fully-qualified # 👐🏼 open hands: medium-light skin tone +1F450 1F3FD ; fully-qualified # 👐🏽 open hands: medium skin tone +1F450 1F3FE ; fully-qualified # 👐🏾 open hands: medium-dark skin tone +1F450 1F3FF ; fully-qualified # 👐🏿 open hands: dark skin tone +1F64C ; fully-qualified # 🙌 raising hands +1F64C 1F3FB ; fully-qualified # 🙌🏻 raising hands: light skin tone +1F64C 1F3FC ; fully-qualified # 🙌🏼 raising hands: medium-light skin tone +1F64C 1F3FD ; fully-qualified # 🙌🏽 raising hands: medium skin tone +1F64C 1F3FE ; fully-qualified # 🙌🏾 raising hands: medium-dark skin tone +1F64C 1F3FF ; fully-qualified # 🙌🏿 raising hands: dark skin tone +1F932 ; fully-qualified # 🤲 palms up together +1F932 1F3FB ; fully-qualified # 🤲🏻 palms up together: light skin tone +1F932 1F3FC ; fully-qualified # 🤲🏼 palms up together: medium-light skin tone +1F932 1F3FD ; fully-qualified # 🤲🏽 palms up together: medium skin tone +1F932 1F3FE ; fully-qualified # 🤲🏾 palms up together: medium-dark skin tone +1F932 1F3FF ; fully-qualified # 🤲🏿 palms up together: dark skin tone +1F64F ; fully-qualified # 🙏 folded hands +1F64F 1F3FB ; fully-qualified # 🙏🏻 folded hands: light skin tone +1F64F 1F3FC ; fully-qualified # 🙏🏼 folded hands: medium-light skin tone +1F64F 1F3FD ; fully-qualified # 🙏🏽 folded hands: medium skin tone +1F64F 1F3FE ; fully-qualified # 🙏🏾 folded hands: medium-dark skin tone +1F64F 1F3FF ; fully-qualified # 🙏🏿 folded hands: dark skin tone +1F91D ; fully-qualified # 🤝 handshake +1F485 ; fully-qualified # 💅 nail polish +1F485 1F3FB ; fully-qualified # 💅🏻 nail polish: light skin tone +1F485 1F3FC ; fully-qualified # 💅🏼 nail polish: medium-light skin tone +1F485 1F3FD ; fully-qualified # 💅🏽 nail polish: medium skin tone +1F485 1F3FE ; fully-qualified # 💅🏾 nail polish: medium-dark skin tone +1F485 1F3FF ; fully-qualified # 💅🏿 nail polish: dark skin tone +1F442 ; fully-qualified # 👂 ear +1F442 1F3FB ; fully-qualified # 👂🏻 ear: light skin tone +1F442 1F3FC ; fully-qualified # 👂🏼 ear: medium-light skin tone +1F442 1F3FD ; fully-qualified # 👂🏽 ear: medium skin tone +1F442 1F3FE ; fully-qualified # 👂🏾 ear: medium-dark skin tone +1F442 1F3FF ; fully-qualified # 👂🏿 ear: dark skin tone +1F443 ; fully-qualified # 👃 nose +1F443 1F3FB ; fully-qualified # 👃🏻 nose: light skin tone +1F443 1F3FC ; fully-qualified # 👃🏼 nose: medium-light skin tone +1F443 1F3FD ; fully-qualified # 👃🏽 nose: medium skin tone +1F443 1F3FE ; fully-qualified # 👃🏾 nose: medium-dark skin tone +1F443 1F3FF ; fully-qualified # 👃🏿 nose: dark skin tone +1F9B0 ; fully-qualified # 🦰 red-haired +1F9B1 ; fully-qualified # 🦱 curly-haired +1F9B2 ; fully-qualified # 🦲 bald +1F9B3 ; fully-qualified # 🦳 white-haired +1F463 ; fully-qualified # 👣 footprints +1F440 ; fully-qualified # 👀 eyes +1F441 FE0F ; fully-qualified # 👁️ eye +1F441 ; non-fully-qualified # 👁 eye +1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ eye in speech bubble +1F441 200D 1F5E8 FE0F ; non-fully-qualified # 👁‍🗨️ eye in speech bubble +1F441 FE0F 200D 1F5E8 ; non-fully-qualified # 👁️‍🗨 eye in speech bubble +1F441 200D 1F5E8 ; non-fully-qualified # 👁‍🗨 eye in speech bubble +1F9E0 ; fully-qualified # 🧠 brain +1F9B4 ; fully-qualified # 🦴 bone +1F9B7 ; fully-qualified # 🦷 tooth +1F445 ; fully-qualified # 👅 tongue +1F444 ; fully-qualified # 👄 mouth + +# subgroup: emotion +1F48B ; fully-qualified # 💋 kiss mark +1F498 ; fully-qualified # 💘 heart with arrow +2764 FE0F ; fully-qualified # ❤️ red heart +2764 ; non-fully-qualified # ❤ red heart +1F493 ; fully-qualified # 💓 beating heart +1F494 ; fully-qualified # 💔 broken heart +1F495 ; fully-qualified # 💕 two hearts +1F496 ; fully-qualified # 💖 sparkling heart +1F497 ; fully-qualified # 💗 growing heart +1F499 ; fully-qualified # 💙 blue heart +1F49A ; fully-qualified # 💚 green heart +1F49B ; fully-qualified # 💛 yellow heart +1F9E1 ; fully-qualified # 🧡 orange heart +1F49C ; fully-qualified # 💜 purple heart +1F5A4 ; fully-qualified # 🖤 black heart +1F49D ; fully-qualified # 💝 heart with ribbon +1F49E ; fully-qualified # 💞 revolving hearts +1F49F ; fully-qualified # 💟 heart decoration +2763 FE0F ; fully-qualified # ❣️ heavy heart exclamation +2763 ; non-fully-qualified # ❣ heavy heart exclamation +1F48C ; fully-qualified # 💌 love letter +1F4A4 ; fully-qualified # 💤 zzz +1F4A2 ; fully-qualified # 💢 anger symbol +1F4A3 ; fully-qualified # 💣 bomb +1F4A5 ; fully-qualified # 💥 collision +1F4A6 ; fully-qualified # 💦 sweat droplets +1F4A8 ; fully-qualified # 💨 dashing away +1F4AB ; fully-qualified # 💫 dizzy +1F4AC ; fully-qualified # 💬 speech balloon +1F5E8 FE0F ; fully-qualified # 🗨️ left speech bubble +1F5E8 ; non-fully-qualified # 🗨 left speech bubble +1F5EF FE0F ; fully-qualified # 🗯️ right anger bubble +1F5EF ; non-fully-qualified # 🗯 right anger bubble +1F4AD ; fully-qualified # 💭 thought balloon +1F573 FE0F ; fully-qualified # 🕳️ hole +1F573 ; non-fully-qualified # 🕳 hole + +# subgroup: clothing +1F453 ; fully-qualified # 👓 glasses +1F576 FE0F ; fully-qualified # 🕶️ sunglasses +1F576 ; non-fully-qualified # 🕶 sunglasses +1F97D ; fully-qualified # 🥽 goggles +1F97C ; fully-qualified # 🥼 lab coat +1F454 ; fully-qualified # 👔 necktie +1F455 ; fully-qualified # 👕 t-shirt +1F456 ; fully-qualified # 👖 jeans +1F9E3 ; fully-qualified # 🧣 scarf +1F9E4 ; fully-qualified # 🧤 gloves +1F9E5 ; fully-qualified # 🧥 coat +1F9E6 ; fully-qualified # 🧦 socks +1F457 ; fully-qualified # 👗 dress +1F458 ; fully-qualified # 👘 kimono +1F459 ; fully-qualified # 👙 bikini +1F45A ; fully-qualified # 👚 woman’s clothes +1F45B ; fully-qualified # 👛 purse +1F45C ; fully-qualified # 👜 handbag +1F45D ; fully-qualified # 👝 clutch bag +1F6CD FE0F ; fully-qualified # 🛍️ shopping bags +1F6CD ; non-fully-qualified # 🛍 shopping bags +1F392 ; fully-qualified # 🎒 school backpack +1F45E ; fully-qualified # 👞 man’s shoe +1F45F ; fully-qualified # 👟 running shoe +1F97E ; fully-qualified # 🥾 hiking boot +1F97F ; fully-qualified # 🥿 woman’s flat shoe +1F460 ; fully-qualified # 👠 high-heeled shoe +1F461 ; fully-qualified # 👡 woman’s sandal +1F462 ; fully-qualified # 👢 woman’s boot +1F451 ; fully-qualified # 👑 crown +1F452 ; fully-qualified # 👒 woman’s hat +1F3A9 ; fully-qualified # 🎩 top hat +1F393 ; fully-qualified # 🎓 graduation cap +1F9E2 ; fully-qualified # 🧢 billed cap +26D1 FE0F ; fully-qualified # ⛑️ rescue worker’s helmet +26D1 ; non-fully-qualified # ⛑ rescue worker’s helmet +1F4FF ; fully-qualified # 📿 prayer beads +1F484 ; fully-qualified # 💄 lipstick +1F48D ; fully-qualified # 💍 ring +1F48E ; fully-qualified # 💎 gem stone + +# Smileys & People subtotal: 2210 +# Smileys & People subtotal: 625 w/o modifiers + +# group: Animals & Nature + +# subgroup: animal-mammal +1F435 ; fully-qualified # 🐵 monkey face +1F412 ; fully-qualified # 🐒 monkey +1F98D ; fully-qualified # 🦍 gorilla +1F436 ; fully-qualified # 🐶 dog face +1F415 ; fully-qualified # 🐕 dog +1F429 ; fully-qualified # 🐩 poodle +1F43A ; fully-qualified # 🐺 wolf face +1F98A ; fully-qualified # 🦊 fox face +1F99D ; fully-qualified # 🦝 raccoon +1F431 ; fully-qualified # 🐱 cat face +1F408 ; fully-qualified # 🐈 cat +1F981 ; fully-qualified # 🦁 lion face +1F42F ; fully-qualified # 🐯 tiger face +1F405 ; fully-qualified # 🐅 tiger +1F406 ; fully-qualified # 🐆 leopard +1F434 ; fully-qualified # 🐴 horse face +1F40E ; fully-qualified # 🐎 horse +1F984 ; fully-qualified # 🦄 unicorn face +1F993 ; fully-qualified # 🦓 zebra +1F98C ; fully-qualified # 🦌 deer +1F42E ; fully-qualified # 🐮 cow face +1F402 ; fully-qualified # 🐂 ox +1F403 ; fully-qualified # 🐃 water buffalo +1F404 ; fully-qualified # 🐄 cow +1F437 ; fully-qualified # 🐷 pig face +1F416 ; fully-qualified # 🐖 pig +1F417 ; fully-qualified # 🐗 boar +1F43D ; fully-qualified # 🐽 pig nose +1F40F ; fully-qualified # 🐏 ram +1F411 ; fully-qualified # 🐑 ewe +1F410 ; fully-qualified # 🐐 goat +1F42A ; fully-qualified # 🐪 camel +1F42B ; fully-qualified # 🐫 two-hump camel +1F999 ; fully-qualified # 🦙 llama +1F992 ; fully-qualified # 🦒 giraffe +1F418 ; fully-qualified # 🐘 elephant +1F98F ; fully-qualified # 🦏 rhinoceros +1F99B ; fully-qualified # 🦛 hippopotamus +1F42D ; fully-qualified # 🐭 mouse face +1F401 ; fully-qualified # 🐁 mouse +1F400 ; fully-qualified # 🐀 rat +1F439 ; fully-qualified # 🐹 hamster face +1F430 ; fully-qualified # 🐰 rabbit face +1F407 ; fully-qualified # 🐇 rabbit +1F43F FE0F ; fully-qualified # 🐿️ chipmunk +1F43F ; non-fully-qualified # 🐿 chipmunk +1F994 ; fully-qualified # 🦔 hedgehog +1F987 ; fully-qualified # 🦇 bat +1F43B ; fully-qualified # 🐻 bear face +1F428 ; fully-qualified # 🐨 koala +1F43C ; fully-qualified # 🐼 panda face +1F998 ; fully-qualified # 🦘 kangaroo +1F9A1 ; fully-qualified # 🦡 badger +1F43E ; fully-qualified # 🐾 paw prints + +# subgroup: animal-bird +1F983 ; fully-qualified # 🦃 turkey +1F414 ; fully-qualified # 🐔 chicken +1F413 ; fully-qualified # 🐓 rooster +1F423 ; fully-qualified # 🐣 hatching chick +1F424 ; fully-qualified # 🐤 baby chick +1F425 ; fully-qualified # 🐥 front-facing baby chick +1F426 ; fully-qualified # 🐦 bird +1F427 ; fully-qualified # 🐧 penguin +1F54A FE0F ; fully-qualified # 🕊️ dove +1F54A ; non-fully-qualified # 🕊 dove +1F985 ; fully-qualified # 🦅 eagle +1F986 ; fully-qualified # 🦆 duck +1F9A2 ; fully-qualified # 🦢 swan +1F989 ; fully-qualified # 🦉 owl +1F99A ; fully-qualified # 🦚 peacock +1F99C ; fully-qualified # 🦜 parrot + +# subgroup: animal-amphibian +1F438 ; fully-qualified # 🐸 frog face + +# subgroup: animal-reptile +1F40A ; fully-qualified # 🐊 crocodile +1F422 ; fully-qualified # 🐢 turtle +1F98E ; fully-qualified # 🦎 lizard +1F40D ; fully-qualified # 🐍 snake +1F432 ; fully-qualified # 🐲 dragon face +1F409 ; fully-qualified # 🐉 dragon +1F995 ; fully-qualified # 🦕 sauropod +1F996 ; fully-qualified # 🦖 T-Rex + +# subgroup: animal-marine +1F433 ; fully-qualified # 🐳 spouting whale +1F40B ; fully-qualified # 🐋 whale +1F42C ; fully-qualified # 🐬 dolphin +1F41F ; fully-qualified # 🐟 fish +1F420 ; fully-qualified # 🐠 tropical fish +1F421 ; fully-qualified # 🐡 blowfish +1F988 ; fully-qualified # 🦈 shark +1F419 ; fully-qualified # 🐙 octopus +1F41A ; fully-qualified # 🐚 spiral shell +1F980 ; fully-qualified # 🦀 crab +1F99E ; fully-qualified # 🦞 lobster +1F990 ; fully-qualified # 🦐 shrimp +1F991 ; fully-qualified # 🦑 squid + +# subgroup: animal-bug +1F40C ; fully-qualified # 🐌 snail +1F98B ; fully-qualified # 🦋 butterfly +1F41B ; fully-qualified # 🐛 bug +1F41C ; fully-qualified # 🐜 ant +1F41D ; fully-qualified # 🐝 honeybee +1F41E ; fully-qualified # 🐞 lady beetle +1F997 ; fully-qualified # 🦗 cricket +1F577 FE0F ; fully-qualified # 🕷️ spider +1F577 ; non-fully-qualified # 🕷 spider +1F578 FE0F ; fully-qualified # 🕸️ spider web +1F578 ; non-fully-qualified # 🕸 spider web +1F982 ; fully-qualified # 🦂 scorpion +1F99F ; fully-qualified # 🦟 mosquito +1F9A0 ; fully-qualified # 🦠 microbe + +# subgroup: plant-flower +1F490 ; fully-qualified # 💐 bouquet +1F338 ; fully-qualified # 🌸 cherry blossom +1F4AE ; fully-qualified # 💮 white flower +1F3F5 FE0F ; fully-qualified # 🏵️ rosette +1F3F5 ; non-fully-qualified # 🏵 rosette +1F339 ; fully-qualified # 🌹 rose +1F940 ; fully-qualified # 🥀 wilted flower +1F33A ; fully-qualified # 🌺 hibiscus +1F33B ; fully-qualified # 🌻 sunflower +1F33C ; fully-qualified # 🌼 blossom +1F337 ; fully-qualified # 🌷 tulip + +# subgroup: plant-other +1F331 ; fully-qualified # 🌱 seedling +1F332 ; fully-qualified # 🌲 evergreen tree +1F333 ; fully-qualified # 🌳 deciduous tree +1F334 ; fully-qualified # 🌴 palm tree +1F335 ; fully-qualified # 🌵 cactus +1F33E ; fully-qualified # 🌾 sheaf of rice +1F33F ; fully-qualified # 🌿 herb +2618 FE0F ; fully-qualified # ☘️ shamrock +2618 ; non-fully-qualified # ☘ shamrock +1F340 ; fully-qualified # 🍀 four leaf clover +1F341 ; fully-qualified # 🍁 maple leaf +1F342 ; fully-qualified # 🍂 fallen leaf +1F343 ; fully-qualified # 🍃 leaf fluttering in wind + +# Animals & Nature subtotal: 130 +# Animals & Nature subtotal: 130 w/o modifiers + +# group: Food & Drink + +# subgroup: food-fruit +1F347 ; fully-qualified # 🍇 grapes +1F348 ; fully-qualified # 🍈 melon +1F349 ; fully-qualified # 🍉 watermelon +1F34A ; fully-qualified # 🍊 tangerine +1F34B ; fully-qualified # 🍋 lemon +1F34C ; fully-qualified # 🍌 banana +1F34D ; fully-qualified # 🍍 pineapple +1F96D ; fully-qualified # 🥭 mango +1F34E ; fully-qualified # 🍎 red apple +1F34F ; fully-qualified # 🍏 green apple +1F350 ; fully-qualified # 🍐 pear +1F351 ; fully-qualified # 🍑 peach +1F352 ; fully-qualified # 🍒 cherries +1F353 ; fully-qualified # 🍓 strawberry +1F95D ; fully-qualified # 🥝 kiwi fruit +1F345 ; fully-qualified # 🍅 tomato +1F965 ; fully-qualified # 🥥 coconut + +# subgroup: food-vegetable +1F951 ; fully-qualified # 🥑 avocado +1F346 ; fully-qualified # 🍆 eggplant +1F954 ; fully-qualified # 🥔 potato +1F955 ; fully-qualified # 🥕 carrot +1F33D ; fully-qualified # 🌽 ear of corn +1F336 FE0F ; fully-qualified # 🌶️ hot pepper +1F336 ; non-fully-qualified # 🌶 hot pepper +1F952 ; fully-qualified # 🥒 cucumber +1F96C ; fully-qualified # 🥬 leafy green +1F966 ; fully-qualified # 🥦 broccoli +1F344 ; fully-qualified # 🍄 mushroom +1F95C ; fully-qualified # 🥜 peanuts +1F330 ; fully-qualified # 🌰 chestnut + +# subgroup: food-prepared +1F35E ; fully-qualified # 🍞 bread +1F950 ; fully-qualified # 🥐 croissant +1F956 ; fully-qualified # 🥖 baguette bread +1F968 ; fully-qualified # 🥨 pretzel +1F96F ; fully-qualified # 🥯 bagel +1F95E ; fully-qualified # 🥞 pancakes +1F9C0 ; fully-qualified # 🧀 cheese wedge +1F356 ; fully-qualified # 🍖 meat on bone +1F357 ; fully-qualified # 🍗 poultry leg +1F969 ; fully-qualified # 🥩 cut of meat +1F953 ; fully-qualified # 🥓 bacon +1F354 ; fully-qualified # 🍔 hamburger +1F35F ; fully-qualified # 🍟 french fries +1F355 ; fully-qualified # 🍕 pizza +1F32D ; fully-qualified # 🌭 hot dog +1F96A ; fully-qualified # 🥪 sandwich +1F32E ; fully-qualified # 🌮 taco +1F32F ; fully-qualified # 🌯 burrito +1F959 ; fully-qualified # 🥙 stuffed flatbread +1F95A ; fully-qualified # 🥚 egg +1F373 ; fully-qualified # 🍳 cooking +1F958 ; fully-qualified # 🥘 shallow pan of food +1F372 ; fully-qualified # 🍲 pot of food +1F963 ; fully-qualified # 🥣 bowl with spoon +1F957 ; fully-qualified # 🥗 green salad +1F37F ; fully-qualified # 🍿 popcorn +1F9C2 ; fully-qualified # 🧂 salt +1F96B ; fully-qualified # 🥫 canned food + +# subgroup: food-asian +1F371 ; fully-qualified # 🍱 bento box +1F358 ; fully-qualified # 🍘 rice cracker +1F359 ; fully-qualified # 🍙 rice ball +1F35A ; fully-qualified # 🍚 cooked rice +1F35B ; fully-qualified # 🍛 curry rice +1F35C ; fully-qualified # 🍜 steaming bowl +1F35D ; fully-qualified # 🍝 spaghetti +1F360 ; fully-qualified # 🍠 roasted sweet potato +1F362 ; fully-qualified # 🍢 oden +1F363 ; fully-qualified # 🍣 sushi +1F364 ; fully-qualified # 🍤 fried shrimp +1F365 ; fully-qualified # 🍥 fish cake with swirl +1F96E ; fully-qualified # 🥮 moon cake +1F361 ; fully-qualified # 🍡 dango +1F95F ; fully-qualified # 🥟 dumpling +1F960 ; fully-qualified # 🥠 fortune cookie +1F961 ; fully-qualified # 🥡 takeout box + +# subgroup: food-sweet +1F366 ; fully-qualified # 🍦 soft ice cream +1F367 ; fully-qualified # 🍧 shaved ice +1F368 ; fully-qualified # 🍨 ice cream +1F369 ; fully-qualified # 🍩 doughnut +1F36A ; fully-qualified # 🍪 cookie +1F382 ; fully-qualified # 🎂 birthday cake +1F370 ; fully-qualified # 🍰 shortcake +1F9C1 ; fully-qualified # 🧁 cupcake +1F967 ; fully-qualified # 🥧 pie +1F36B ; fully-qualified # 🍫 chocolate bar +1F36C ; fully-qualified # 🍬 candy +1F36D ; fully-qualified # 🍭 lollipop +1F36E ; fully-qualified # 🍮 custard +1F36F ; fully-qualified # 🍯 honey pot + +# subgroup: drink +1F37C ; fully-qualified # 🍼 baby bottle +1F95B ; fully-qualified # 🥛 glass of milk +2615 ; fully-qualified # ☕ hot beverage +1F375 ; fully-qualified # 🍵 teacup without handle +1F376 ; fully-qualified # 🍶 sake +1F37E ; fully-qualified # 🍾 bottle with popping cork +1F377 ; fully-qualified # 🍷 wine glass +1F378 ; fully-qualified # 🍸 cocktail glass +1F379 ; fully-qualified # 🍹 tropical drink +1F37A ; fully-qualified # 🍺 beer mug +1F37B ; fully-qualified # 🍻 clinking beer mugs +1F942 ; fully-qualified # 🥂 clinking glasses +1F943 ; fully-qualified # 🥃 tumbler glass +1F964 ; fully-qualified # 🥤 cup with straw + +# subgroup: dishware +1F962 ; fully-qualified # 🥢 chopsticks +1F37D FE0F ; fully-qualified # 🍽️ fork and knife with plate +1F37D ; non-fully-qualified # 🍽 fork and knife with plate +1F374 ; fully-qualified # 🍴 fork and knife +1F944 ; fully-qualified # 🥄 spoon +1F52A ; fully-qualified # 🔪 kitchen knife +1F3FA ; fully-qualified # 🏺 amphora + +# Food & Drink subtotal: 110 +# Food & Drink subtotal: 110 w/o modifiers + +# group: Travel & Places + +# subgroup: place-map +1F30D ; fully-qualified # 🌍 globe showing Europe-Africa +1F30E ; fully-qualified # 🌎 globe showing Americas +1F30F ; fully-qualified # 🌏 globe showing Asia-Australia +1F310 ; fully-qualified # 🌐 globe with meridians +1F5FA FE0F ; fully-qualified # 🗺️ world map +1F5FA ; non-fully-qualified # 🗺 world map +1F5FE ; fully-qualified # 🗾 map of Japan +1F9ED ; fully-qualified # 🧭 compass + +# subgroup: place-geographic +1F3D4 FE0F ; fully-qualified # 🏔️ snow-capped mountain +1F3D4 ; non-fully-qualified # 🏔 snow-capped mountain +26F0 FE0F ; fully-qualified # ⛰️ mountain +26F0 ; non-fully-qualified # ⛰ mountain +1F30B ; fully-qualified # 🌋 volcano +1F5FB ; fully-qualified # 🗻 mount fuji +1F3D5 FE0F ; fully-qualified # 🏕️ camping +1F3D5 ; non-fully-qualified # 🏕 camping +1F3D6 FE0F ; fully-qualified # 🏖️ beach with umbrella +1F3D6 ; non-fully-qualified # 🏖 beach with umbrella +1F3DC FE0F ; fully-qualified # 🏜️ desert +1F3DC ; non-fully-qualified # 🏜 desert +1F3DD FE0F ; fully-qualified # 🏝️ desert island +1F3DD ; non-fully-qualified # 🏝 desert island +1F3DE FE0F ; fully-qualified # 🏞️ national park +1F3DE ; non-fully-qualified # 🏞 national park + +# subgroup: place-building +1F3DF FE0F ; fully-qualified # 🏟️ stadium +1F3DF ; non-fully-qualified # 🏟 stadium +1F3DB FE0F ; fully-qualified # 🏛️ classical building +1F3DB ; non-fully-qualified # 🏛 classical building +1F3D7 FE0F ; fully-qualified # 🏗️ building construction +1F3D7 ; non-fully-qualified # 🏗 building construction +1F9F1 ; fully-qualified # 🧱 bricks +1F3D8 FE0F ; fully-qualified # 🏘️ houses +1F3D8 ; non-fully-qualified # 🏘 houses +1F3DA FE0F ; fully-qualified # 🏚️ derelict house +1F3DA ; non-fully-qualified # 🏚 derelict house +1F3E0 ; fully-qualified # 🏠 house +1F3E1 ; fully-qualified # 🏡 house with garden +1F3E2 ; fully-qualified # 🏢 office building +1F3E3 ; fully-qualified # 🏣 Japanese post office +1F3E4 ; fully-qualified # 🏤 post office +1F3E5 ; fully-qualified # 🏥 hospital +1F3E6 ; fully-qualified # 🏦 bank +1F3E8 ; fully-qualified # 🏨 hotel +1F3E9 ; fully-qualified # 🏩 love hotel +1F3EA ; fully-qualified # 🏪 convenience store +1F3EB ; fully-qualified # 🏫 school +1F3EC ; fully-qualified # 🏬 department store +1F3ED ; fully-qualified # 🏭 factory +1F3EF ; fully-qualified # 🏯 Japanese castle +1F3F0 ; fully-qualified # 🏰 castle +1F492 ; fully-qualified # 💒 wedding +1F5FC ; fully-qualified # 🗼 Tokyo tower +1F5FD ; fully-qualified # 🗽 Statue of Liberty + +# subgroup: place-religious +26EA ; fully-qualified # ⛪ church +1F54C ; fully-qualified # 🕌 mosque +1F54D ; fully-qualified # 🕍 synagogue +26E9 FE0F ; fully-qualified # ⛩️ shinto shrine +26E9 ; non-fully-qualified # ⛩ shinto shrine +1F54B ; fully-qualified # 🕋 kaaba + +# subgroup: place-other +26F2 ; fully-qualified # ⛲ fountain +26FA ; fully-qualified # ⛺ tent +1F301 ; fully-qualified # 🌁 foggy +1F303 ; fully-qualified # 🌃 night with stars +1F3D9 FE0F ; fully-qualified # 🏙️ cityscape +1F3D9 ; non-fully-qualified # 🏙 cityscape +1F304 ; fully-qualified # 🌄 sunrise over mountains +1F305 ; fully-qualified # 🌅 sunrise +1F306 ; fully-qualified # 🌆 cityscape at dusk +1F307 ; fully-qualified # 🌇 sunset +1F309 ; fully-qualified # 🌉 bridge at night +2668 FE0F ; fully-qualified # ♨️ hot springs +2668 ; non-fully-qualified # ♨ hot springs +1F30C ; fully-qualified # 🌌 milky way +1F3A0 ; fully-qualified # 🎠 carousel horse +1F3A1 ; fully-qualified # 🎡 ferris wheel +1F3A2 ; fully-qualified # 🎢 roller coaster +1F488 ; fully-qualified # 💈 barber pole +1F3AA ; fully-qualified # 🎪 circus tent + +# subgroup: transport-ground +1F682 ; fully-qualified # 🚂 locomotive +1F683 ; fully-qualified # 🚃 railway car +1F684 ; fully-qualified # 🚄 high-speed train +1F685 ; fully-qualified # 🚅 bullet train +1F686 ; fully-qualified # 🚆 train +1F687 ; fully-qualified # 🚇 metro +1F688 ; fully-qualified # 🚈 light rail +1F689 ; fully-qualified # 🚉 station +1F68A ; fully-qualified # 🚊 tram +1F69D ; fully-qualified # 🚝 monorail +1F69E ; fully-qualified # 🚞 mountain railway +1F68B ; fully-qualified # 🚋 tram car +1F68C ; fully-qualified # 🚌 bus +1F68D ; fully-qualified # 🚍 oncoming bus +1F68E ; fully-qualified # 🚎 trolleybus +1F690 ; fully-qualified # 🚐 minibus +1F691 ; fully-qualified # 🚑 ambulance +1F692 ; fully-qualified # 🚒 fire engine +1F693 ; fully-qualified # 🚓 police car +1F694 ; fully-qualified # 🚔 oncoming police car +1F695 ; fully-qualified # 🚕 taxi +1F696 ; fully-qualified # 🚖 oncoming taxi +1F697 ; fully-qualified # 🚗 automobile +1F698 ; fully-qualified # 🚘 oncoming automobile +1F699 ; fully-qualified # 🚙 sport utility vehicle +1F69A ; fully-qualified # 🚚 delivery truck +1F69B ; fully-qualified # 🚛 articulated lorry +1F69C ; fully-qualified # 🚜 tractor +1F6B2 ; fully-qualified # 🚲 bicycle +1F6F4 ; fully-qualified # 🛴 kick scooter +1F6F9 ; fully-qualified # 🛹 skateboard +1F6F5 ; fully-qualified # 🛵 motor scooter +1F68F ; fully-qualified # 🚏 bus stop +1F6E3 FE0F ; fully-qualified # 🛣️ motorway +1F6E3 ; non-fully-qualified # 🛣 motorway +1F6E4 FE0F ; fully-qualified # 🛤️ railway track +1F6E4 ; non-fully-qualified # 🛤 railway track +1F6E2 FE0F ; fully-qualified # 🛢️ oil drum +1F6E2 ; non-fully-qualified # 🛢 oil drum +26FD ; fully-qualified # ⛽ fuel pump +1F6A8 ; fully-qualified # 🚨 police car light +1F6A5 ; fully-qualified # 🚥 horizontal traffic light +1F6A6 ; fully-qualified # 🚦 vertical traffic light +1F6D1 ; fully-qualified # 🛑 stop sign +1F6A7 ; fully-qualified # 🚧 construction + +# subgroup: transport-water +2693 ; fully-qualified # ⚓ anchor +26F5 ; fully-qualified # ⛵ sailboat +1F6F6 ; fully-qualified # 🛶 canoe +1F6A4 ; fully-qualified # 🚤 speedboat +1F6F3 FE0F ; fully-qualified # 🛳️ passenger ship +1F6F3 ; non-fully-qualified # 🛳 passenger ship +26F4 FE0F ; fully-qualified # ⛴️ ferry +26F4 ; non-fully-qualified # ⛴ ferry +1F6E5 FE0F ; fully-qualified # 🛥️ motor boat +1F6E5 ; non-fully-qualified # 🛥 motor boat +1F6A2 ; fully-qualified # 🚢 ship + +# subgroup: transport-air +2708 FE0F ; fully-qualified # ✈️ airplane +2708 ; non-fully-qualified # ✈ airplane +1F6E9 FE0F ; fully-qualified # 🛩️ small airplane +1F6E9 ; non-fully-qualified # 🛩 small airplane +1F6EB ; fully-qualified # 🛫 airplane departure +1F6EC ; fully-qualified # 🛬 airplane arrival +1F4BA ; fully-qualified # 💺 seat +1F681 ; fully-qualified # 🚁 helicopter +1F69F ; fully-qualified # 🚟 suspension railway +1F6A0 ; fully-qualified # 🚠 mountain cableway +1F6A1 ; fully-qualified # 🚡 aerial tramway +1F6F0 FE0F ; fully-qualified # 🛰️ satellite +1F6F0 ; non-fully-qualified # 🛰 satellite +1F680 ; fully-qualified # 🚀 rocket +1F6F8 ; fully-qualified # 🛸 flying saucer + +# subgroup: hotel +1F6CE FE0F ; fully-qualified # 🛎️ bellhop bell +1F6CE ; non-fully-qualified # 🛎 bellhop bell +1F9F3 ; fully-qualified # 🧳 luggage + +# subgroup: time +231B ; fully-qualified # ⌛ hourglass done +23F3 ; fully-qualified # ⏳ hourglass not done +231A ; fully-qualified # ⌚ watch +23F0 ; fully-qualified # ⏰ alarm clock +23F1 FE0F ; fully-qualified # ⏱️ stopwatch +23F1 ; non-fully-qualified # ⏱ stopwatch +23F2 FE0F ; fully-qualified # ⏲️ timer clock +23F2 ; non-fully-qualified # ⏲ timer clock +1F570 FE0F ; fully-qualified # 🕰️ mantelpiece clock +1F570 ; non-fully-qualified # 🕰 mantelpiece clock +1F55B ; fully-qualified # 🕛 twelve o’clock +1F567 ; fully-qualified # 🕧 twelve-thirty +1F550 ; fully-qualified # 🕐 one o’clock +1F55C ; fully-qualified # 🕜 one-thirty +1F551 ; fully-qualified # 🕑 two o’clock +1F55D ; fully-qualified # 🕝 two-thirty +1F552 ; fully-qualified # 🕒 three o’clock +1F55E ; fully-qualified # 🕞 three-thirty +1F553 ; fully-qualified # 🕓 four o’clock +1F55F ; fully-qualified # 🕟 four-thirty +1F554 ; fully-qualified # 🕔 five o’clock +1F560 ; fully-qualified # 🕠 five-thirty +1F555 ; fully-qualified # 🕕 six o’clock +1F561 ; fully-qualified # 🕡 six-thirty +1F556 ; fully-qualified # 🕖 seven o’clock +1F562 ; fully-qualified # 🕢 seven-thirty +1F557 ; fully-qualified # 🕗 eight o’clock +1F563 ; fully-qualified # 🕣 eight-thirty +1F558 ; fully-qualified # 🕘 nine o’clock +1F564 ; fully-qualified # 🕤 nine-thirty +1F559 ; fully-qualified # 🕙 ten o’clock +1F565 ; fully-qualified # 🕥 ten-thirty +1F55A ; fully-qualified # 🕚 eleven o’clock +1F566 ; fully-qualified # 🕦 eleven-thirty + +# subgroup: sky & weather +1F311 ; fully-qualified # 🌑 new moon +1F312 ; fully-qualified # 🌒 waxing crescent moon +1F313 ; fully-qualified # 🌓 first quarter moon +1F314 ; fully-qualified # 🌔 waxing gibbous moon +1F315 ; fully-qualified # 🌕 full moon +1F316 ; fully-qualified # 🌖 waning gibbous moon +1F317 ; fully-qualified # 🌗 last quarter moon +1F318 ; fully-qualified # 🌘 waning crescent moon +1F319 ; fully-qualified # 🌙 crescent moon +1F31A ; fully-qualified # 🌚 new moon face +1F31B ; fully-qualified # 🌛 first quarter moon face +1F31C ; fully-qualified # 🌜 last quarter moon face +1F321 FE0F ; fully-qualified # 🌡️ thermometer +1F321 ; non-fully-qualified # 🌡 thermometer +2600 FE0F ; fully-qualified # ☀️ sun +2600 ; non-fully-qualified # ☀ sun +1F31D ; fully-qualified # 🌝 full moon face +1F31E ; fully-qualified # 🌞 sun with face +2B50 ; fully-qualified # ⭐ star +1F31F ; fully-qualified # 🌟 glowing star +1F320 ; fully-qualified # 🌠 shooting star +2601 FE0F ; fully-qualified # ☁️ cloud +2601 ; non-fully-qualified # ☁ cloud +26C5 ; fully-qualified # ⛅ sun behind cloud +26C8 FE0F ; fully-qualified # ⛈️ cloud with lightning and rain +26C8 ; non-fully-qualified # ⛈ cloud with lightning and rain +1F324 FE0F ; fully-qualified # 🌤️ sun behind small cloud +1F324 ; non-fully-qualified # 🌤 sun behind small cloud +1F325 FE0F ; fully-qualified # 🌥️ sun behind large cloud +1F325 ; non-fully-qualified # 🌥 sun behind large cloud +1F326 FE0F ; fully-qualified # 🌦️ sun behind rain cloud +1F326 ; non-fully-qualified # 🌦 sun behind rain cloud +1F327 FE0F ; fully-qualified # 🌧️ cloud with rain +1F327 ; non-fully-qualified # 🌧 cloud with rain +1F328 FE0F ; fully-qualified # 🌨️ cloud with snow +1F328 ; non-fully-qualified # 🌨 cloud with snow +1F329 FE0F ; fully-qualified # 🌩️ cloud with lightning +1F329 ; non-fully-qualified # 🌩 cloud with lightning +1F32A FE0F ; fully-qualified # 🌪️ tornado +1F32A ; non-fully-qualified # 🌪 tornado +1F32B FE0F ; fully-qualified # 🌫️ fog +1F32B ; non-fully-qualified # 🌫 fog +1F32C FE0F ; fully-qualified # 🌬️ wind face +1F32C ; non-fully-qualified # 🌬 wind face +1F300 ; fully-qualified # 🌀 cyclone +1F308 ; fully-qualified # 🌈 rainbow +1F302 ; fully-qualified # 🌂 closed umbrella +2602 FE0F ; fully-qualified # ☂️ umbrella +2602 ; non-fully-qualified # ☂ umbrella +2614 ; fully-qualified # ☔ umbrella with rain drops +26F1 FE0F ; fully-qualified # ⛱️ umbrella on ground +26F1 ; non-fully-qualified # ⛱ umbrella on ground +26A1 ; fully-qualified # ⚡ high voltage +2744 FE0F ; fully-qualified # ❄️ snowflake +2744 ; non-fully-qualified # ❄ snowflake +2603 FE0F ; fully-qualified # ☃️ snowman +2603 ; non-fully-qualified # ☃ snowman +26C4 ; fully-qualified # ⛄ snowman without snow +2604 FE0F ; fully-qualified # ☄️ comet +2604 ; non-fully-qualified # ☄ comet +1F525 ; fully-qualified # 🔥 fire +1F4A7 ; fully-qualified # 💧 droplet +1F30A ; fully-qualified # 🌊 water wave + +# Travel & Places subtotal: 249 +# Travel & Places subtotal: 249 w/o modifiers + +# group: Activities + +# subgroup: event +1F383 ; fully-qualified # 🎃 jack-o-lantern +1F384 ; fully-qualified # 🎄 Christmas tree +1F386 ; fully-qualified # 🎆 fireworks +1F387 ; fully-qualified # 🎇 sparkler +1F9E8 ; fully-qualified # 🧨 firecracker +2728 ; fully-qualified # ✨ sparkles +1F388 ; fully-qualified # 🎈 balloon +1F389 ; fully-qualified # 🎉 party popper +1F38A ; fully-qualified # 🎊 confetti ball +1F38B ; fully-qualified # 🎋 tanabata tree +1F38D ; fully-qualified # 🎍 pine decoration +1F38E ; fully-qualified # 🎎 Japanese dolls +1F38F ; fully-qualified # 🎏 carp streamer +1F390 ; fully-qualified # 🎐 wind chime +1F391 ; fully-qualified # 🎑 moon viewing ceremony +1F9E7 ; fully-qualified # 🧧 red envelope +1F380 ; fully-qualified # 🎀 ribbon +1F381 ; fully-qualified # 🎁 wrapped gift +1F397 FE0F ; fully-qualified # 🎗️ reminder ribbon +1F397 ; non-fully-qualified # 🎗 reminder ribbon +1F39F FE0F ; fully-qualified # 🎟️ admission tickets +1F39F ; non-fully-qualified # 🎟 admission tickets +1F3AB ; fully-qualified # 🎫 ticket + +# subgroup: award-medal +1F396 FE0F ; fully-qualified # 🎖️ military medal +1F396 ; non-fully-qualified # 🎖 military medal +1F3C6 ; fully-qualified # 🏆 trophy +1F3C5 ; fully-qualified # 🏅 sports medal +1F947 ; fully-qualified # 🥇 1st place medal +1F948 ; fully-qualified # 🥈 2nd place medal +1F949 ; fully-qualified # 🥉 3rd place medal + +# subgroup: sport +26BD ; fully-qualified # ⚽ soccer ball +26BE ; fully-qualified # ⚾ baseball +1F94E ; fully-qualified # 🥎 softball +1F3C0 ; fully-qualified # 🏀 basketball +1F3D0 ; fully-qualified # 🏐 volleyball +1F3C8 ; fully-qualified # 🏈 american football +1F3C9 ; fully-qualified # 🏉 rugby football +1F3BE ; fully-qualified # 🎾 tennis +1F94F ; fully-qualified # 🥏 flying disc +1F3B3 ; fully-qualified # 🎳 bowling +1F3CF ; fully-qualified # 🏏 cricket game +1F3D1 ; fully-qualified # 🏑 field hockey +1F3D2 ; fully-qualified # 🏒 ice hockey +1F94D ; fully-qualified # 🥍 lacrosse +1F3D3 ; fully-qualified # 🏓 ping pong +1F3F8 ; fully-qualified # 🏸 badminton +1F94A ; fully-qualified # 🥊 boxing glove +1F94B ; fully-qualified # 🥋 martial arts uniform +1F945 ; fully-qualified # 🥅 goal net +26F3 ; fully-qualified # ⛳ flag in hole +26F8 FE0F ; fully-qualified # ⛸️ ice skate +26F8 ; non-fully-qualified # ⛸ ice skate +1F3A3 ; fully-qualified # 🎣 fishing pole +1F3BD ; fully-qualified # 🎽 running shirt +1F3BF ; fully-qualified # 🎿 skis +1F6F7 ; fully-qualified # 🛷 sled +1F94C ; fully-qualified # 🥌 curling stone + +# subgroup: game +1F3AF ; fully-qualified # 🎯 direct hit +1F3B1 ; fully-qualified # 🎱 pool 8 ball +1F52E ; fully-qualified # 🔮 crystal ball +1F9FF ; fully-qualified # 🧿 nazar amulet +1F3AE ; fully-qualified # 🎮 video game +1F579 FE0F ; fully-qualified # 🕹️ joystick +1F579 ; non-fully-qualified # 🕹 joystick +1F3B0 ; fully-qualified # 🎰 slot machine +1F3B2 ; fully-qualified # 🎲 game die +1F9E9 ; fully-qualified # 🧩 jigsaw +1F9F8 ; fully-qualified # 🧸 teddy bear +2660 FE0F ; fully-qualified # ♠️ spade suit +2660 ; non-fully-qualified # ♠ spade suit +2665 FE0F ; fully-qualified # ♥️ heart suit +2665 ; non-fully-qualified # ♥ heart suit +2666 FE0F ; fully-qualified # ♦️ diamond suit +2666 ; non-fully-qualified # ♦ diamond suit +2663 FE0F ; fully-qualified # ♣️ club suit +2663 ; non-fully-qualified # ♣ club suit +265F FE0F ; fully-qualified # ♟️ chess pawn +265F ; non-fully-qualified # ♟ chess pawn +1F0CF ; fully-qualified # 🃏 joker +1F004 ; fully-qualified # 🀄 mahjong red dragon +1F3B4 ; fully-qualified # 🎴 flower playing cards + +# subgroup: arts & crafts +1F3AD ; fully-qualified # 🎭 performing arts +1F5BC FE0F ; fully-qualified # 🖼️ framed picture +1F5BC ; non-fully-qualified # 🖼 framed picture +1F3A8 ; fully-qualified # 🎨 artist palette +1F9F5 ; fully-qualified # 🧵 thread +1F9F6 ; fully-qualified # 🧶 yarn + +# Activities subtotal: 87 +# Activities subtotal: 87 w/o modifiers + +# group: Objects + +# subgroup: sound +1F507 ; fully-qualified # 🔇 muted speaker +1F508 ; fully-qualified # 🔈 speaker low volume +1F509 ; fully-qualified # 🔉 speaker medium volume +1F50A ; fully-qualified # 🔊 speaker high volume +1F4E2 ; fully-qualified # 📢 loudspeaker +1F4E3 ; fully-qualified # 📣 megaphone +1F4EF ; fully-qualified # 📯 postal horn +1F514 ; fully-qualified # 🔔 bell +1F515 ; fully-qualified # 🔕 bell with slash + +# subgroup: music +1F3BC ; fully-qualified # 🎼 musical score +1F3B5 ; fully-qualified # 🎵 musical note +1F3B6 ; fully-qualified # 🎶 musical notes +1F399 FE0F ; fully-qualified # 🎙️ studio microphone +1F399 ; non-fully-qualified # 🎙 studio microphone +1F39A FE0F ; fully-qualified # 🎚️ level slider +1F39A ; non-fully-qualified # 🎚 level slider +1F39B FE0F ; fully-qualified # 🎛️ control knobs +1F39B ; non-fully-qualified # 🎛 control knobs +1F3A4 ; fully-qualified # 🎤 microphone +1F3A7 ; fully-qualified # 🎧 headphone +1F4FB ; fully-qualified # 📻 radio + +# subgroup: musical-instrument +1F3B7 ; fully-qualified # 🎷 saxophone +1F3B8 ; fully-qualified # 🎸 guitar +1F3B9 ; fully-qualified # 🎹 musical keyboard +1F3BA ; fully-qualified # 🎺 trumpet +1F3BB ; fully-qualified # 🎻 violin +1F941 ; fully-qualified # 🥁 drum + +# subgroup: phone +1F4F1 ; fully-qualified # 📱 mobile phone +1F4F2 ; fully-qualified # 📲 mobile phone with arrow +260E FE0F ; fully-qualified # ☎️ telephone +260E ; non-fully-qualified # ☎ telephone +1F4DE ; fully-qualified # 📞 telephone receiver +1F4DF ; fully-qualified # 📟 pager +1F4E0 ; fully-qualified # 📠 fax machine + +# subgroup: computer +1F50B ; fully-qualified # 🔋 battery +1F50C ; fully-qualified # 🔌 electric plug +1F4BB ; fully-qualified # 💻 laptop computer +1F5A5 FE0F ; fully-qualified # 🖥️ desktop computer +1F5A5 ; non-fully-qualified # 🖥 desktop computer +1F5A8 FE0F ; fully-qualified # 🖨️ printer +1F5A8 ; non-fully-qualified # 🖨 printer +2328 FE0F ; fully-qualified # ⌨️ keyboard +2328 ; non-fully-qualified # ⌨ keyboard +1F5B1 FE0F ; fully-qualified # 🖱️ computer mouse +1F5B1 ; non-fully-qualified # 🖱 computer mouse +1F5B2 FE0F ; fully-qualified # 🖲️ trackball +1F5B2 ; non-fully-qualified # 🖲 trackball +1F4BD ; fully-qualified # 💽 computer disk +1F4BE ; fully-qualified # 💾 floppy disk +1F4BF ; fully-qualified # 💿 optical disk +1F4C0 ; fully-qualified # 📀 dvd +1F9EE ; fully-qualified # 🧮 abacus + +# subgroup: light & video +1F3A5 ; fully-qualified # 🎥 movie camera +1F39E FE0F ; fully-qualified # 🎞️ film frames +1F39E ; non-fully-qualified # 🎞 film frames +1F4FD FE0F ; fully-qualified # 📽️ film projector +1F4FD ; non-fully-qualified # 📽 film projector +1F3AC ; fully-qualified # 🎬 clapper board +1F4FA ; fully-qualified # 📺 television +1F4F7 ; fully-qualified # 📷 camera +1F4F8 ; fully-qualified # 📸 camera with flash +1F4F9 ; fully-qualified # 📹 video camera +1F4FC ; fully-qualified # 📼 videocassette +1F50D ; fully-qualified # 🔍 magnifying glass tilted left +1F50E ; fully-qualified # 🔎 magnifying glass tilted right +1F56F FE0F ; fully-qualified # 🕯️ candle +1F56F ; non-fully-qualified # 🕯 candle +1F4A1 ; fully-qualified # 💡 light bulb +1F526 ; fully-qualified # 🔦 flashlight +1F3EE ; fully-qualified # 🏮 red paper lantern + +# subgroup: book-paper +1F4D4 ; fully-qualified # 📔 notebook with decorative cover +1F4D5 ; fully-qualified # 📕 closed book +1F4D6 ; fully-qualified # 📖 open book +1F4D7 ; fully-qualified # 📗 green book +1F4D8 ; fully-qualified # 📘 blue book +1F4D9 ; fully-qualified # 📙 orange book +1F4DA ; fully-qualified # 📚 books +1F4D3 ; fully-qualified # 📓 notebook +1F4D2 ; fully-qualified # 📒 ledger +1F4C3 ; fully-qualified # 📃 page with curl +1F4DC ; fully-qualified # 📜 scroll +1F4C4 ; fully-qualified # 📄 page facing up +1F4F0 ; fully-qualified # 📰 newspaper +1F5DE FE0F ; fully-qualified # 🗞️ rolled-up newspaper +1F5DE ; non-fully-qualified # 🗞 rolled-up newspaper +1F4D1 ; fully-qualified # 📑 bookmark tabs +1F516 ; fully-qualified # 🔖 bookmark +1F3F7 FE0F ; fully-qualified # 🏷️ label +1F3F7 ; non-fully-qualified # 🏷 label + +# subgroup: money +1F4B0 ; fully-qualified # 💰 money bag +1F4B4 ; fully-qualified # 💴 yen banknote +1F4B5 ; fully-qualified # 💵 dollar banknote +1F4B6 ; fully-qualified # 💶 euro banknote +1F4B7 ; fully-qualified # 💷 pound banknote +1F4B8 ; fully-qualified # 💸 money with wings +1F4B3 ; fully-qualified # 💳 credit card +1F9FE ; fully-qualified # 🧾 receipt +1F4B9 ; fully-qualified # 💹 chart increasing with yen +1F4B1 ; fully-qualified # 💱 currency exchange +1F4B2 ; fully-qualified # 💲 heavy dollar sign + +# subgroup: mail +2709 FE0F ; fully-qualified # ✉️ envelope +2709 ; non-fully-qualified # ✉ envelope +1F4E7 ; fully-qualified # 📧 e-mail +1F4E8 ; fully-qualified # 📨 incoming envelope +1F4E9 ; fully-qualified # 📩 envelope with arrow +1F4E4 ; fully-qualified # 📤 outbox tray +1F4E5 ; fully-qualified # 📥 inbox tray +1F4E6 ; fully-qualified # 📦 package +1F4EB ; fully-qualified # 📫 closed mailbox with raised flag +1F4EA ; fully-qualified # 📪 closed mailbox with lowered flag +1F4EC ; fully-qualified # 📬 open mailbox with raised flag +1F4ED ; fully-qualified # 📭 open mailbox with lowered flag +1F4EE ; fully-qualified # 📮 postbox +1F5F3 FE0F ; fully-qualified # 🗳️ ballot box with ballot +1F5F3 ; non-fully-qualified # 🗳 ballot box with ballot + +# subgroup: writing +270F FE0F ; fully-qualified # ✏️ pencil +270F ; non-fully-qualified # ✏ pencil +2712 FE0F ; fully-qualified # ✒️ black nib +2712 ; non-fully-qualified # ✒ black nib +1F58B FE0F ; fully-qualified # 🖋️ fountain pen +1F58B ; non-fully-qualified # 🖋 fountain pen +1F58A FE0F ; fully-qualified # 🖊️ pen +1F58A ; non-fully-qualified # 🖊 pen +1F58C FE0F ; fully-qualified # 🖌️ paintbrush +1F58C ; non-fully-qualified # 🖌 paintbrush +1F58D FE0F ; fully-qualified # 🖍️ crayon +1F58D ; non-fully-qualified # 🖍 crayon +1F4DD ; fully-qualified # 📝 memo + +# subgroup: office +1F4BC ; fully-qualified # 💼 briefcase +1F4C1 ; fully-qualified # 📁 file folder +1F4C2 ; fully-qualified # 📂 open file folder +1F5C2 FE0F ; fully-qualified # 🗂️ card index dividers +1F5C2 ; non-fully-qualified # 🗂 card index dividers +1F4C5 ; fully-qualified # 📅 calendar +1F4C6 ; fully-qualified # 📆 tear-off calendar +1F5D2 FE0F ; fully-qualified # 🗒️ spiral notepad +1F5D2 ; non-fully-qualified # 🗒 spiral notepad +1F5D3 FE0F ; fully-qualified # 🗓️ spiral calendar +1F5D3 ; non-fully-qualified # 🗓 spiral calendar +1F4C7 ; fully-qualified # 📇 card index +1F4C8 ; fully-qualified # 📈 chart increasing +1F4C9 ; fully-qualified # 📉 chart decreasing +1F4CA ; fully-qualified # 📊 bar chart +1F4CB ; fully-qualified # 📋 clipboard +1F4CC ; fully-qualified # 📌 pushpin +1F4CD ; fully-qualified # 📍 round pushpin +1F4CE ; fully-qualified # 📎 paperclip +1F587 FE0F ; fully-qualified # 🖇️ linked paperclips +1F587 ; non-fully-qualified # 🖇 linked paperclips +1F4CF ; fully-qualified # 📏 straight ruler +1F4D0 ; fully-qualified # 📐 triangular ruler +2702 FE0F ; fully-qualified # ✂️ scissors +2702 ; non-fully-qualified # ✂ scissors +1F5C3 FE0F ; fully-qualified # 🗃️ card file box +1F5C3 ; non-fully-qualified # 🗃 card file box +1F5C4 FE0F ; fully-qualified # 🗄️ file cabinet +1F5C4 ; non-fully-qualified # 🗄 file cabinet +1F5D1 FE0F ; fully-qualified # 🗑️ wastebasket +1F5D1 ; non-fully-qualified # 🗑 wastebasket + +# subgroup: lock +1F512 ; fully-qualified # 🔒 locked +1F513 ; fully-qualified # 🔓 unlocked +1F50F ; fully-qualified # 🔏 locked with pen +1F510 ; fully-qualified # 🔐 locked with key +1F511 ; fully-qualified # 🔑 key +1F5DD FE0F ; fully-qualified # 🗝️ old key +1F5DD ; non-fully-qualified # 🗝 old key + +# subgroup: tool +1F528 ; fully-qualified # 🔨 hammer +26CF FE0F ; fully-qualified # ⛏️ pick +26CF ; non-fully-qualified # ⛏ pick +2692 FE0F ; fully-qualified # ⚒️ hammer and pick +2692 ; non-fully-qualified # ⚒ hammer and pick +1F6E0 FE0F ; fully-qualified # 🛠️ hammer and wrench +1F6E0 ; non-fully-qualified # 🛠 hammer and wrench +1F5E1 FE0F ; fully-qualified # 🗡️ dagger +1F5E1 ; non-fully-qualified # 🗡 dagger +2694 FE0F ; fully-qualified # ⚔️ crossed swords +2694 ; non-fully-qualified # ⚔ crossed swords +1F52B ; fully-qualified # 🔫 pistol +1F3F9 ; fully-qualified # 🏹 bow and arrow +1F6E1 FE0F ; fully-qualified # 🛡️ shield +1F6E1 ; non-fully-qualified # 🛡 shield +1F527 ; fully-qualified # 🔧 wrench +1F529 ; fully-qualified # 🔩 nut and bolt +2699 FE0F ; fully-qualified # ⚙️ gear +2699 ; non-fully-qualified # ⚙ gear +1F5DC FE0F ; fully-qualified # 🗜️ clamp +1F5DC ; non-fully-qualified # 🗜 clamp +2696 FE0F ; fully-qualified # ⚖️ balance scale +2696 ; non-fully-qualified # ⚖ balance scale +1F517 ; fully-qualified # 🔗 link +26D3 FE0F ; fully-qualified # ⛓️ chains +26D3 ; non-fully-qualified # ⛓ chains +1F9F0 ; fully-qualified # 🧰 toolbox +1F9F2 ; fully-qualified # 🧲 magnet + +# subgroup: science +2697 FE0F ; fully-qualified # ⚗️ alembic +2697 ; non-fully-qualified # ⚗ alembic +1F9EA ; fully-qualified # 🧪 test tube +1F9EB ; fully-qualified # 🧫 petri dish +1F9EC ; fully-qualified # 🧬 dna +1F52C ; fully-qualified # 🔬 microscope +1F52D ; fully-qualified # 🔭 telescope +1F4E1 ; fully-qualified # 📡 satellite antenna + +# subgroup: medical +1F489 ; fully-qualified # 💉 syringe +1F48A ; fully-qualified # 💊 pill + +# subgroup: household +1F6AA ; fully-qualified # 🚪 door +1F6CF FE0F ; fully-qualified # 🛏️ bed +1F6CF ; non-fully-qualified # 🛏 bed +1F6CB FE0F ; fully-qualified # 🛋️ couch and lamp +1F6CB ; non-fully-qualified # 🛋 couch and lamp +1F6BD ; fully-qualified # 🚽 toilet +1F6BF ; fully-qualified # 🚿 shower +1F6C1 ; fully-qualified # 🛁 bathtub +1F9F4 ; fully-qualified # 🧴 lotion bottle +1F9F7 ; fully-qualified # 🧷 safety pin +1F9F9 ; fully-qualified # 🧹 broom +1F9FA ; fully-qualified # 🧺 basket +1F9FB ; fully-qualified # 🧻 roll of paper +1F9FC ; fully-qualified # 🧼 soap +1F9FD ; fully-qualified # 🧽 sponge +1F9EF ; fully-qualified # 🧯 fire extinguisher +1F6D2 ; fully-qualified # 🛒 shopping cart + +# subgroup: other-object +1F6AC ; fully-qualified # 🚬 cigarette +26B0 FE0F ; fully-qualified # ⚰️ coffin +26B0 ; non-fully-qualified # ⚰ coffin +26B1 FE0F ; fully-qualified # ⚱️ funeral urn +26B1 ; non-fully-qualified # ⚱ funeral urn +1F5FF ; fully-qualified # 🗿 moai + +# Objects subtotal: 227 +# Objects subtotal: 227 w/o modifiers + +# group: Symbols + +# subgroup: transport-sign +1F3E7 ; fully-qualified # 🏧 ATM sign +1F6AE ; fully-qualified # 🚮 litter in bin sign +1F6B0 ; fully-qualified # 🚰 potable water +267F ; fully-qualified # ♿ wheelchair symbol +1F6B9 ; fully-qualified # 🚹 men’s room +1F6BA ; fully-qualified # 🚺 women’s room +1F6BB ; fully-qualified # 🚻 restroom +1F6BC ; fully-qualified # 🚼 baby symbol +1F6BE ; fully-qualified # 🚾 water closet +1F6C2 ; fully-qualified # 🛂 passport control +1F6C3 ; fully-qualified # 🛃 customs +1F6C4 ; fully-qualified # 🛄 baggage claim +1F6C5 ; fully-qualified # 🛅 left luggage + +# subgroup: warning +26A0 FE0F ; fully-qualified # ⚠️ warning +26A0 ; non-fully-qualified # ⚠ warning +1F6B8 ; fully-qualified # 🚸 children crossing +26D4 ; fully-qualified # ⛔ no entry +1F6AB ; fully-qualified # 🚫 prohibited +1F6B3 ; fully-qualified # 🚳 no bicycles +1F6AD ; fully-qualified # 🚭 no smoking +1F6AF ; fully-qualified # 🚯 no littering +1F6B1 ; fully-qualified # 🚱 non-potable water +1F6B7 ; fully-qualified # 🚷 no pedestrians +1F4F5 ; fully-qualified # 📵 no mobile phones +1F51E ; fully-qualified # 🔞 no one under eighteen +2622 FE0F ; fully-qualified # ☢️ radioactive +2622 ; non-fully-qualified # ☢ radioactive +2623 FE0F ; fully-qualified # ☣️ biohazard +2623 ; non-fully-qualified # ☣ biohazard + +# subgroup: arrow +2B06 FE0F ; fully-qualified # ⬆️ up arrow +2B06 ; non-fully-qualified # ⬆ up arrow +2197 FE0F ; fully-qualified # ↗️ up-right arrow +2197 ; non-fully-qualified # ↗ up-right arrow +27A1 FE0F ; fully-qualified # ➡️ right arrow +27A1 ; non-fully-qualified # ➡ right arrow +2198 FE0F ; fully-qualified # ↘️ down-right arrow +2198 ; non-fully-qualified # ↘ down-right arrow +2B07 FE0F ; fully-qualified # ⬇️ down arrow +2B07 ; non-fully-qualified # ⬇ down arrow +2199 FE0F ; fully-qualified # ↙️ down-left arrow +2199 ; non-fully-qualified # ↙ down-left arrow +2B05 FE0F ; fully-qualified # ⬅️ left arrow +2B05 ; non-fully-qualified # ⬅ left arrow +2196 FE0F ; fully-qualified # ↖️ up-left arrow +2196 ; non-fully-qualified # ↖ up-left arrow +2195 FE0F ; fully-qualified # ↕️ up-down arrow +2195 ; non-fully-qualified # ↕ up-down arrow +2194 FE0F ; fully-qualified # ↔️ left-right arrow +2194 ; non-fully-qualified # ↔ left-right arrow +21A9 FE0F ; fully-qualified # ↩️ right arrow curving left +21A9 ; non-fully-qualified # ↩ right arrow curving left +21AA FE0F ; fully-qualified # ↪️ left arrow curving right +21AA ; non-fully-qualified # ↪ left arrow curving right +2934 FE0F ; fully-qualified # ⤴️ right arrow curving up +2934 ; non-fully-qualified # ⤴ right arrow curving up +2935 FE0F ; fully-qualified # ⤵️ right arrow curving down +2935 ; non-fully-qualified # ⤵ right arrow curving down +1F503 ; fully-qualified # 🔃 clockwise vertical arrows +1F504 ; fully-qualified # 🔄 counterclockwise arrows button +1F519 ; fully-qualified # 🔙 BACK arrow +1F51A ; fully-qualified # 🔚 END arrow +1F51B ; fully-qualified # 🔛 ON! arrow +1F51C ; fully-qualified # 🔜 SOON arrow +1F51D ; fully-qualified # 🔝 TOP arrow + +# subgroup: religion +1F6D0 ; fully-qualified # 🛐 place of worship +269B FE0F ; fully-qualified # ⚛️ atom symbol +269B ; non-fully-qualified # ⚛ atom symbol +1F549 FE0F ; fully-qualified # 🕉️ om +1F549 ; non-fully-qualified # 🕉 om +2721 FE0F ; fully-qualified # ✡️ star of David +2721 ; non-fully-qualified # ✡ star of David +2638 FE0F ; fully-qualified # ☸️ wheel of dharma +2638 ; non-fully-qualified # ☸ wheel of dharma +262F FE0F ; fully-qualified # ☯️ yin yang +262F ; non-fully-qualified # ☯ yin yang +271D FE0F ; fully-qualified # ✝️ latin cross +271D ; non-fully-qualified # ✝ latin cross +2626 FE0F ; fully-qualified # ☦️ orthodox cross +2626 ; non-fully-qualified # ☦ orthodox cross +262A FE0F ; fully-qualified # ☪️ star and crescent +262A ; non-fully-qualified # ☪ star and crescent +262E FE0F ; fully-qualified # ☮️ peace symbol +262E ; non-fully-qualified # ☮ peace symbol +1F54E ; fully-qualified # 🕎 menorah +1F52F ; fully-qualified # 🔯 dotted six-pointed star + +# subgroup: zodiac +2648 ; fully-qualified # ♈ Aries +2649 ; fully-qualified # ♉ Taurus +264A ; fully-qualified # ♊ Gemini +264B ; fully-qualified # ♋ Cancer +264C ; fully-qualified # ♌ Leo +264D ; fully-qualified # ♍ Virgo +264E ; fully-qualified # ♎ Libra +264F ; fully-qualified # ♏ Scorpio +2650 ; fully-qualified # ♐ Sagittarius +2651 ; fully-qualified # ♑ Capricorn +2652 ; fully-qualified # ♒ Aquarius +2653 ; fully-qualified # ♓ Pisces +26CE ; fully-qualified # ⛎ Ophiuchus + +# subgroup: av-symbol +1F500 ; fully-qualified # 🔀 shuffle tracks button +1F501 ; fully-qualified # 🔁 repeat button +1F502 ; fully-qualified # 🔂 repeat single button +25B6 FE0F ; fully-qualified # ▶️ play button +25B6 ; non-fully-qualified # ▶ play button +23E9 ; fully-qualified # ⏩ fast-forward button +23ED FE0F ; fully-qualified # ⏭️ next track button +23ED ; non-fully-qualified # ⏭ next track button +23EF FE0F ; fully-qualified # ⏯️ play or pause button +23EF ; non-fully-qualified # ⏯ play or pause button +25C0 FE0F ; fully-qualified # ◀️ reverse button +25C0 ; non-fully-qualified # ◀ reverse button +23EA ; fully-qualified # ⏪ fast reverse button +23EE FE0F ; fully-qualified # ⏮️ last track button +23EE ; non-fully-qualified # ⏮ last track button +1F53C ; fully-qualified # 🔼 upwards button +23EB ; fully-qualified # ⏫ fast up button +1F53D ; fully-qualified # 🔽 downwards button +23EC ; fully-qualified # ⏬ fast down button +23F8 FE0F ; fully-qualified # ⏸️ pause button +23F8 ; non-fully-qualified # ⏸ pause button +23F9 FE0F ; fully-qualified # ⏹️ stop button +23F9 ; non-fully-qualified # ⏹ stop button +23FA FE0F ; fully-qualified # ⏺️ record button +23FA ; non-fully-qualified # ⏺ record button +23CF FE0F ; fully-qualified # ⏏️ eject button +23CF ; non-fully-qualified # ⏏ eject button +1F3A6 ; fully-qualified # 🎦 cinema +1F505 ; fully-qualified # 🔅 dim button +1F506 ; fully-qualified # 🔆 bright button +1F4F6 ; fully-qualified # 📶 antenna bars +1F4F3 ; fully-qualified # 📳 vibration mode +1F4F4 ; fully-qualified # 📴 mobile phone off + +# subgroup: other-symbol +2640 FE0F ; fully-qualified # ♀️ female sign +2640 ; non-fully-qualified # ♀ female sign +2642 FE0F ; fully-qualified # ♂️ male sign +2642 ; non-fully-qualified # ♂ male sign +2695 FE0F ; fully-qualified # ⚕️ medical symbol +2695 ; non-fully-qualified # ⚕ medical symbol +267E FE0F ; fully-qualified # ♾️ infinity +267E ; non-fully-qualified # ♾ infinity +267B FE0F ; fully-qualified # ♻️ recycling symbol +267B ; non-fully-qualified # ♻ recycling symbol +269C FE0F ; fully-qualified # ⚜️ fleur-de-lis +269C ; non-fully-qualified # ⚜ fleur-de-lis +1F531 ; fully-qualified # 🔱 trident emblem +1F4DB ; fully-qualified # 📛 name badge +1F530 ; fully-qualified # 🔰 Japanese symbol for beginner +2B55 ; fully-qualified # ⭕ heavy large circle +2705 ; fully-qualified # ✅ white heavy check mark +2611 FE0F ; fully-qualified # ☑️ ballot box with check +2611 ; non-fully-qualified # ☑ ballot box with check +2714 FE0F ; fully-qualified # ✔️ heavy check mark +2714 ; non-fully-qualified # ✔ heavy check mark +2716 FE0F ; fully-qualified # ✖️ heavy multiplication x +2716 ; non-fully-qualified # ✖ heavy multiplication x +274C ; fully-qualified # ❌ cross mark +274E ; fully-qualified # ❎ cross mark button +2795 ; fully-qualified # ➕ heavy plus sign +2796 ; fully-qualified # ➖ heavy minus sign +2797 ; fully-qualified # ➗ heavy division sign +27B0 ; fully-qualified # ➰ curly loop +27BF ; fully-qualified # ➿ double curly loop +303D FE0F ; fully-qualified # 〽️ part alternation mark +303D ; non-fully-qualified # 〽 part alternation mark +2733 FE0F ; fully-qualified # ✳️ eight-spoked asterisk +2733 ; non-fully-qualified # ✳ eight-spoked asterisk +2734 FE0F ; fully-qualified # ✴️ eight-pointed star +2734 ; non-fully-qualified # ✴ eight-pointed star +2747 FE0F ; fully-qualified # ❇️ sparkle +2747 ; non-fully-qualified # ❇ sparkle +203C FE0F ; fully-qualified # ‼️ double exclamation mark +203C ; non-fully-qualified # ‼ double exclamation mark +2049 FE0F ; fully-qualified # ⁉️ exclamation question mark +2049 ; non-fully-qualified # ⁉ exclamation question mark +2753 ; fully-qualified # ❓ question mark +2754 ; fully-qualified # ❔ white question mark +2755 ; fully-qualified # ❕ white exclamation mark +2757 ; fully-qualified # ❗ exclamation mark +3030 FE0F ; fully-qualified # 〰️ wavy dash +3030 ; non-fully-qualified # 〰 wavy dash +00A9 FE0F ; fully-qualified # ©️ copyright +00A9 ; non-fully-qualified # © copyright +00AE FE0F ; fully-qualified # ®️ registered +00AE ; non-fully-qualified # ® registered +2122 FE0F ; fully-qualified # ™️ trade mark +2122 ; non-fully-qualified # ™ trade mark + +# subgroup: keycap +0023 FE0F 20E3 ; fully-qualified # #️⃣ keycap: # +0023 20E3 ; non-fully-qualified # #⃣ keycap: # +002A FE0F 20E3 ; fully-qualified # *️⃣ keycap: * +002A 20E3 ; non-fully-qualified # *⃣ keycap: * +0030 FE0F 20E3 ; fully-qualified # 0️⃣ keycap: 0 +0030 20E3 ; non-fully-qualified # 0⃣ keycap: 0 +0031 FE0F 20E3 ; fully-qualified # 1️⃣ keycap: 1 +0031 20E3 ; non-fully-qualified # 1⃣ keycap: 1 +0032 FE0F 20E3 ; fully-qualified # 2️⃣ keycap: 2 +0032 20E3 ; non-fully-qualified # 2⃣ keycap: 2 +0033 FE0F 20E3 ; fully-qualified # 3️⃣ keycap: 3 +0033 20E3 ; non-fully-qualified # 3⃣ keycap: 3 +0034 FE0F 20E3 ; fully-qualified # 4️⃣ keycap: 4 +0034 20E3 ; non-fully-qualified # 4⃣ keycap: 4 +0035 FE0F 20E3 ; fully-qualified # 5️⃣ keycap: 5 +0035 20E3 ; non-fully-qualified # 5⃣ keycap: 5 +0036 FE0F 20E3 ; fully-qualified # 6️⃣ keycap: 6 +0036 20E3 ; non-fully-qualified # 6⃣ keycap: 6 +0037 FE0F 20E3 ; fully-qualified # 7️⃣ keycap: 7 +0037 20E3 ; non-fully-qualified # 7⃣ keycap: 7 +0038 FE0F 20E3 ; fully-qualified # 8️⃣ keycap: 8 +0038 20E3 ; non-fully-qualified # 8⃣ keycap: 8 +0039 FE0F 20E3 ; fully-qualified # 9️⃣ keycap: 9 +0039 20E3 ; non-fully-qualified # 9⃣ keycap: 9 +1F51F ; fully-qualified # 🔟 keycap: 10 + +# subgroup: alphanum +1F4AF ; fully-qualified # 💯 hundred points +1F520 ; fully-qualified # 🔠 input latin uppercase +1F521 ; fully-qualified # 🔡 input latin lowercase +1F522 ; fully-qualified # 🔢 input numbers +1F523 ; fully-qualified # 🔣 input symbols +1F524 ; fully-qualified # 🔤 input latin letters +1F170 FE0F ; fully-qualified # 🅰️ A button (blood type) +1F170 ; non-fully-qualified # 🅰 A button (blood type) +1F18E ; fully-qualified # 🆎 AB button (blood type) +1F171 FE0F ; fully-qualified # 🅱️ B button (blood type) +1F171 ; non-fully-qualified # 🅱 B button (blood type) +1F191 ; fully-qualified # 🆑 CL button +1F192 ; fully-qualified # 🆒 COOL button +1F193 ; fully-qualified # 🆓 FREE button +2139 FE0F ; fully-qualified # ℹ️ information +2139 ; non-fully-qualified # ℹ information +1F194 ; fully-qualified # 🆔 ID button +24C2 FE0F ; fully-qualified # Ⓜ️ circled M +24C2 ; non-fully-qualified # Ⓜ circled M +1F195 ; fully-qualified # 🆕 NEW button +1F196 ; fully-qualified # 🆖 NG button +1F17E FE0F ; fully-qualified # 🅾️ O button (blood type) +1F17E ; non-fully-qualified # 🅾 O button (blood type) +1F197 ; fully-qualified # 🆗 OK button +1F17F FE0F ; fully-qualified # 🅿️ P button +1F17F ; non-fully-qualified # 🅿 P button +1F198 ; fully-qualified # 🆘 SOS button +1F199 ; fully-qualified # 🆙 UP! button +1F19A ; fully-qualified # 🆚 VS button +1F201 ; fully-qualified # 🈁 Japanese “here” button +1F202 FE0F ; fully-qualified # 🈂️ Japanese “service charge” button +1F202 ; non-fully-qualified # 🈂 Japanese “service charge” button +1F237 FE0F ; fully-qualified # 🈷️ Japanese “monthly amount” button +1F237 ; non-fully-qualified # 🈷 Japanese “monthly amount” button +1F236 ; fully-qualified # 🈶 Japanese “not free of charge” button +1F22F ; fully-qualified # 🈯 Japanese “reserved” button +1F250 ; fully-qualified # 🉐 Japanese “bargain” button +1F239 ; fully-qualified # 🈹 Japanese “discount” button +1F21A ; fully-qualified # 🈚 Japanese “free of charge” button +1F232 ; fully-qualified # 🈲 Japanese “prohibited” button +1F251 ; fully-qualified # 🉑 Japanese “acceptable” button +1F238 ; fully-qualified # 🈸 Japanese “application” button +1F234 ; fully-qualified # 🈴 Japanese “passing grade” button +1F233 ; fully-qualified # 🈳 Japanese “vacancy” button +3297 FE0F ; fully-qualified # ㊗️ Japanese “congratulations” button +3297 ; non-fully-qualified # ㊗ Japanese “congratulations” button +3299 FE0F ; fully-qualified # ㊙️ Japanese “secret” button +3299 ; non-fully-qualified # ㊙ Japanese “secret” button +1F23A ; fully-qualified # 🈺 Japanese “open for business” button +1F235 ; fully-qualified # 🈵 Japanese “no vacancy” button + +# subgroup: geometric +25AA FE0F ; fully-qualified # ▪️ black small square +25AA ; non-fully-qualified # ▪ black small square +25AB FE0F ; fully-qualified # ▫️ white small square +25AB ; non-fully-qualified # ▫ white small square +25FB FE0F ; fully-qualified # ◻️ white medium square +25FB ; non-fully-qualified # ◻ white medium square +25FC FE0F ; fully-qualified # ◼️ black medium square +25FC ; non-fully-qualified # ◼ black medium square +25FD ; fully-qualified # ◽ white medium-small square +25FE ; fully-qualified # ◾ black medium-small square +2B1B ; fully-qualified # ⬛ black large square +2B1C ; fully-qualified # ⬜ white large square +1F536 ; fully-qualified # 🔶 large orange diamond +1F537 ; fully-qualified # 🔷 large blue diamond +1F538 ; fully-qualified # 🔸 small orange diamond +1F539 ; fully-qualified # 🔹 small blue diamond +1F53A ; fully-qualified # 🔺 red triangle pointed up +1F53B ; fully-qualified # 🔻 red triangle pointed down +1F4A0 ; fully-qualified # 💠 diamond with a dot +1F518 ; fully-qualified # 🔘 radio button +1F532 ; fully-qualified # 🔲 black square button +1F533 ; fully-qualified # 🔳 white square button +26AA ; fully-qualified # ⚪ white circle +26AB ; fully-qualified # ⚫ black circle +1F534 ; fully-qualified # 🔴 red circle +1F535 ; fully-qualified # 🔵 blue circle + +# Symbols subtotal: 286 +# Symbols subtotal: 286 w/o modifiers + +# group: Flags + +# subgroup: flag +1F3C1 ; fully-qualified # 🏁 chequered flag +1F6A9 ; fully-qualified # 🚩 triangular flag +1F38C ; fully-qualified # 🎌 crossed flags +1F3F4 ; fully-qualified # 🏴 black flag +1F3F3 FE0F ; fully-qualified # 🏳️ white flag +1F3F3 ; non-fully-qualified # 🏳 white flag +1F3F3 FE0F 200D 1F308 ; fully-qualified # 🏳️‍🌈 rainbow flag +1F3F3 200D 1F308 ; non-fully-qualified # 🏳‍🌈 rainbow flag +1F3F4 200D 2620 FE0F ; fully-qualified # 🏴‍☠️ pirate flag +1F3F4 200D 2620 ; non-fully-qualified # 🏴‍☠ pirate flag + +# subgroup: country-flag +1F1E6 1F1E8 ; fully-qualified # 🇦🇨 Ascension Island +1F1E6 1F1E9 ; fully-qualified # 🇦🇩 Andorra +1F1E6 1F1EA ; fully-qualified # 🇦🇪 United Arab Emirates +1F1E6 1F1EB ; fully-qualified # 🇦🇫 Afghanistan +1F1E6 1F1EC ; fully-qualified # 🇦🇬 Antigua & Barbuda +1F1E6 1F1EE ; fully-qualified # 🇦🇮 Anguilla +1F1E6 1F1F1 ; fully-qualified # 🇦🇱 Albania +1F1E6 1F1F2 ; fully-qualified # 🇦🇲 Armenia +1F1E6 1F1F4 ; fully-qualified # 🇦🇴 Angola +1F1E6 1F1F6 ; fully-qualified # 🇦🇶 Antarctica +1F1E6 1F1F7 ; fully-qualified # 🇦🇷 Argentina +1F1E6 1F1F8 ; fully-qualified # 🇦🇸 American Samoa +1F1E6 1F1F9 ; fully-qualified # 🇦🇹 Austria +1F1E6 1F1FA ; fully-qualified # 🇦🇺 Australia +1F1E6 1F1FC ; fully-qualified # 🇦🇼 Aruba +1F1E6 1F1FD ; fully-qualified # 🇦🇽 Åland Islands +1F1E6 1F1FF ; fully-qualified # 🇦🇿 Azerbaijan +1F1E7 1F1E6 ; fully-qualified # 🇧🇦 Bosnia & Herzegovina +1F1E7 1F1E7 ; fully-qualified # 🇧🇧 Barbados +1F1E7 1F1E9 ; fully-qualified # 🇧🇩 Bangladesh +1F1E7 1F1EA ; fully-qualified # 🇧🇪 Belgium +1F1E7 1F1EB ; fully-qualified # 🇧🇫 Burkina Faso +1F1E7 1F1EC ; fully-qualified # 🇧🇬 Bulgaria +1F1E7 1F1ED ; fully-qualified # 🇧🇭 Bahrain +1F1E7 1F1EE ; fully-qualified # 🇧🇮 Burundi +1F1E7 1F1EF ; fully-qualified # 🇧🇯 Benin +1F1E7 1F1F1 ; fully-qualified # 🇧🇱 St. Barthélemy +1F1E7 1F1F2 ; fully-qualified # 🇧🇲 Bermuda +1F1E7 1F1F3 ; fully-qualified # 🇧🇳 Brunei +1F1E7 1F1F4 ; fully-qualified # 🇧🇴 Bolivia +1F1E7 1F1F6 ; fully-qualified # 🇧🇶 Caribbean Netherlands +1F1E7 1F1F7 ; fully-qualified # 🇧🇷 Brazil +1F1E7 1F1F8 ; fully-qualified # 🇧🇸 Bahamas +1F1E7 1F1F9 ; fully-qualified # 🇧🇹 Bhutan +1F1E7 1F1FB ; fully-qualified # 🇧🇻 Bouvet Island +1F1E7 1F1FC ; fully-qualified # 🇧🇼 Botswana +1F1E7 1F1FE ; fully-qualified # 🇧🇾 Belarus +1F1E7 1F1FF ; fully-qualified # 🇧🇿 Belize +1F1E8 1F1E6 ; fully-qualified # 🇨🇦 Canada +1F1E8 1F1E8 ; fully-qualified # 🇨🇨 Cocos (Keeling) Islands +1F1E8 1F1E9 ; fully-qualified # 🇨🇩 Congo - Kinshasa +1F1E8 1F1EB ; fully-qualified # 🇨🇫 Central African Republic +1F1E8 1F1EC ; fully-qualified # 🇨🇬 Congo - Brazzaville +1F1E8 1F1ED ; fully-qualified # 🇨🇭 Switzerland +1F1E8 1F1EE ; fully-qualified # 🇨🇮 Côte d’Ivoire +1F1E8 1F1F0 ; fully-qualified # 🇨🇰 Cook Islands +1F1E8 1F1F1 ; fully-qualified # 🇨🇱 Chile +1F1E8 1F1F2 ; fully-qualified # 🇨🇲 Cameroon +1F1E8 1F1F3 ; fully-qualified # 🇨🇳 China +1F1E8 1F1F4 ; fully-qualified # 🇨🇴 Colombia +1F1E8 1F1F5 ; fully-qualified # 🇨🇵 Clipperton Island +1F1E8 1F1F7 ; fully-qualified # 🇨🇷 Costa Rica +1F1E8 1F1FA ; fully-qualified # 🇨🇺 Cuba +1F1E8 1F1FB ; fully-qualified # 🇨🇻 Cape Verde +1F1E8 1F1FC ; fully-qualified # 🇨🇼 Curaçao +1F1E8 1F1FD ; fully-qualified # 🇨🇽 Christmas Island +1F1E8 1F1FE ; fully-qualified # 🇨🇾 Cyprus +1F1E8 1F1FF ; fully-qualified # 🇨🇿 Czechia +1F1E9 1F1EA ; fully-qualified # 🇩🇪 Germany +1F1E9 1F1EC ; fully-qualified # 🇩🇬 Diego Garcia +1F1E9 1F1EF ; fully-qualified # 🇩🇯 Djibouti +1F1E9 1F1F0 ; fully-qualified # 🇩🇰 Denmark +1F1E9 1F1F2 ; fully-qualified # 🇩🇲 Dominica +1F1E9 1F1F4 ; fully-qualified # 🇩🇴 Dominican Republic +1F1E9 1F1FF ; fully-qualified # 🇩🇿 Algeria +1F1EA 1F1E6 ; fully-qualified # 🇪🇦 Ceuta & Melilla +1F1EA 1F1E8 ; fully-qualified # 🇪🇨 Ecuador +1F1EA 1F1EA ; fully-qualified # 🇪🇪 Estonia +1F1EA 1F1EC ; fully-qualified # 🇪🇬 Egypt +1F1EA 1F1ED ; fully-qualified # 🇪🇭 Western Sahara +1F1EA 1F1F7 ; fully-qualified # 🇪🇷 Eritrea +1F1EA 1F1F8 ; fully-qualified # 🇪🇸 Spain +1F1EA 1F1F9 ; fully-qualified # 🇪🇹 Ethiopia +1F1EA 1F1FA ; fully-qualified # 🇪🇺 European Union +1F1EB 1F1EE ; fully-qualified # 🇫🇮 Finland +1F1EB 1F1EF ; fully-qualified # 🇫🇯 Fiji +1F1EB 1F1F0 ; fully-qualified # 🇫🇰 Falkland Islands +1F1EB 1F1F2 ; fully-qualified # 🇫🇲 Micronesia +1F1EB 1F1F4 ; fully-qualified # 🇫🇴 Faroe Islands +1F1EB 1F1F7 ; fully-qualified # 🇫🇷 France +1F1EC 1F1E6 ; fully-qualified # 🇬🇦 Gabon +1F1EC 1F1E7 ; fully-qualified # 🇬🇧 United Kingdom +1F1EC 1F1E9 ; fully-qualified # 🇬🇩 Grenada +1F1EC 1F1EA ; fully-qualified # 🇬🇪 Georgia +1F1EC 1F1EB ; fully-qualified # 🇬🇫 French Guiana +1F1EC 1F1EC ; fully-qualified # 🇬🇬 Guernsey +1F1EC 1F1ED ; fully-qualified # 🇬🇭 Ghana +1F1EC 1F1EE ; fully-qualified # 🇬🇮 Gibraltar +1F1EC 1F1F1 ; fully-qualified # 🇬🇱 Greenland +1F1EC 1F1F2 ; fully-qualified # 🇬🇲 Gambia +1F1EC 1F1F3 ; fully-qualified # 🇬🇳 Guinea +1F1EC 1F1F5 ; fully-qualified # 🇬🇵 Guadeloupe +1F1EC 1F1F6 ; fully-qualified # 🇬🇶 Equatorial Guinea +1F1EC 1F1F7 ; fully-qualified # 🇬🇷 Greece +1F1EC 1F1F8 ; fully-qualified # 🇬🇸 South Georgia & South Sandwich Islands +1F1EC 1F1F9 ; fully-qualified # 🇬🇹 Guatemala +1F1EC 1F1FA ; fully-qualified # 🇬🇺 Guam +1F1EC 1F1FC ; fully-qualified # 🇬🇼 Guinea-Bissau +1F1EC 1F1FE ; fully-qualified # 🇬🇾 Guyana +1F1ED 1F1F0 ; fully-qualified # 🇭🇰 Hong Kong SAR China +1F1ED 1F1F2 ; fully-qualified # 🇭🇲 Heard & McDonald Islands +1F1ED 1F1F3 ; fully-qualified # 🇭🇳 Honduras +1F1ED 1F1F7 ; fully-qualified # 🇭🇷 Croatia +1F1ED 1F1F9 ; fully-qualified # 🇭🇹 Haiti +1F1ED 1F1FA ; fully-qualified # 🇭🇺 Hungary +1F1EE 1F1E8 ; fully-qualified # 🇮🇨 Canary Islands +1F1EE 1F1E9 ; fully-qualified # 🇮🇩 Indonesia +1F1EE 1F1EA ; fully-qualified # 🇮🇪 Ireland +1F1EE 1F1F1 ; fully-qualified # 🇮🇱 Israel +1F1EE 1F1F2 ; fully-qualified # 🇮🇲 Isle of Man +1F1EE 1F1F3 ; fully-qualified # 🇮🇳 India +1F1EE 1F1F4 ; fully-qualified # 🇮🇴 British Indian Ocean Territory +1F1EE 1F1F6 ; fully-qualified # 🇮🇶 Iraq +1F1EE 1F1F7 ; fully-qualified # 🇮🇷 Iran +1F1EE 1F1F8 ; fully-qualified # 🇮🇸 Iceland +1F1EE 1F1F9 ; fully-qualified # 🇮🇹 Italy +1F1EF 1F1EA ; fully-qualified # 🇯🇪 Jersey +1F1EF 1F1F2 ; fully-qualified # 🇯🇲 Jamaica +1F1EF 1F1F4 ; fully-qualified # 🇯🇴 Jordan +1F1EF 1F1F5 ; fully-qualified # 🇯🇵 Japan +1F1F0 1F1EA ; fully-qualified # 🇰🇪 Kenya +1F1F0 1F1EC ; fully-qualified # 🇰🇬 Kyrgyzstan +1F1F0 1F1ED ; fully-qualified # 🇰🇭 Cambodia +1F1F0 1F1EE ; fully-qualified # 🇰🇮 Kiribati +1F1F0 1F1F2 ; fully-qualified # 🇰🇲 Comoros +1F1F0 1F1F3 ; fully-qualified # 🇰🇳 St. Kitts & Nevis +1F1F0 1F1F5 ; fully-qualified # 🇰🇵 North Korea +1F1F0 1F1F7 ; fully-qualified # 🇰🇷 South Korea +1F1F0 1F1FC ; fully-qualified # 🇰🇼 Kuwait +1F1F0 1F1FE ; fully-qualified # 🇰🇾 Cayman Islands +1F1F0 1F1FF ; fully-qualified # 🇰🇿 Kazakhstan +1F1F1 1F1E6 ; fully-qualified # 🇱🇦 Laos +1F1F1 1F1E7 ; fully-qualified # 🇱🇧 Lebanon +1F1F1 1F1E8 ; fully-qualified # 🇱🇨 St. Lucia +1F1F1 1F1EE ; fully-qualified # 🇱🇮 Liechtenstein +1F1F1 1F1F0 ; fully-qualified # 🇱🇰 Sri Lanka +1F1F1 1F1F7 ; fully-qualified # 🇱🇷 Liberia +1F1F1 1F1F8 ; fully-qualified # 🇱🇸 Lesotho +1F1F1 1F1F9 ; fully-qualified # 🇱🇹 Lithuania +1F1F1 1F1FA ; fully-qualified # 🇱🇺 Luxembourg +1F1F1 1F1FB ; fully-qualified # 🇱🇻 Latvia +1F1F1 1F1FE ; fully-qualified # 🇱🇾 Libya +1F1F2 1F1E6 ; fully-qualified # 🇲🇦 Morocco +1F1F2 1F1E8 ; fully-qualified # 🇲🇨 Monaco +1F1F2 1F1E9 ; fully-qualified # 🇲🇩 Moldova +1F1F2 1F1EA ; fully-qualified # 🇲🇪 Montenegro +1F1F2 1F1EB ; fully-qualified # 🇲🇫 St. Martin +1F1F2 1F1EC ; fully-qualified # 🇲🇬 Madagascar +1F1F2 1F1ED ; fully-qualified # 🇲🇭 Marshall Islands +1F1F2 1F1F0 ; fully-qualified # 🇲🇰 Macedonia +1F1F2 1F1F1 ; fully-qualified # 🇲🇱 Mali +1F1F2 1F1F2 ; fully-qualified # 🇲🇲 Myanmar (Burma) +1F1F2 1F1F3 ; fully-qualified # 🇲🇳 Mongolia +1F1F2 1F1F4 ; fully-qualified # 🇲🇴 Macau SAR China +1F1F2 1F1F5 ; fully-qualified # 🇲🇵 Northern Mariana Islands +1F1F2 1F1F6 ; fully-qualified # 🇲🇶 Martinique +1F1F2 1F1F7 ; fully-qualified # 🇲🇷 Mauritania +1F1F2 1F1F8 ; fully-qualified # 🇲🇸 Montserrat +1F1F2 1F1F9 ; fully-qualified # 🇲🇹 Malta +1F1F2 1F1FA ; fully-qualified # 🇲🇺 Mauritius +1F1F2 1F1FB ; fully-qualified # 🇲🇻 Maldives +1F1F2 1F1FC ; fully-qualified # 🇲🇼 Malawi +1F1F2 1F1FD ; fully-qualified # 🇲🇽 Mexico +1F1F2 1F1FE ; fully-qualified # 🇲🇾 Malaysia +1F1F2 1F1FF ; fully-qualified # 🇲🇿 Mozambique +1F1F3 1F1E6 ; fully-qualified # 🇳🇦 Namibia +1F1F3 1F1E8 ; fully-qualified # 🇳🇨 New Caledonia +1F1F3 1F1EA ; fully-qualified # 🇳🇪 Niger +1F1F3 1F1EB ; fully-qualified # 🇳🇫 Norfolk Island +1F1F3 1F1EC ; fully-qualified # 🇳🇬 Nigeria +1F1F3 1F1EE ; fully-qualified # 🇳🇮 Nicaragua +1F1F3 1F1F1 ; fully-qualified # 🇳🇱 Netherlands +1F1F3 1F1F4 ; fully-qualified # 🇳🇴 Norway +1F1F3 1F1F5 ; fully-qualified # 🇳🇵 Nepal +1F1F3 1F1F7 ; fully-qualified # 🇳🇷 Nauru +1F1F3 1F1FA ; fully-qualified # 🇳🇺 Niue +1F1F3 1F1FF ; fully-qualified # 🇳🇿 New Zealand +1F1F4 1F1F2 ; fully-qualified # 🇴🇲 Oman +1F1F5 1F1E6 ; fully-qualified # 🇵🇦 Panama +1F1F5 1F1EA ; fully-qualified # 🇵🇪 Peru +1F1F5 1F1EB ; fully-qualified # 🇵🇫 French Polynesia +1F1F5 1F1EC ; fully-qualified # 🇵🇬 Papua New Guinea +1F1F5 1F1ED ; fully-qualified # 🇵🇭 Philippines +1F1F5 1F1F0 ; fully-qualified # 🇵🇰 Pakistan +1F1F5 1F1F1 ; fully-qualified # 🇵🇱 Poland +1F1F5 1F1F2 ; fully-qualified # 🇵🇲 St. Pierre & Miquelon +1F1F5 1F1F3 ; fully-qualified # 🇵🇳 Pitcairn Islands +1F1F5 1F1F7 ; fully-qualified # 🇵🇷 Puerto Rico +1F1F5 1F1F8 ; fully-qualified # 🇵🇸 Palestinian Territories +1F1F5 1F1F9 ; fully-qualified # 🇵🇹 Portugal +1F1F5 1F1FC ; fully-qualified # 🇵🇼 Palau +1F1F5 1F1FE ; fully-qualified # 🇵🇾 Paraguay +1F1F6 1F1E6 ; fully-qualified # 🇶🇦 Qatar +1F1F7 1F1EA ; fully-qualified # 🇷🇪 Réunion +1F1F7 1F1F4 ; fully-qualified # 🇷🇴 Romania +1F1F7 1F1F8 ; fully-qualified # 🇷🇸 Serbia +1F1F7 1F1FA ; fully-qualified # 🇷🇺 Russia +1F1F7 1F1FC ; fully-qualified # 🇷🇼 Rwanda +1F1F8 1F1E6 ; fully-qualified # 🇸🇦 Saudi Arabia +1F1F8 1F1E7 ; fully-qualified # 🇸🇧 Solomon Islands +1F1F8 1F1E8 ; fully-qualified # 🇸🇨 Seychelles +1F1F8 1F1E9 ; fully-qualified # 🇸🇩 Sudan +1F1F8 1F1EA ; fully-qualified # 🇸🇪 Sweden +1F1F8 1F1EC ; fully-qualified # 🇸🇬 Singapore +1F1F8 1F1ED ; fully-qualified # 🇸🇭 St. Helena +1F1F8 1F1EE ; fully-qualified # 🇸🇮 Slovenia +1F1F8 1F1EF ; fully-qualified # 🇸🇯 Svalbard & Jan Mayen +1F1F8 1F1F0 ; fully-qualified # 🇸🇰 Slovakia +1F1F8 1F1F1 ; fully-qualified # 🇸🇱 Sierra Leone +1F1F8 1F1F2 ; fully-qualified # 🇸🇲 San Marino +1F1F8 1F1F3 ; fully-qualified # 🇸🇳 Senegal +1F1F8 1F1F4 ; fully-qualified # 🇸🇴 Somalia +1F1F8 1F1F7 ; fully-qualified # 🇸🇷 Suriname +1F1F8 1F1F8 ; fully-qualified # 🇸🇸 South Sudan +1F1F8 1F1F9 ; fully-qualified # 🇸🇹 São Tomé & Príncipe +1F1F8 1F1FB ; fully-qualified # 🇸🇻 El Salvador +1F1F8 1F1FD ; fully-qualified # 🇸🇽 Sint Maarten +1F1F8 1F1FE ; fully-qualified # 🇸🇾 Syria +1F1F8 1F1FF ; fully-qualified # 🇸🇿 Swaziland +1F1F9 1F1E6 ; fully-qualified # 🇹🇦 Tristan da Cunha +1F1F9 1F1E8 ; fully-qualified # 🇹🇨 Turks & Caicos Islands +1F1F9 1F1E9 ; fully-qualified # 🇹🇩 Chad +1F1F9 1F1EB ; fully-qualified # 🇹🇫 French Southern Territories +1F1F9 1F1EC ; fully-qualified # 🇹🇬 Togo +1F1F9 1F1ED ; fully-qualified # 🇹🇭 Thailand +1F1F9 1F1EF ; fully-qualified # 🇹🇯 Tajikistan +1F1F9 1F1F0 ; fully-qualified # 🇹🇰 Tokelau +1F1F9 1F1F1 ; fully-qualified # 🇹🇱 Timor-Leste +1F1F9 1F1F2 ; fully-qualified # 🇹🇲 Turkmenistan +1F1F9 1F1F3 ; fully-qualified # 🇹🇳 Tunisia +1F1F9 1F1F4 ; fully-qualified # 🇹🇴 Tonga +1F1F9 1F1F7 ; fully-qualified # 🇹🇷 Turkey +1F1F9 1F1F9 ; fully-qualified # 🇹🇹 Trinidad & Tobago +1F1F9 1F1FB ; fully-qualified # 🇹🇻 Tuvalu +1F1F9 1F1FC ; fully-qualified # 🇹🇼 Taiwan +1F1F9 1F1FF ; fully-qualified # 🇹🇿 Tanzania +1F1FA 1F1E6 ; fully-qualified # 🇺🇦 Ukraine +1F1FA 1F1EC ; fully-qualified # 🇺🇬 Uganda +1F1FA 1F1F2 ; fully-qualified # 🇺🇲 U.S. Outlying Islands +1F1FA 1F1F3 ; fully-qualified # 🇺🇳 United Nations +1F1FA 1F1F8 ; fully-qualified # 🇺🇸 United States +1F1FA 1F1FE ; fully-qualified # 🇺🇾 Uruguay +1F1FA 1F1FF ; fully-qualified # 🇺🇿 Uzbekistan +1F1FB 1F1E6 ; fully-qualified # 🇻🇦 Vatican City +1F1FB 1F1E8 ; fully-qualified # 🇻🇨 St. Vincent & Grenadines +1F1FB 1F1EA ; fully-qualified # 🇻🇪 Venezuela +1F1FB 1F1EC ; fully-qualified # 🇻🇬 British Virgin Islands +1F1FB 1F1EE ; fully-qualified # 🇻🇮 U.S. Virgin Islands +1F1FB 1F1F3 ; fully-qualified # 🇻🇳 Vietnam +1F1FB 1F1FA ; fully-qualified # 🇻🇺 Vanuatu +1F1FC 1F1EB ; fully-qualified # 🇼🇫 Wallis & Futuna +1F1FC 1F1F8 ; fully-qualified # 🇼🇸 Samoa +1F1FD 1F1F0 ; fully-qualified # 🇽🇰 Kosovo +1F1FE 1F1EA ; fully-qualified # 🇾🇪 Yemen +1F1FE 1F1F9 ; fully-qualified # 🇾🇹 Mayotte +1F1FF 1F1E6 ; fully-qualified # 🇿🇦 South Africa +1F1FF 1F1F2 ; fully-qualified # 🇿🇲 Zambia +1F1FF 1F1FC ; fully-qualified # 🇿🇼 Zimbabwe + +# subgroup: subdivision-flag +1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; fully-qualified # 🏴󠁧󠁢󠁥󠁮󠁧󠁿 England +1F3F4 E0067 E0062 E0073 E0063 E0074 E007F ; fully-qualified # 🏴󠁧󠁢󠁳󠁣󠁴󠁿 Scotland +1F3F4 E0067 E0062 E0077 E006C E0073 E007F ; fully-qualified # 🏴󠁧󠁢󠁷󠁬󠁳󠁿 Wales + +# Flags subtotal: 271 +# Flags subtotal: 271 w/o modifiers + +#EOF diff --git a/datastructures-validation/src/test/resources/emoji-test-12.txt b/datastructures-validation/src/test/resources/emoji-test-12.txt new file mode 100644 index 0000000..5f2f43c --- /dev/null +++ b/datastructures-validation/src/test/resources/emoji-test-12.txt @@ -0,0 +1,4119 @@ +# emoji-test.txt +# Date: 2019-01-27, 15:43:01 GMT +# © 2019 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see http://www.unicode.org/terms_of_use.html +# +# Emoji Keyboard/Display Test Data for UTS #51 +# Version: 12.0 +# +# For documentation and usage, see http://www.unicode.org/reports/tr51 +# +# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. +# Format: code points; status # emoji name +# Code points — list of one or more hex code points, separated by spaces +# Status +# component — an Emoji_Component, +# excluding Regional_Indicators, ASCII, and non-Emoji. +# fully-qualified — a fully-qualified emoji (see ED-18 in UTS #51), +# excluding Emoji_Component +# minimally-qualified — a minimally-qualified emoji (see ED-18a in UTS #51) +# unqualified — a unqualified emoji (See ED-19 in UTS #51) +# Notes: +# • This includes the emoji components that need emoji presentation (skin tone and hair) +# when isolated, but omits the components that need not have an emoji +# presentation when isolated. +# • The RGI set is covered by the listed fully-qualified emoji. +# • The listed minimally-qualified and unqualified cover all cases where an +# element of the RGI set is missing one or more emoji presentation selectors. +# • The file is in CLDR order, not codepoint order. This is recommended (but not required!) for keyboard palettes. +# • The groups and subgroups are illustrative. See the Emoji Order chart for more information. + + +# group: Smileys & Emotion + +# subgroup: face-smiling +1F600 ; fully-qualified # 😀 grinning face +1F603 ; fully-qualified # 😃 grinning face with big eyes +1F604 ; fully-qualified # 😄 grinning face with smiling eyes +1F601 ; fully-qualified # 😁 beaming face with smiling eyes +1F606 ; fully-qualified # 😆 grinning squinting face +1F605 ; fully-qualified # 😅 grinning face with sweat +1F923 ; fully-qualified # 🤣 rolling on the floor laughing +1F602 ; fully-qualified # 😂 face with tears of joy +1F642 ; fully-qualified # 🙂 slightly smiling face +1F643 ; fully-qualified # 🙃 upside-down face +1F609 ; fully-qualified # 😉 winking face +1F60A ; fully-qualified # 😊 smiling face with smiling eyes +1F607 ; fully-qualified # 😇 smiling face with halo + +# subgroup: face-affection +1F970 ; fully-qualified # 🥰 smiling face with hearts +1F60D ; fully-qualified # 😍 smiling face with heart-eyes +1F929 ; fully-qualified # 🤩 star-struck +1F618 ; fully-qualified # 😘 face blowing a kiss +1F617 ; fully-qualified # 😗 kissing face +263A FE0F ; fully-qualified # ☺️ smiling face +263A ; unqualified # ☺ smiling face +1F61A ; fully-qualified # 😚 kissing face with closed eyes +1F619 ; fully-qualified # 😙 kissing face with smiling eyes + +# subgroup: face-tongue +1F60B ; fully-qualified # 😋 face savoring food +1F61B ; fully-qualified # 😛 face with tongue +1F61C ; fully-qualified # 😜 winking face with tongue +1F92A ; fully-qualified # 🤪 zany face +1F61D ; fully-qualified # 😝 squinting face with tongue +1F911 ; fully-qualified # 🤑 money-mouth face + +# subgroup: face-hand +1F917 ; fully-qualified # 🤗 hugging face +1F92D ; fully-qualified # 🤭 face with hand over mouth +1F92B ; fully-qualified # 🤫 shushing face +1F914 ; fully-qualified # 🤔 thinking face + +# subgroup: face-neutral-skeptical +1F910 ; fully-qualified # 🤐 zipper-mouth face +1F928 ; fully-qualified # 🤨 face with raised eyebrow +1F610 ; fully-qualified # 😐 neutral face +1F611 ; fully-qualified # 😑 expressionless face +1F636 ; fully-qualified # 😶 face without mouth +1F60F ; fully-qualified # 😏 smirking face +1F612 ; fully-qualified # 😒 unamused face +1F644 ; fully-qualified # 🙄 face with rolling eyes +1F62C ; fully-qualified # 😬 grimacing face +1F925 ; fully-qualified # 🤥 lying face + +# subgroup: face-sleepy +1F60C ; fully-qualified # 😌 relieved face +1F614 ; fully-qualified # 😔 pensive face +1F62A ; fully-qualified # 😪 sleepy face +1F924 ; fully-qualified # 🤤 drooling face +1F634 ; fully-qualified # 😴 sleeping face + +# subgroup: face-unwell +1F637 ; fully-qualified # 😷 face with medical mask +1F912 ; fully-qualified # 🤒 face with thermometer +1F915 ; fully-qualified # 🤕 face with head-bandage +1F922 ; fully-qualified # 🤢 nauseated face +1F92E ; fully-qualified # 🤮 face vomiting +1F927 ; fully-qualified # 🤧 sneezing face +1F975 ; fully-qualified # 🥵 hot face +1F976 ; fully-qualified # 🥶 cold face +1F974 ; fully-qualified # 🥴 woozy face +1F635 ; fully-qualified # 😵 dizzy face +1F92F ; fully-qualified # 🤯 exploding head + +# subgroup: face-hat +1F920 ; fully-qualified # 🤠 cowboy hat face +1F973 ; fully-qualified # 🥳 partying face + +# subgroup: face-glasses +1F60E ; fully-qualified # 😎 smiling face with sunglasses +1F913 ; fully-qualified # 🤓 nerd face +1F9D0 ; fully-qualified # 🧐 face with monocle + +# subgroup: face-concerned +1F615 ; fully-qualified # 😕 confused face +1F61F ; fully-qualified # 😟 worried face +1F641 ; fully-qualified # 🙁 slightly frowning face +2639 FE0F ; fully-qualified # ☹️ frowning face +2639 ; unqualified # ☹ frowning face +1F62E ; fully-qualified # 😮 face with open mouth +1F62F ; fully-qualified # 😯 hushed face +1F632 ; fully-qualified # 😲 astonished face +1F633 ; fully-qualified # 😳 flushed face +1F97A ; fully-qualified # 🥺 pleading face +1F626 ; fully-qualified # 😦 frowning face with open mouth +1F627 ; fully-qualified # 😧 anguished face +1F628 ; fully-qualified # 😨 fearful face +1F630 ; fully-qualified # 😰 anxious face with sweat +1F625 ; fully-qualified # 😥 sad but relieved face +1F622 ; fully-qualified # 😢 crying face +1F62D ; fully-qualified # 😭 loudly crying face +1F631 ; fully-qualified # 😱 face screaming in fear +1F616 ; fully-qualified # 😖 confounded face +1F623 ; fully-qualified # 😣 persevering face +1F61E ; fully-qualified # 😞 disappointed face +1F613 ; fully-qualified # 😓 downcast face with sweat +1F629 ; fully-qualified # 😩 weary face +1F62B ; fully-qualified # 😫 tired face +1F971 ; fully-qualified # 🥱 yawning face + +# subgroup: face-negative +1F624 ; fully-qualified # 😤 face with steam from nose +1F621 ; fully-qualified # 😡 pouting face +1F620 ; fully-qualified # 😠 angry face +1F92C ; fully-qualified # 🤬 face with symbols on mouth +1F608 ; fully-qualified # 😈 smiling face with horns +1F47F ; fully-qualified # 👿 angry face with horns +1F480 ; fully-qualified # 💀 skull +2620 FE0F ; fully-qualified # ☠️ skull and crossbones +2620 ; unqualified # ☠ skull and crossbones + +# subgroup: face-costume +1F4A9 ; fully-qualified # 💩 pile of poo +1F921 ; fully-qualified # 🤡 clown face +1F479 ; fully-qualified # 👹 ogre +1F47A ; fully-qualified # 👺 goblin +1F47B ; fully-qualified # 👻 ghost +1F47D ; fully-qualified # 👽 alien +1F47E ; fully-qualified # 👾 alien monster +1F916 ; fully-qualified # 🤖 robot + +# subgroup: cat-face +1F63A ; fully-qualified # 😺 grinning cat +1F638 ; fully-qualified # 😸 grinning cat with smiling eyes +1F639 ; fully-qualified # 😹 cat with tears of joy +1F63B ; fully-qualified # 😻 smiling cat with heart-eyes +1F63C ; fully-qualified # 😼 cat with wry smile +1F63D ; fully-qualified # 😽 kissing cat +1F640 ; fully-qualified # 🙀 weary cat +1F63F ; fully-qualified # 😿 crying cat +1F63E ; fully-qualified # 😾 pouting cat + +# subgroup: monkey-face +1F648 ; fully-qualified # 🙈 see-no-evil monkey +1F649 ; fully-qualified # 🙉 hear-no-evil monkey +1F64A ; fully-qualified # 🙊 speak-no-evil monkey + +# subgroup: emotion +1F48B ; fully-qualified # 💋 kiss mark +1F48C ; fully-qualified # 💌 love letter +1F498 ; fully-qualified # 💘 heart with arrow +1F49D ; fully-qualified # 💝 heart with ribbon +1F496 ; fully-qualified # 💖 sparkling heart +1F497 ; fully-qualified # 💗 growing heart +1F493 ; fully-qualified # 💓 beating heart +1F49E ; fully-qualified # 💞 revolving hearts +1F495 ; fully-qualified # 💕 two hearts +1F49F ; fully-qualified # 💟 heart decoration +2763 FE0F ; fully-qualified # ❣️ heart exclamation +2763 ; unqualified # ❣ heart exclamation +1F494 ; fully-qualified # 💔 broken heart +2764 FE0F ; fully-qualified # ❤️ red heart +2764 ; unqualified # ❤ red heart +1F9E1 ; fully-qualified # 🧡 orange heart +1F49B ; fully-qualified # 💛 yellow heart +1F49A ; fully-qualified # 💚 green heart +1F499 ; fully-qualified # 💙 blue heart +1F49C ; fully-qualified # 💜 purple heart +1F90E ; fully-qualified # 🤎 brown heart +1F5A4 ; fully-qualified # 🖤 black heart +1F90D ; fully-qualified # 🤍 white heart +1F4AF ; fully-qualified # 💯 hundred points +1F4A2 ; fully-qualified # 💢 anger symbol +1F4A5 ; fully-qualified # 💥 collision +1F4AB ; fully-qualified # 💫 dizzy +1F4A6 ; fully-qualified # 💦 sweat droplets +1F4A8 ; fully-qualified # 💨 dashing away +1F573 FE0F ; fully-qualified # 🕳️ hole +1F573 ; unqualified # 🕳 hole +1F4A3 ; fully-qualified # 💣 bomb +1F4AC ; fully-qualified # 💬 speech balloon +1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ eye in speech bubble +1F441 200D 1F5E8 FE0F ; unqualified # 👁‍🗨️ eye in speech bubble +1F441 FE0F 200D 1F5E8 ; unqualified # 👁️‍🗨 eye in speech bubble +1F441 200D 1F5E8 ; unqualified # 👁‍🗨 eye in speech bubble +1F5E8 FE0F ; fully-qualified # 🗨️ left speech bubble +1F5E8 ; unqualified # 🗨 left speech bubble +1F5EF FE0F ; fully-qualified # 🗯️ right anger bubble +1F5EF ; unqualified # 🗯 right anger bubble +1F4AD ; fully-qualified # 💭 thought balloon +1F4A4 ; fully-qualified # 💤 zzz + +# Smileys & Emotion subtotal: 160 +# Smileys & Emotion subtotal: 160 w/o modifiers + +# group: People & Body + +# subgroup: hand-fingers-open +1F44B ; fully-qualified # 👋 waving hand +1F44B 1F3FB ; fully-qualified # 👋🏻 waving hand: light skin tone +1F44B 1F3FC ; fully-qualified # 👋🏼 waving hand: medium-light skin tone +1F44B 1F3FD ; fully-qualified # 👋🏽 waving hand: medium skin tone +1F44B 1F3FE ; fully-qualified # 👋🏾 waving hand: medium-dark skin tone +1F44B 1F3FF ; fully-qualified # 👋🏿 waving hand: dark skin tone +1F91A ; fully-qualified # 🤚 raised back of hand +1F91A 1F3FB ; fully-qualified # 🤚🏻 raised back of hand: light skin tone +1F91A 1F3FC ; fully-qualified # 🤚🏼 raised back of hand: medium-light skin tone +1F91A 1F3FD ; fully-qualified # 🤚🏽 raised back of hand: medium skin tone +1F91A 1F3FE ; fully-qualified # 🤚🏾 raised back of hand: medium-dark skin tone +1F91A 1F3FF ; fully-qualified # 🤚🏿 raised back of hand: dark skin tone +1F590 FE0F ; fully-qualified # 🖐️ hand with fingers splayed +1F590 ; unqualified # 🖐 hand with fingers splayed +1F590 1F3FB ; fully-qualified # 🖐🏻 hand with fingers splayed: light skin tone +1F590 1F3FC ; fully-qualified # 🖐🏼 hand with fingers splayed: medium-light skin tone +1F590 1F3FD ; fully-qualified # 🖐🏽 hand with fingers splayed: medium skin tone +1F590 1F3FE ; fully-qualified # 🖐🏾 hand with fingers splayed: medium-dark skin tone +1F590 1F3FF ; fully-qualified # 🖐🏿 hand with fingers splayed: dark skin tone +270B ; fully-qualified # ✋ raised hand +270B 1F3FB ; fully-qualified # ✋🏻 raised hand: light skin tone +270B 1F3FC ; fully-qualified # ✋🏼 raised hand: medium-light skin tone +270B 1F3FD ; fully-qualified # ✋🏽 raised hand: medium skin tone +270B 1F3FE ; fully-qualified # ✋🏾 raised hand: medium-dark skin tone +270B 1F3FF ; fully-qualified # ✋🏿 raised hand: dark skin tone +1F596 ; fully-qualified # 🖖 vulcan salute +1F596 1F3FB ; fully-qualified # 🖖🏻 vulcan salute: light skin tone +1F596 1F3FC ; fully-qualified # 🖖🏼 vulcan salute: medium-light skin tone +1F596 1F3FD ; fully-qualified # 🖖🏽 vulcan salute: medium skin tone +1F596 1F3FE ; fully-qualified # 🖖🏾 vulcan salute: medium-dark skin tone +1F596 1F3FF ; fully-qualified # 🖖🏿 vulcan salute: dark skin tone + +# subgroup: hand-fingers-partial +1F44C ; fully-qualified # 👌 OK hand +1F44C 1F3FB ; fully-qualified # 👌🏻 OK hand: light skin tone +1F44C 1F3FC ; fully-qualified # 👌🏼 OK hand: medium-light skin tone +1F44C 1F3FD ; fully-qualified # 👌🏽 OK hand: medium skin tone +1F44C 1F3FE ; fully-qualified # 👌🏾 OK hand: medium-dark skin tone +1F44C 1F3FF ; fully-qualified # 👌🏿 OK hand: dark skin tone +1F90F ; fully-qualified # 🤏 pinching hand +1F90F 1F3FB ; fully-qualified # 🤏🏻 pinching hand: light skin tone +1F90F 1F3FC ; fully-qualified # 🤏🏼 pinching hand: medium-light skin tone +1F90F 1F3FD ; fully-qualified # 🤏🏽 pinching hand: medium skin tone +1F90F 1F3FE ; fully-qualified # 🤏🏾 pinching hand: medium-dark skin tone +1F90F 1F3FF ; fully-qualified # 🤏🏿 pinching hand: dark skin tone +270C FE0F ; fully-qualified # ✌️ victory hand +270C ; unqualified # ✌ victory hand +270C 1F3FB ; fully-qualified # ✌🏻 victory hand: light skin tone +270C 1F3FC ; fully-qualified # ✌🏼 victory hand: medium-light skin tone +270C 1F3FD ; fully-qualified # ✌🏽 victory hand: medium skin tone +270C 1F3FE ; fully-qualified # ✌🏾 victory hand: medium-dark skin tone +270C 1F3FF ; fully-qualified # ✌🏿 victory hand: dark skin tone +1F91E ; fully-qualified # 🤞 crossed fingers +1F91E 1F3FB ; fully-qualified # 🤞🏻 crossed fingers: light skin tone +1F91E 1F3FC ; fully-qualified # 🤞🏼 crossed fingers: medium-light skin tone +1F91E 1F3FD ; fully-qualified # 🤞🏽 crossed fingers: medium skin tone +1F91E 1F3FE ; fully-qualified # 🤞🏾 crossed fingers: medium-dark skin tone +1F91E 1F3FF ; fully-qualified # 🤞🏿 crossed fingers: dark skin tone +1F91F ; fully-qualified # 🤟 love-you gesture +1F91F 1F3FB ; fully-qualified # 🤟🏻 love-you gesture: light skin tone +1F91F 1F3FC ; fully-qualified # 🤟🏼 love-you gesture: medium-light skin tone +1F91F 1F3FD ; fully-qualified # 🤟🏽 love-you gesture: medium skin tone +1F91F 1F3FE ; fully-qualified # 🤟🏾 love-you gesture: medium-dark skin tone +1F91F 1F3FF ; fully-qualified # 🤟🏿 love-you gesture: dark skin tone +1F918 ; fully-qualified # 🤘 sign of the horns +1F918 1F3FB ; fully-qualified # 🤘🏻 sign of the horns: light skin tone +1F918 1F3FC ; fully-qualified # 🤘🏼 sign of the horns: medium-light skin tone +1F918 1F3FD ; fully-qualified # 🤘🏽 sign of the horns: medium skin tone +1F918 1F3FE ; fully-qualified # 🤘🏾 sign of the horns: medium-dark skin tone +1F918 1F3FF ; fully-qualified # 🤘🏿 sign of the horns: dark skin tone +1F919 ; fully-qualified # 🤙 call me hand +1F919 1F3FB ; fully-qualified # 🤙🏻 call me hand: light skin tone +1F919 1F3FC ; fully-qualified # 🤙🏼 call me hand: medium-light skin tone +1F919 1F3FD ; fully-qualified # 🤙🏽 call me hand: medium skin tone +1F919 1F3FE ; fully-qualified # 🤙🏾 call me hand: medium-dark skin tone +1F919 1F3FF ; fully-qualified # 🤙🏿 call me hand: dark skin tone + +# subgroup: hand-single-finger +1F448 ; fully-qualified # 👈 backhand index pointing left +1F448 1F3FB ; fully-qualified # 👈🏻 backhand index pointing left: light skin tone +1F448 1F3FC ; fully-qualified # 👈🏼 backhand index pointing left: medium-light skin tone +1F448 1F3FD ; fully-qualified # 👈🏽 backhand index pointing left: medium skin tone +1F448 1F3FE ; fully-qualified # 👈🏾 backhand index pointing left: medium-dark skin tone +1F448 1F3FF ; fully-qualified # 👈🏿 backhand index pointing left: dark skin tone +1F449 ; fully-qualified # 👉 backhand index pointing right +1F449 1F3FB ; fully-qualified # 👉🏻 backhand index pointing right: light skin tone +1F449 1F3FC ; fully-qualified # 👉🏼 backhand index pointing right: medium-light skin tone +1F449 1F3FD ; fully-qualified # 👉🏽 backhand index pointing right: medium skin tone +1F449 1F3FE ; fully-qualified # 👉🏾 backhand index pointing right: medium-dark skin tone +1F449 1F3FF ; fully-qualified # 👉🏿 backhand index pointing right: dark skin tone +1F446 ; fully-qualified # 👆 backhand index pointing up +1F446 1F3FB ; fully-qualified # 👆🏻 backhand index pointing up: light skin tone +1F446 1F3FC ; fully-qualified # 👆🏼 backhand index pointing up: medium-light skin tone +1F446 1F3FD ; fully-qualified # 👆🏽 backhand index pointing up: medium skin tone +1F446 1F3FE ; fully-qualified # 👆🏾 backhand index pointing up: medium-dark skin tone +1F446 1F3FF ; fully-qualified # 👆🏿 backhand index pointing up: dark skin tone +1F595 ; fully-qualified # 🖕 middle finger +1F595 1F3FB ; fully-qualified # 🖕🏻 middle finger: light skin tone +1F595 1F3FC ; fully-qualified # 🖕🏼 middle finger: medium-light skin tone +1F595 1F3FD ; fully-qualified # 🖕🏽 middle finger: medium skin tone +1F595 1F3FE ; fully-qualified # 🖕🏾 middle finger: medium-dark skin tone +1F595 1F3FF ; fully-qualified # 🖕🏿 middle finger: dark skin tone +1F447 ; fully-qualified # 👇 backhand index pointing down +1F447 1F3FB ; fully-qualified # 👇🏻 backhand index pointing down: light skin tone +1F447 1F3FC ; fully-qualified # 👇🏼 backhand index pointing down: medium-light skin tone +1F447 1F3FD ; fully-qualified # 👇🏽 backhand index pointing down: medium skin tone +1F447 1F3FE ; fully-qualified # 👇🏾 backhand index pointing down: medium-dark skin tone +1F447 1F3FF ; fully-qualified # 👇🏿 backhand index pointing down: dark skin tone +261D FE0F ; fully-qualified # ☝️ index pointing up +261D ; unqualified # ☝ index pointing up +261D 1F3FB ; fully-qualified # ☝🏻 index pointing up: light skin tone +261D 1F3FC ; fully-qualified # ☝🏼 index pointing up: medium-light skin tone +261D 1F3FD ; fully-qualified # ☝🏽 index pointing up: medium skin tone +261D 1F3FE ; fully-qualified # ☝🏾 index pointing up: medium-dark skin tone +261D 1F3FF ; fully-qualified # ☝🏿 index pointing up: dark skin tone + +# subgroup: hand-fingers-closed +1F44D ; fully-qualified # 👍 thumbs up +1F44D 1F3FB ; fully-qualified # 👍🏻 thumbs up: light skin tone +1F44D 1F3FC ; fully-qualified # 👍🏼 thumbs up: medium-light skin tone +1F44D 1F3FD ; fully-qualified # 👍🏽 thumbs up: medium skin tone +1F44D 1F3FE ; fully-qualified # 👍🏾 thumbs up: medium-dark skin tone +1F44D 1F3FF ; fully-qualified # 👍🏿 thumbs up: dark skin tone +1F44E ; fully-qualified # 👎 thumbs down +1F44E 1F3FB ; fully-qualified # 👎🏻 thumbs down: light skin tone +1F44E 1F3FC ; fully-qualified # 👎🏼 thumbs down: medium-light skin tone +1F44E 1F3FD ; fully-qualified # 👎🏽 thumbs down: medium skin tone +1F44E 1F3FE ; fully-qualified # 👎🏾 thumbs down: medium-dark skin tone +1F44E 1F3FF ; fully-qualified # 👎🏿 thumbs down: dark skin tone +270A ; fully-qualified # ✊ raised fist +270A 1F3FB ; fully-qualified # ✊🏻 raised fist: light skin tone +270A 1F3FC ; fully-qualified # ✊🏼 raised fist: medium-light skin tone +270A 1F3FD ; fully-qualified # ✊🏽 raised fist: medium skin tone +270A 1F3FE ; fully-qualified # ✊🏾 raised fist: medium-dark skin tone +270A 1F3FF ; fully-qualified # ✊🏿 raised fist: dark skin tone +1F44A ; fully-qualified # 👊 oncoming fist +1F44A 1F3FB ; fully-qualified # 👊🏻 oncoming fist: light skin tone +1F44A 1F3FC ; fully-qualified # 👊🏼 oncoming fist: medium-light skin tone +1F44A 1F3FD ; fully-qualified # 👊🏽 oncoming fist: medium skin tone +1F44A 1F3FE ; fully-qualified # 👊🏾 oncoming fist: medium-dark skin tone +1F44A 1F3FF ; fully-qualified # 👊🏿 oncoming fist: dark skin tone +1F91B ; fully-qualified # 🤛 left-facing fist +1F91B 1F3FB ; fully-qualified # 🤛🏻 left-facing fist: light skin tone +1F91B 1F3FC ; fully-qualified # 🤛🏼 left-facing fist: medium-light skin tone +1F91B 1F3FD ; fully-qualified # 🤛🏽 left-facing fist: medium skin tone +1F91B 1F3FE ; fully-qualified # 🤛🏾 left-facing fist: medium-dark skin tone +1F91B 1F3FF ; fully-qualified # 🤛🏿 left-facing fist: dark skin tone +1F91C ; fully-qualified # 🤜 right-facing fist +1F91C 1F3FB ; fully-qualified # 🤜🏻 right-facing fist: light skin tone +1F91C 1F3FC ; fully-qualified # 🤜🏼 right-facing fist: medium-light skin tone +1F91C 1F3FD ; fully-qualified # 🤜🏽 right-facing fist: medium skin tone +1F91C 1F3FE ; fully-qualified # 🤜🏾 right-facing fist: medium-dark skin tone +1F91C 1F3FF ; fully-qualified # 🤜🏿 right-facing fist: dark skin tone + +# subgroup: hands +1F44F ; fully-qualified # 👏 clapping hands +1F44F 1F3FB ; fully-qualified # 👏🏻 clapping hands: light skin tone +1F44F 1F3FC ; fully-qualified # 👏🏼 clapping hands: medium-light skin tone +1F44F 1F3FD ; fully-qualified # 👏🏽 clapping hands: medium skin tone +1F44F 1F3FE ; fully-qualified # 👏🏾 clapping hands: medium-dark skin tone +1F44F 1F3FF ; fully-qualified # 👏🏿 clapping hands: dark skin tone +1F64C ; fully-qualified # 🙌 raising hands +1F64C 1F3FB ; fully-qualified # 🙌🏻 raising hands: light skin tone +1F64C 1F3FC ; fully-qualified # 🙌🏼 raising hands: medium-light skin tone +1F64C 1F3FD ; fully-qualified # 🙌🏽 raising hands: medium skin tone +1F64C 1F3FE ; fully-qualified # 🙌🏾 raising hands: medium-dark skin tone +1F64C 1F3FF ; fully-qualified # 🙌🏿 raising hands: dark skin tone +1F450 ; fully-qualified # 👐 open hands +1F450 1F3FB ; fully-qualified # 👐🏻 open hands: light skin tone +1F450 1F3FC ; fully-qualified # 👐🏼 open hands: medium-light skin tone +1F450 1F3FD ; fully-qualified # 👐🏽 open hands: medium skin tone +1F450 1F3FE ; fully-qualified # 👐🏾 open hands: medium-dark skin tone +1F450 1F3FF ; fully-qualified # 👐🏿 open hands: dark skin tone +1F932 ; fully-qualified # 🤲 palms up together +1F932 1F3FB ; fully-qualified # 🤲🏻 palms up together: light skin tone +1F932 1F3FC ; fully-qualified # 🤲🏼 palms up together: medium-light skin tone +1F932 1F3FD ; fully-qualified # 🤲🏽 palms up together: medium skin tone +1F932 1F3FE ; fully-qualified # 🤲🏾 palms up together: medium-dark skin tone +1F932 1F3FF ; fully-qualified # 🤲🏿 palms up together: dark skin tone +1F91D ; fully-qualified # 🤝 handshake +1F64F ; fully-qualified # 🙏 folded hands +1F64F 1F3FB ; fully-qualified # 🙏🏻 folded hands: light skin tone +1F64F 1F3FC ; fully-qualified # 🙏🏼 folded hands: medium-light skin tone +1F64F 1F3FD ; fully-qualified # 🙏🏽 folded hands: medium skin tone +1F64F 1F3FE ; fully-qualified # 🙏🏾 folded hands: medium-dark skin tone +1F64F 1F3FF ; fully-qualified # 🙏🏿 folded hands: dark skin tone + +# subgroup: hand-prop +270D FE0F ; fully-qualified # ✍️ writing hand +270D ; unqualified # ✍ writing hand +270D 1F3FB ; fully-qualified # ✍🏻 writing hand: light skin tone +270D 1F3FC ; fully-qualified # ✍🏼 writing hand: medium-light skin tone +270D 1F3FD ; fully-qualified # ✍🏽 writing hand: medium skin tone +270D 1F3FE ; fully-qualified # ✍🏾 writing hand: medium-dark skin tone +270D 1F3FF ; fully-qualified # ✍🏿 writing hand: dark skin tone +1F485 ; fully-qualified # 💅 nail polish +1F485 1F3FB ; fully-qualified # 💅🏻 nail polish: light skin tone +1F485 1F3FC ; fully-qualified # 💅🏼 nail polish: medium-light skin tone +1F485 1F3FD ; fully-qualified # 💅🏽 nail polish: medium skin tone +1F485 1F3FE ; fully-qualified # 💅🏾 nail polish: medium-dark skin tone +1F485 1F3FF ; fully-qualified # 💅🏿 nail polish: dark skin tone +1F933 ; fully-qualified # 🤳 selfie +1F933 1F3FB ; fully-qualified # 🤳🏻 selfie: light skin tone +1F933 1F3FC ; fully-qualified # 🤳🏼 selfie: medium-light skin tone +1F933 1F3FD ; fully-qualified # 🤳🏽 selfie: medium skin tone +1F933 1F3FE ; fully-qualified # 🤳🏾 selfie: medium-dark skin tone +1F933 1F3FF ; fully-qualified # 🤳🏿 selfie: dark skin tone + +# subgroup: body-parts +1F4AA ; fully-qualified # 💪 flexed biceps +1F4AA 1F3FB ; fully-qualified # 💪🏻 flexed biceps: light skin tone +1F4AA 1F3FC ; fully-qualified # 💪🏼 flexed biceps: medium-light skin tone +1F4AA 1F3FD ; fully-qualified # 💪🏽 flexed biceps: medium skin tone +1F4AA 1F3FE ; fully-qualified # 💪🏾 flexed biceps: medium-dark skin tone +1F4AA 1F3FF ; fully-qualified # 💪🏿 flexed biceps: dark skin tone +1F9BE ; fully-qualified # 🦾 mechanical arm +1F9BF ; fully-qualified # 🦿 mechanical leg +1F9B5 ; fully-qualified # 🦵 leg +1F9B5 1F3FB ; fully-qualified # 🦵🏻 leg: light skin tone +1F9B5 1F3FC ; fully-qualified # 🦵🏼 leg: medium-light skin tone +1F9B5 1F3FD ; fully-qualified # 🦵🏽 leg: medium skin tone +1F9B5 1F3FE ; fully-qualified # 🦵🏾 leg: medium-dark skin tone +1F9B5 1F3FF ; fully-qualified # 🦵🏿 leg: dark skin tone +1F9B6 ; fully-qualified # 🦶 foot +1F9B6 1F3FB ; fully-qualified # 🦶🏻 foot: light skin tone +1F9B6 1F3FC ; fully-qualified # 🦶🏼 foot: medium-light skin tone +1F9B6 1F3FD ; fully-qualified # 🦶🏽 foot: medium skin tone +1F9B6 1F3FE ; fully-qualified # 🦶🏾 foot: medium-dark skin tone +1F9B6 1F3FF ; fully-qualified # 🦶🏿 foot: dark skin tone +1F442 ; fully-qualified # 👂 ear +1F442 1F3FB ; fully-qualified # 👂🏻 ear: light skin tone +1F442 1F3FC ; fully-qualified # 👂🏼 ear: medium-light skin tone +1F442 1F3FD ; fully-qualified # 👂🏽 ear: medium skin tone +1F442 1F3FE ; fully-qualified # 👂🏾 ear: medium-dark skin tone +1F442 1F3FF ; fully-qualified # 👂🏿 ear: dark skin tone +1F9BB ; fully-qualified # 🦻 ear with hearing aid +1F9BB 1F3FB ; fully-qualified # 🦻🏻 ear with hearing aid: light skin tone +1F9BB 1F3FC ; fully-qualified # 🦻🏼 ear with hearing aid: medium-light skin tone +1F9BB 1F3FD ; fully-qualified # 🦻🏽 ear with hearing aid: medium skin tone +1F9BB 1F3FE ; fully-qualified # 🦻🏾 ear with hearing aid: medium-dark skin tone +1F9BB 1F3FF ; fully-qualified # 🦻🏿 ear with hearing aid: dark skin tone +1F443 ; fully-qualified # 👃 nose +1F443 1F3FB ; fully-qualified # 👃🏻 nose: light skin tone +1F443 1F3FC ; fully-qualified # 👃🏼 nose: medium-light skin tone +1F443 1F3FD ; fully-qualified # 👃🏽 nose: medium skin tone +1F443 1F3FE ; fully-qualified # 👃🏾 nose: medium-dark skin tone +1F443 1F3FF ; fully-qualified # 👃🏿 nose: dark skin tone +1F9E0 ; fully-qualified # 🧠 brain +1F9B7 ; fully-qualified # 🦷 tooth +1F9B4 ; fully-qualified # 🦴 bone +1F440 ; fully-qualified # 👀 eyes +1F441 FE0F ; fully-qualified # 👁️ eye +1F441 ; unqualified # 👁 eye +1F445 ; fully-qualified # 👅 tongue +1F444 ; fully-qualified # 👄 mouth + +# subgroup: person +1F476 ; fully-qualified # 👶 baby +1F476 1F3FB ; fully-qualified # 👶🏻 baby: light skin tone +1F476 1F3FC ; fully-qualified # 👶🏼 baby: medium-light skin tone +1F476 1F3FD ; fully-qualified # 👶🏽 baby: medium skin tone +1F476 1F3FE ; fully-qualified # 👶🏾 baby: medium-dark skin tone +1F476 1F3FF ; fully-qualified # 👶🏿 baby: dark skin tone +1F9D2 ; fully-qualified # 🧒 child +1F9D2 1F3FB ; fully-qualified # 🧒🏻 child: light skin tone +1F9D2 1F3FC ; fully-qualified # 🧒🏼 child: medium-light skin tone +1F9D2 1F3FD ; fully-qualified # 🧒🏽 child: medium skin tone +1F9D2 1F3FE ; fully-qualified # 🧒🏾 child: medium-dark skin tone +1F9D2 1F3FF ; fully-qualified # 🧒🏿 child: dark skin tone +1F466 ; fully-qualified # 👦 boy +1F466 1F3FB ; fully-qualified # 👦🏻 boy: light skin tone +1F466 1F3FC ; fully-qualified # 👦🏼 boy: medium-light skin tone +1F466 1F3FD ; fully-qualified # 👦🏽 boy: medium skin tone +1F466 1F3FE ; fully-qualified # 👦🏾 boy: medium-dark skin tone +1F466 1F3FF ; fully-qualified # 👦🏿 boy: dark skin tone +1F467 ; fully-qualified # 👧 girl +1F467 1F3FB ; fully-qualified # 👧🏻 girl: light skin tone +1F467 1F3FC ; fully-qualified # 👧🏼 girl: medium-light skin tone +1F467 1F3FD ; fully-qualified # 👧🏽 girl: medium skin tone +1F467 1F3FE ; fully-qualified # 👧🏾 girl: medium-dark skin tone +1F467 1F3FF ; fully-qualified # 👧🏿 girl: dark skin tone +1F9D1 ; fully-qualified # 🧑 person +1F9D1 1F3FB ; fully-qualified # 🧑🏻 person: light skin tone +1F9D1 1F3FC ; fully-qualified # 🧑🏼 person: medium-light skin tone +1F9D1 1F3FD ; fully-qualified # 🧑🏽 person: medium skin tone +1F9D1 1F3FE ; fully-qualified # 🧑🏾 person: medium-dark skin tone +1F9D1 1F3FF ; fully-qualified # 🧑🏿 person: dark skin tone +1F471 ; fully-qualified # 👱 person: blond hair +1F471 1F3FB ; fully-qualified # 👱🏻 person: light skin tone, blond hair +1F471 1F3FC ; fully-qualified # 👱🏼 person: medium-light skin tone, blond hair +1F471 1F3FD ; fully-qualified # 👱🏽 person: medium skin tone, blond hair +1F471 1F3FE ; fully-qualified # 👱🏾 person: medium-dark skin tone, blond hair +1F471 1F3FF ; fully-qualified # 👱🏿 person: dark skin tone, blond hair +1F468 ; fully-qualified # 👨 man +1F468 1F3FB ; fully-qualified # 👨🏻 man: light skin tone +1F468 1F3FC ; fully-qualified # 👨🏼 man: medium-light skin tone +1F468 1F3FD ; fully-qualified # 👨🏽 man: medium skin tone +1F468 1F3FE ; fully-qualified # 👨🏾 man: medium-dark skin tone +1F468 1F3FF ; fully-qualified # 👨🏿 man: dark skin tone +1F9D4 ; fully-qualified # 🧔 man: beard +1F9D4 1F3FB ; fully-qualified # 🧔🏻 man: light skin tone, beard +1F9D4 1F3FC ; fully-qualified # 🧔🏼 man: medium-light skin tone, beard +1F9D4 1F3FD ; fully-qualified # 🧔🏽 man: medium skin tone, beard +1F9D4 1F3FE ; fully-qualified # 🧔🏾 man: medium-dark skin tone, beard +1F9D4 1F3FF ; fully-qualified # 🧔🏿 man: dark skin tone, beard +1F471 200D 2642 FE0F ; fully-qualified # 👱‍♂️ man: blond hair +1F471 200D 2642 ; minimally-qualified # 👱‍♂ man: blond hair +1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻‍♂️ man: light skin tone, blond hair +1F471 1F3FB 200D 2642 ; minimally-qualified # 👱🏻‍♂ man: light skin tone, blond hair +1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼‍♂️ man: medium-light skin tone, blond hair +1F471 1F3FC 200D 2642 ; minimally-qualified # 👱🏼‍♂ man: medium-light skin tone, blond hair +1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽‍♂️ man: medium skin tone, blond hair +1F471 1F3FD 200D 2642 ; minimally-qualified # 👱🏽‍♂ man: medium skin tone, blond hair +1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾‍♂️ man: medium-dark skin tone, blond hair +1F471 1F3FE 200D 2642 ; minimally-qualified # 👱🏾‍♂ man: medium-dark skin tone, blond hair +1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿‍♂️ man: dark skin tone, blond hair +1F471 1F3FF 200D 2642 ; minimally-qualified # 👱🏿‍♂ man: dark skin tone, blond hair +1F468 200D 1F9B0 ; fully-qualified # 👨‍🦰 man: red hair +1F468 1F3FB 200D 1F9B0 ; fully-qualified # 👨🏻‍🦰 man: light skin tone, red hair +1F468 1F3FC 200D 1F9B0 ; fully-qualified # 👨🏼‍🦰 man: medium-light skin tone, red hair +1F468 1F3FD 200D 1F9B0 ; fully-qualified # 👨🏽‍🦰 man: medium skin tone, red hair +1F468 1F3FE 200D 1F9B0 ; fully-qualified # 👨🏾‍🦰 man: medium-dark skin tone, red hair +1F468 1F3FF 200D 1F9B0 ; fully-qualified # 👨🏿‍🦰 man: dark skin tone, red hair +1F468 200D 1F9B1 ; fully-qualified # 👨‍🦱 man: curly hair +1F468 1F3FB 200D 1F9B1 ; fully-qualified # 👨🏻‍🦱 man: light skin tone, curly hair +1F468 1F3FC 200D 1F9B1 ; fully-qualified # 👨🏼‍🦱 man: medium-light skin tone, curly hair +1F468 1F3FD 200D 1F9B1 ; fully-qualified # 👨🏽‍🦱 man: medium skin tone, curly hair +1F468 1F3FE 200D 1F9B1 ; fully-qualified # 👨🏾‍🦱 man: medium-dark skin tone, curly hair +1F468 1F3FF 200D 1F9B1 ; fully-qualified # 👨🏿‍🦱 man: dark skin tone, curly hair +1F468 200D 1F9B3 ; fully-qualified # 👨‍🦳 man: white hair +1F468 1F3FB 200D 1F9B3 ; fully-qualified # 👨🏻‍🦳 man: light skin tone, white hair +1F468 1F3FC 200D 1F9B3 ; fully-qualified # 👨🏼‍🦳 man: medium-light skin tone, white hair +1F468 1F3FD 200D 1F9B3 ; fully-qualified # 👨🏽‍🦳 man: medium skin tone, white hair +1F468 1F3FE 200D 1F9B3 ; fully-qualified # 👨🏾‍🦳 man: medium-dark skin tone, white hair +1F468 1F3FF 200D 1F9B3 ; fully-qualified # 👨🏿‍🦳 man: dark skin tone, white hair +1F468 200D 1F9B2 ; fully-qualified # 👨‍🦲 man: bald +1F468 1F3FB 200D 1F9B2 ; fully-qualified # 👨🏻‍🦲 man: light skin tone, bald +1F468 1F3FC 200D 1F9B2 ; fully-qualified # 👨🏼‍🦲 man: medium-light skin tone, bald +1F468 1F3FD 200D 1F9B2 ; fully-qualified # 👨🏽‍🦲 man: medium skin tone, bald +1F468 1F3FE 200D 1F9B2 ; fully-qualified # 👨🏾‍🦲 man: medium-dark skin tone, bald +1F468 1F3FF 200D 1F9B2 ; fully-qualified # 👨🏿‍🦲 man: dark skin tone, bald +1F469 ; fully-qualified # 👩 woman +1F469 1F3FB ; fully-qualified # 👩🏻 woman: light skin tone +1F469 1F3FC ; fully-qualified # 👩🏼 woman: medium-light skin tone +1F469 1F3FD ; fully-qualified # 👩🏽 woman: medium skin tone +1F469 1F3FE ; fully-qualified # 👩🏾 woman: medium-dark skin tone +1F469 1F3FF ; fully-qualified # 👩🏿 woman: dark skin tone +1F471 200D 2640 FE0F ; fully-qualified # 👱‍♀️ woman: blond hair +1F471 200D 2640 ; minimally-qualified # 👱‍♀ woman: blond hair +1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻‍♀️ woman: light skin tone, blond hair +1F471 1F3FB 200D 2640 ; minimally-qualified # 👱🏻‍♀ woman: light skin tone, blond hair +1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼‍♀️ woman: medium-light skin tone, blond hair +1F471 1F3FC 200D 2640 ; minimally-qualified # 👱🏼‍♀ woman: medium-light skin tone, blond hair +1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽‍♀️ woman: medium skin tone, blond hair +1F471 1F3FD 200D 2640 ; minimally-qualified # 👱🏽‍♀ woman: medium skin tone, blond hair +1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾‍♀️ woman: medium-dark skin tone, blond hair +1F471 1F3FE 200D 2640 ; minimally-qualified # 👱🏾‍♀ woman: medium-dark skin tone, blond hair +1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿‍♀️ woman: dark skin tone, blond hair +1F471 1F3FF 200D 2640 ; minimally-qualified # 👱🏿‍♀ woman: dark skin tone, blond hair +1F469 200D 1F9B0 ; fully-qualified # 👩‍🦰 woman: red hair +1F469 1F3FB 200D 1F9B0 ; fully-qualified # 👩🏻‍🦰 woman: light skin tone, red hair +1F469 1F3FC 200D 1F9B0 ; fully-qualified # 👩🏼‍🦰 woman: medium-light skin tone, red hair +1F469 1F3FD 200D 1F9B0 ; fully-qualified # 👩🏽‍🦰 woman: medium skin tone, red hair +1F469 1F3FE 200D 1F9B0 ; fully-qualified # 👩🏾‍🦰 woman: medium-dark skin tone, red hair +1F469 1F3FF 200D 1F9B0 ; fully-qualified # 👩🏿‍🦰 woman: dark skin tone, red hair +1F469 200D 1F9B1 ; fully-qualified # 👩‍🦱 woman: curly hair +1F469 1F3FB 200D 1F9B1 ; fully-qualified # 👩🏻‍🦱 woman: light skin tone, curly hair +1F469 1F3FC 200D 1F9B1 ; fully-qualified # 👩🏼‍🦱 woman: medium-light skin tone, curly hair +1F469 1F3FD 200D 1F9B1 ; fully-qualified # 👩🏽‍🦱 woman: medium skin tone, curly hair +1F469 1F3FE 200D 1F9B1 ; fully-qualified # 👩🏾‍🦱 woman: medium-dark skin tone, curly hair +1F469 1F3FF 200D 1F9B1 ; fully-qualified # 👩🏿‍🦱 woman: dark skin tone, curly hair +1F469 200D 1F9B3 ; fully-qualified # 👩‍🦳 woman: white hair +1F469 1F3FB 200D 1F9B3 ; fully-qualified # 👩🏻‍🦳 woman: light skin tone, white hair +1F469 1F3FC 200D 1F9B3 ; fully-qualified # 👩🏼‍🦳 woman: medium-light skin tone, white hair +1F469 1F3FD 200D 1F9B3 ; fully-qualified # 👩🏽‍🦳 woman: medium skin tone, white hair +1F469 1F3FE 200D 1F9B3 ; fully-qualified # 👩🏾‍🦳 woman: medium-dark skin tone, white hair +1F469 1F3FF 200D 1F9B3 ; fully-qualified # 👩🏿‍🦳 woman: dark skin tone, white hair +1F469 200D 1F9B2 ; fully-qualified # 👩‍🦲 woman: bald +1F469 1F3FB 200D 1F9B2 ; fully-qualified # 👩🏻‍🦲 woman: light skin tone, bald +1F469 1F3FC 200D 1F9B2 ; fully-qualified # 👩🏼‍🦲 woman: medium-light skin tone, bald +1F469 1F3FD 200D 1F9B2 ; fully-qualified # 👩🏽‍🦲 woman: medium skin tone, bald +1F469 1F3FE 200D 1F9B2 ; fully-qualified # 👩🏾‍🦲 woman: medium-dark skin tone, bald +1F469 1F3FF 200D 1F9B2 ; fully-qualified # 👩🏿‍🦲 woman: dark skin tone, bald +1F9D3 ; fully-qualified # 🧓 older person +1F9D3 1F3FB ; fully-qualified # 🧓🏻 older person: light skin tone +1F9D3 1F3FC ; fully-qualified # 🧓🏼 older person: medium-light skin tone +1F9D3 1F3FD ; fully-qualified # 🧓🏽 older person: medium skin tone +1F9D3 1F3FE ; fully-qualified # 🧓🏾 older person: medium-dark skin tone +1F9D3 1F3FF ; fully-qualified # 🧓🏿 older person: dark skin tone +1F474 ; fully-qualified # 👴 old man +1F474 1F3FB ; fully-qualified # 👴🏻 old man: light skin tone +1F474 1F3FC ; fully-qualified # 👴🏼 old man: medium-light skin tone +1F474 1F3FD ; fully-qualified # 👴🏽 old man: medium skin tone +1F474 1F3FE ; fully-qualified # 👴🏾 old man: medium-dark skin tone +1F474 1F3FF ; fully-qualified # 👴🏿 old man: dark skin tone +1F475 ; fully-qualified # 👵 old woman +1F475 1F3FB ; fully-qualified # 👵🏻 old woman: light skin tone +1F475 1F3FC ; fully-qualified # 👵🏼 old woman: medium-light skin tone +1F475 1F3FD ; fully-qualified # 👵🏽 old woman: medium skin tone +1F475 1F3FE ; fully-qualified # 👵🏾 old woman: medium-dark skin tone +1F475 1F3FF ; fully-qualified # 👵🏿 old woman: dark skin tone + +# subgroup: person-gesture +1F64D ; fully-qualified # 🙍 person frowning +1F64D 1F3FB ; fully-qualified # 🙍🏻 person frowning: light skin tone +1F64D 1F3FC ; fully-qualified # 🙍🏼 person frowning: medium-light skin tone +1F64D 1F3FD ; fully-qualified # 🙍🏽 person frowning: medium skin tone +1F64D 1F3FE ; fully-qualified # 🙍🏾 person frowning: medium-dark skin tone +1F64D 1F3FF ; fully-qualified # 🙍🏿 person frowning: dark skin tone +1F64D 200D 2642 FE0F ; fully-qualified # 🙍‍♂️ man frowning +1F64D 200D 2642 ; minimally-qualified # 🙍‍♂ man frowning +1F64D 1F3FB 200D 2642 FE0F ; fully-qualified # 🙍🏻‍♂️ man frowning: light skin tone +1F64D 1F3FB 200D 2642 ; minimally-qualified # 🙍🏻‍♂ man frowning: light skin tone +1F64D 1F3FC 200D 2642 FE0F ; fully-qualified # 🙍🏼‍♂️ man frowning: medium-light skin tone +1F64D 1F3FC 200D 2642 ; minimally-qualified # 🙍🏼‍♂ man frowning: medium-light skin tone +1F64D 1F3FD 200D 2642 FE0F ; fully-qualified # 🙍🏽‍♂️ man frowning: medium skin tone +1F64D 1F3FD 200D 2642 ; minimally-qualified # 🙍🏽‍♂ man frowning: medium skin tone +1F64D 1F3FE 200D 2642 FE0F ; fully-qualified # 🙍🏾‍♂️ man frowning: medium-dark skin tone +1F64D 1F3FE 200D 2642 ; minimally-qualified # 🙍🏾‍♂ man frowning: medium-dark skin tone +1F64D 1F3FF 200D 2642 FE0F ; fully-qualified # 🙍🏿‍♂️ man frowning: dark skin tone +1F64D 1F3FF 200D 2642 ; minimally-qualified # 🙍🏿‍♂ man frowning: dark skin tone +1F64D 200D 2640 FE0F ; fully-qualified # 🙍‍♀️ woman frowning +1F64D 200D 2640 ; minimally-qualified # 🙍‍♀ woman frowning +1F64D 1F3FB 200D 2640 FE0F ; fully-qualified # 🙍🏻‍♀️ woman frowning: light skin tone +1F64D 1F3FB 200D 2640 ; minimally-qualified # 🙍🏻‍♀ woman frowning: light skin tone +1F64D 1F3FC 200D 2640 FE0F ; fully-qualified # 🙍🏼‍♀️ woman frowning: medium-light skin tone +1F64D 1F3FC 200D 2640 ; minimally-qualified # 🙍🏼‍♀ woman frowning: medium-light skin tone +1F64D 1F3FD 200D 2640 FE0F ; fully-qualified # 🙍🏽‍♀️ woman frowning: medium skin tone +1F64D 1F3FD 200D 2640 ; minimally-qualified # 🙍🏽‍♀ woman frowning: medium skin tone +1F64D 1F3FE 200D 2640 FE0F ; fully-qualified # 🙍🏾‍♀️ woman frowning: medium-dark skin tone +1F64D 1F3FE 200D 2640 ; minimally-qualified # 🙍🏾‍♀ woman frowning: medium-dark skin tone +1F64D 1F3FF 200D 2640 FE0F ; fully-qualified # 🙍🏿‍♀️ woman frowning: dark skin tone +1F64D 1F3FF 200D 2640 ; minimally-qualified # 🙍🏿‍♀ woman frowning: dark skin tone +1F64E ; fully-qualified # 🙎 person pouting +1F64E 1F3FB ; fully-qualified # 🙎🏻 person pouting: light skin tone +1F64E 1F3FC ; fully-qualified # 🙎🏼 person pouting: medium-light skin tone +1F64E 1F3FD ; fully-qualified # 🙎🏽 person pouting: medium skin tone +1F64E 1F3FE ; fully-qualified # 🙎🏾 person pouting: medium-dark skin tone +1F64E 1F3FF ; fully-qualified # 🙎🏿 person pouting: dark skin tone +1F64E 200D 2642 FE0F ; fully-qualified # 🙎‍♂️ man pouting +1F64E 200D 2642 ; minimally-qualified # 🙎‍♂ man pouting +1F64E 1F3FB 200D 2642 FE0F ; fully-qualified # 🙎🏻‍♂️ man pouting: light skin tone +1F64E 1F3FB 200D 2642 ; minimally-qualified # 🙎🏻‍♂ man pouting: light skin tone +1F64E 1F3FC 200D 2642 FE0F ; fully-qualified # 🙎🏼‍♂️ man pouting: medium-light skin tone +1F64E 1F3FC 200D 2642 ; minimally-qualified # 🙎🏼‍♂ man pouting: medium-light skin tone +1F64E 1F3FD 200D 2642 FE0F ; fully-qualified # 🙎🏽‍♂️ man pouting: medium skin tone +1F64E 1F3FD 200D 2642 ; minimally-qualified # 🙎🏽‍♂ man pouting: medium skin tone +1F64E 1F3FE 200D 2642 FE0F ; fully-qualified # 🙎🏾‍♂️ man pouting: medium-dark skin tone +1F64E 1F3FE 200D 2642 ; minimally-qualified # 🙎🏾‍♂ man pouting: medium-dark skin tone +1F64E 1F3FF 200D 2642 FE0F ; fully-qualified # 🙎🏿‍♂️ man pouting: dark skin tone +1F64E 1F3FF 200D 2642 ; minimally-qualified # 🙎🏿‍♂ man pouting: dark skin tone +1F64E 200D 2640 FE0F ; fully-qualified # 🙎‍♀️ woman pouting +1F64E 200D 2640 ; minimally-qualified # 🙎‍♀ woman pouting +1F64E 1F3FB 200D 2640 FE0F ; fully-qualified # 🙎🏻‍♀️ woman pouting: light skin tone +1F64E 1F3FB 200D 2640 ; minimally-qualified # 🙎🏻‍♀ woman pouting: light skin tone +1F64E 1F3FC 200D 2640 FE0F ; fully-qualified # 🙎🏼‍♀️ woman pouting: medium-light skin tone +1F64E 1F3FC 200D 2640 ; minimally-qualified # 🙎🏼‍♀ woman pouting: medium-light skin tone +1F64E 1F3FD 200D 2640 FE0F ; fully-qualified # 🙎🏽‍♀️ woman pouting: medium skin tone +1F64E 1F3FD 200D 2640 ; minimally-qualified # 🙎🏽‍♀ woman pouting: medium skin tone +1F64E 1F3FE 200D 2640 FE0F ; fully-qualified # 🙎🏾‍♀️ woman pouting: medium-dark skin tone +1F64E 1F3FE 200D 2640 ; minimally-qualified # 🙎🏾‍♀ woman pouting: medium-dark skin tone +1F64E 1F3FF 200D 2640 FE0F ; fully-qualified # 🙎🏿‍♀️ woman pouting: dark skin tone +1F64E 1F3FF 200D 2640 ; minimally-qualified # 🙎🏿‍♀ woman pouting: dark skin tone +1F645 ; fully-qualified # 🙅 person gesturing NO +1F645 1F3FB ; fully-qualified # 🙅🏻 person gesturing NO: light skin tone +1F645 1F3FC ; fully-qualified # 🙅🏼 person gesturing NO: medium-light skin tone +1F645 1F3FD ; fully-qualified # 🙅🏽 person gesturing NO: medium skin tone +1F645 1F3FE ; fully-qualified # 🙅🏾 person gesturing NO: medium-dark skin tone +1F645 1F3FF ; fully-qualified # 🙅🏿 person gesturing NO: dark skin tone +1F645 200D 2642 FE0F ; fully-qualified # 🙅‍♂️ man gesturing NO +1F645 200D 2642 ; minimally-qualified # 🙅‍♂ man gesturing NO +1F645 1F3FB 200D 2642 FE0F ; fully-qualified # 🙅🏻‍♂️ man gesturing NO: light skin tone +1F645 1F3FB 200D 2642 ; minimally-qualified # 🙅🏻‍♂ man gesturing NO: light skin tone +1F645 1F3FC 200D 2642 FE0F ; fully-qualified # 🙅🏼‍♂️ man gesturing NO: medium-light skin tone +1F645 1F3FC 200D 2642 ; minimally-qualified # 🙅🏼‍♂ man gesturing NO: medium-light skin tone +1F645 1F3FD 200D 2642 FE0F ; fully-qualified # 🙅🏽‍♂️ man gesturing NO: medium skin tone +1F645 1F3FD 200D 2642 ; minimally-qualified # 🙅🏽‍♂ man gesturing NO: medium skin tone +1F645 1F3FE 200D 2642 FE0F ; fully-qualified # 🙅🏾‍♂️ man gesturing NO: medium-dark skin tone +1F645 1F3FE 200D 2642 ; minimally-qualified # 🙅🏾‍♂ man gesturing NO: medium-dark skin tone +1F645 1F3FF 200D 2642 FE0F ; fully-qualified # 🙅🏿‍♂️ man gesturing NO: dark skin tone +1F645 1F3FF 200D 2642 ; minimally-qualified # 🙅🏿‍♂ man gesturing NO: dark skin tone +1F645 200D 2640 FE0F ; fully-qualified # 🙅‍♀️ woman gesturing NO +1F645 200D 2640 ; minimally-qualified # 🙅‍♀ woman gesturing NO +1F645 1F3FB 200D 2640 FE0F ; fully-qualified # 🙅🏻‍♀️ woman gesturing NO: light skin tone +1F645 1F3FB 200D 2640 ; minimally-qualified # 🙅🏻‍♀ woman gesturing NO: light skin tone +1F645 1F3FC 200D 2640 FE0F ; fully-qualified # 🙅🏼‍♀️ woman gesturing NO: medium-light skin tone +1F645 1F3FC 200D 2640 ; minimally-qualified # 🙅🏼‍♀ woman gesturing NO: medium-light skin tone +1F645 1F3FD 200D 2640 FE0F ; fully-qualified # 🙅🏽‍♀️ woman gesturing NO: medium skin tone +1F645 1F3FD 200D 2640 ; minimally-qualified # 🙅🏽‍♀ woman gesturing NO: medium skin tone +1F645 1F3FE 200D 2640 FE0F ; fully-qualified # 🙅🏾‍♀️ woman gesturing NO: medium-dark skin tone +1F645 1F3FE 200D 2640 ; minimally-qualified # 🙅🏾‍♀ woman gesturing NO: medium-dark skin tone +1F645 1F3FF 200D 2640 FE0F ; fully-qualified # 🙅🏿‍♀️ woman gesturing NO: dark skin tone +1F645 1F3FF 200D 2640 ; minimally-qualified # 🙅🏿‍♀ woman gesturing NO: dark skin tone +1F646 ; fully-qualified # 🙆 person gesturing OK +1F646 1F3FB ; fully-qualified # 🙆🏻 person gesturing OK: light skin tone +1F646 1F3FC ; fully-qualified # 🙆🏼 person gesturing OK: medium-light skin tone +1F646 1F3FD ; fully-qualified # 🙆🏽 person gesturing OK: medium skin tone +1F646 1F3FE ; fully-qualified # 🙆🏾 person gesturing OK: medium-dark skin tone +1F646 1F3FF ; fully-qualified # 🙆🏿 person gesturing OK: dark skin tone +1F646 200D 2642 FE0F ; fully-qualified # 🙆‍♂️ man gesturing OK +1F646 200D 2642 ; minimally-qualified # 🙆‍♂ man gesturing OK +1F646 1F3FB 200D 2642 FE0F ; fully-qualified # 🙆🏻‍♂️ man gesturing OK: light skin tone +1F646 1F3FB 200D 2642 ; minimally-qualified # 🙆🏻‍♂ man gesturing OK: light skin tone +1F646 1F3FC 200D 2642 FE0F ; fully-qualified # 🙆🏼‍♂️ man gesturing OK: medium-light skin tone +1F646 1F3FC 200D 2642 ; minimally-qualified # 🙆🏼‍♂ man gesturing OK: medium-light skin tone +1F646 1F3FD 200D 2642 FE0F ; fully-qualified # 🙆🏽‍♂️ man gesturing OK: medium skin tone +1F646 1F3FD 200D 2642 ; minimally-qualified # 🙆🏽‍♂ man gesturing OK: medium skin tone +1F646 1F3FE 200D 2642 FE0F ; fully-qualified # 🙆🏾‍♂️ man gesturing OK: medium-dark skin tone +1F646 1F3FE 200D 2642 ; minimally-qualified # 🙆🏾‍♂ man gesturing OK: medium-dark skin tone +1F646 1F3FF 200D 2642 FE0F ; fully-qualified # 🙆🏿‍♂️ man gesturing OK: dark skin tone +1F646 1F3FF 200D 2642 ; minimally-qualified # 🙆🏿‍♂ man gesturing OK: dark skin tone +1F646 200D 2640 FE0F ; fully-qualified # 🙆‍♀️ woman gesturing OK +1F646 200D 2640 ; minimally-qualified # 🙆‍♀ woman gesturing OK +1F646 1F3FB 200D 2640 FE0F ; fully-qualified # 🙆🏻‍♀️ woman gesturing OK: light skin tone +1F646 1F3FB 200D 2640 ; minimally-qualified # 🙆🏻‍♀ woman gesturing OK: light skin tone +1F646 1F3FC 200D 2640 FE0F ; fully-qualified # 🙆🏼‍♀️ woman gesturing OK: medium-light skin tone +1F646 1F3FC 200D 2640 ; minimally-qualified # 🙆🏼‍♀ woman gesturing OK: medium-light skin tone +1F646 1F3FD 200D 2640 FE0F ; fully-qualified # 🙆🏽‍♀️ woman gesturing OK: medium skin tone +1F646 1F3FD 200D 2640 ; minimally-qualified # 🙆🏽‍♀ woman gesturing OK: medium skin tone +1F646 1F3FE 200D 2640 FE0F ; fully-qualified # 🙆🏾‍♀️ woman gesturing OK: medium-dark skin tone +1F646 1F3FE 200D 2640 ; minimally-qualified # 🙆🏾‍♀ woman gesturing OK: medium-dark skin tone +1F646 1F3FF 200D 2640 FE0F ; fully-qualified # 🙆🏿‍♀️ woman gesturing OK: dark skin tone +1F646 1F3FF 200D 2640 ; minimally-qualified # 🙆🏿‍♀ woman gesturing OK: dark skin tone +1F481 ; fully-qualified # 💁 person tipping hand +1F481 1F3FB ; fully-qualified # 💁🏻 person tipping hand: light skin tone +1F481 1F3FC ; fully-qualified # 💁🏼 person tipping hand: medium-light skin tone +1F481 1F3FD ; fully-qualified # 💁🏽 person tipping hand: medium skin tone +1F481 1F3FE ; fully-qualified # 💁🏾 person tipping hand: medium-dark skin tone +1F481 1F3FF ; fully-qualified # 💁🏿 person tipping hand: dark skin tone +1F481 200D 2642 FE0F ; fully-qualified # 💁‍♂️ man tipping hand +1F481 200D 2642 ; minimally-qualified # 💁‍♂ man tipping hand +1F481 1F3FB 200D 2642 FE0F ; fully-qualified # 💁🏻‍♂️ man tipping hand: light skin tone +1F481 1F3FB 200D 2642 ; minimally-qualified # 💁🏻‍♂ man tipping hand: light skin tone +1F481 1F3FC 200D 2642 FE0F ; fully-qualified # 💁🏼‍♂️ man tipping hand: medium-light skin tone +1F481 1F3FC 200D 2642 ; minimally-qualified # 💁🏼‍♂ man tipping hand: medium-light skin tone +1F481 1F3FD 200D 2642 FE0F ; fully-qualified # 💁🏽‍♂️ man tipping hand: medium skin tone +1F481 1F3FD 200D 2642 ; minimally-qualified # 💁🏽‍♂ man tipping hand: medium skin tone +1F481 1F3FE 200D 2642 FE0F ; fully-qualified # 💁🏾‍♂️ man tipping hand: medium-dark skin tone +1F481 1F3FE 200D 2642 ; minimally-qualified # 💁🏾‍♂ man tipping hand: medium-dark skin tone +1F481 1F3FF 200D 2642 FE0F ; fully-qualified # 💁🏿‍♂️ man tipping hand: dark skin tone +1F481 1F3FF 200D 2642 ; minimally-qualified # 💁🏿‍♂ man tipping hand: dark skin tone +1F481 200D 2640 FE0F ; fully-qualified # 💁‍♀️ woman tipping hand +1F481 200D 2640 ; minimally-qualified # 💁‍♀ woman tipping hand +1F481 1F3FB 200D 2640 FE0F ; fully-qualified # 💁🏻‍♀️ woman tipping hand: light skin tone +1F481 1F3FB 200D 2640 ; minimally-qualified # 💁🏻‍♀ woman tipping hand: light skin tone +1F481 1F3FC 200D 2640 FE0F ; fully-qualified # 💁🏼‍♀️ woman tipping hand: medium-light skin tone +1F481 1F3FC 200D 2640 ; minimally-qualified # 💁🏼‍♀ woman tipping hand: medium-light skin tone +1F481 1F3FD 200D 2640 FE0F ; fully-qualified # 💁🏽‍♀️ woman tipping hand: medium skin tone +1F481 1F3FD 200D 2640 ; minimally-qualified # 💁🏽‍♀ woman tipping hand: medium skin tone +1F481 1F3FE 200D 2640 FE0F ; fully-qualified # 💁🏾‍♀️ woman tipping hand: medium-dark skin tone +1F481 1F3FE 200D 2640 ; minimally-qualified # 💁🏾‍♀ woman tipping hand: medium-dark skin tone +1F481 1F3FF 200D 2640 FE0F ; fully-qualified # 💁🏿‍♀️ woman tipping hand: dark skin tone +1F481 1F3FF 200D 2640 ; minimally-qualified # 💁🏿‍♀ woman tipping hand: dark skin tone +1F64B ; fully-qualified # 🙋 person raising hand +1F64B 1F3FB ; fully-qualified # 🙋🏻 person raising hand: light skin tone +1F64B 1F3FC ; fully-qualified # 🙋🏼 person raising hand: medium-light skin tone +1F64B 1F3FD ; fully-qualified # 🙋🏽 person raising hand: medium skin tone +1F64B 1F3FE ; fully-qualified # 🙋🏾 person raising hand: medium-dark skin tone +1F64B 1F3FF ; fully-qualified # 🙋🏿 person raising hand: dark skin tone +1F64B 200D 2642 FE0F ; fully-qualified # 🙋‍♂️ man raising hand +1F64B 200D 2642 ; minimally-qualified # 🙋‍♂ man raising hand +1F64B 1F3FB 200D 2642 FE0F ; fully-qualified # 🙋🏻‍♂️ man raising hand: light skin tone +1F64B 1F3FB 200D 2642 ; minimally-qualified # 🙋🏻‍♂ man raising hand: light skin tone +1F64B 1F3FC 200D 2642 FE0F ; fully-qualified # 🙋🏼‍♂️ man raising hand: medium-light skin tone +1F64B 1F3FC 200D 2642 ; minimally-qualified # 🙋🏼‍♂ man raising hand: medium-light skin tone +1F64B 1F3FD 200D 2642 FE0F ; fully-qualified # 🙋🏽‍♂️ man raising hand: medium skin tone +1F64B 1F3FD 200D 2642 ; minimally-qualified # 🙋🏽‍♂ man raising hand: medium skin tone +1F64B 1F3FE 200D 2642 FE0F ; fully-qualified # 🙋🏾‍♂️ man raising hand: medium-dark skin tone +1F64B 1F3FE 200D 2642 ; minimally-qualified # 🙋🏾‍♂ man raising hand: medium-dark skin tone +1F64B 1F3FF 200D 2642 FE0F ; fully-qualified # 🙋🏿‍♂️ man raising hand: dark skin tone +1F64B 1F3FF 200D 2642 ; minimally-qualified # 🙋🏿‍♂ man raising hand: dark skin tone +1F64B 200D 2640 FE0F ; fully-qualified # 🙋‍♀️ woman raising hand +1F64B 200D 2640 ; minimally-qualified # 🙋‍♀ woman raising hand +1F64B 1F3FB 200D 2640 FE0F ; fully-qualified # 🙋🏻‍♀️ woman raising hand: light skin tone +1F64B 1F3FB 200D 2640 ; minimally-qualified # 🙋🏻‍♀ woman raising hand: light skin tone +1F64B 1F3FC 200D 2640 FE0F ; fully-qualified # 🙋🏼‍♀️ woman raising hand: medium-light skin tone +1F64B 1F3FC 200D 2640 ; minimally-qualified # 🙋🏼‍♀ woman raising hand: medium-light skin tone +1F64B 1F3FD 200D 2640 FE0F ; fully-qualified # 🙋🏽‍♀️ woman raising hand: medium skin tone +1F64B 1F3FD 200D 2640 ; minimally-qualified # 🙋🏽‍♀ woman raising hand: medium skin tone +1F64B 1F3FE 200D 2640 FE0F ; fully-qualified # 🙋🏾‍♀️ woman raising hand: medium-dark skin tone +1F64B 1F3FE 200D 2640 ; minimally-qualified # 🙋🏾‍♀ woman raising hand: medium-dark skin tone +1F64B 1F3FF 200D 2640 FE0F ; fully-qualified # 🙋🏿‍♀️ woman raising hand: dark skin tone +1F64B 1F3FF 200D 2640 ; minimally-qualified # 🙋🏿‍♀ woman raising hand: dark skin tone +1F9CF ; fully-qualified # 🧏 deaf person +1F9CF 1F3FB ; fully-qualified # 🧏🏻 deaf person: light skin tone +1F9CF 1F3FC ; fully-qualified # 🧏🏼 deaf person: medium-light skin tone +1F9CF 1F3FD ; fully-qualified # 🧏🏽 deaf person: medium skin tone +1F9CF 1F3FE ; fully-qualified # 🧏🏾 deaf person: medium-dark skin tone +1F9CF 1F3FF ; fully-qualified # 🧏🏿 deaf person: dark skin tone +1F9CF 200D 2642 FE0F ; fully-qualified # 🧏‍♂️ deaf man +1F9CF 200D 2642 ; minimally-qualified # 🧏‍♂ deaf man +1F9CF 1F3FB 200D 2642 FE0F ; fully-qualified # 🧏🏻‍♂️ deaf man: light skin tone +1F9CF 1F3FB 200D 2642 ; minimally-qualified # 🧏🏻‍♂ deaf man: light skin tone +1F9CF 1F3FC 200D 2642 FE0F ; fully-qualified # 🧏🏼‍♂️ deaf man: medium-light skin tone +1F9CF 1F3FC 200D 2642 ; minimally-qualified # 🧏🏼‍♂ deaf man: medium-light skin tone +1F9CF 1F3FD 200D 2642 FE0F ; fully-qualified # 🧏🏽‍♂️ deaf man: medium skin tone +1F9CF 1F3FD 200D 2642 ; minimally-qualified # 🧏🏽‍♂ deaf man: medium skin tone +1F9CF 1F3FE 200D 2642 FE0F ; fully-qualified # 🧏🏾‍♂️ deaf man: medium-dark skin tone +1F9CF 1F3FE 200D 2642 ; minimally-qualified # 🧏🏾‍♂ deaf man: medium-dark skin tone +1F9CF 1F3FF 200D 2642 FE0F ; fully-qualified # 🧏🏿‍♂️ deaf man: dark skin tone +1F9CF 1F3FF 200D 2642 ; minimally-qualified # 🧏🏿‍♂ deaf man: dark skin tone +1F9CF 200D 2640 FE0F ; fully-qualified # 🧏‍♀️ deaf woman +1F9CF 200D 2640 ; minimally-qualified # 🧏‍♀ deaf woman +1F9CF 1F3FB 200D 2640 FE0F ; fully-qualified # 🧏🏻‍♀️ deaf woman: light skin tone +1F9CF 1F3FB 200D 2640 ; minimally-qualified # 🧏🏻‍♀ deaf woman: light skin tone +1F9CF 1F3FC 200D 2640 FE0F ; fully-qualified # 🧏🏼‍♀️ deaf woman: medium-light skin tone +1F9CF 1F3FC 200D 2640 ; minimally-qualified # 🧏🏼‍♀ deaf woman: medium-light skin tone +1F9CF 1F3FD 200D 2640 FE0F ; fully-qualified # 🧏🏽‍♀️ deaf woman: medium skin tone +1F9CF 1F3FD 200D 2640 ; minimally-qualified # 🧏🏽‍♀ deaf woman: medium skin tone +1F9CF 1F3FE 200D 2640 FE0F ; fully-qualified # 🧏🏾‍♀️ deaf woman: medium-dark skin tone +1F9CF 1F3FE 200D 2640 ; minimally-qualified # 🧏🏾‍♀ deaf woman: medium-dark skin tone +1F9CF 1F3FF 200D 2640 FE0F ; fully-qualified # 🧏🏿‍♀️ deaf woman: dark skin tone +1F9CF 1F3FF 200D 2640 ; minimally-qualified # 🧏🏿‍♀ deaf woman: dark skin tone +1F647 ; fully-qualified # 🙇 person bowing +1F647 1F3FB ; fully-qualified # 🙇🏻 person bowing: light skin tone +1F647 1F3FC ; fully-qualified # 🙇🏼 person bowing: medium-light skin tone +1F647 1F3FD ; fully-qualified # 🙇🏽 person bowing: medium skin tone +1F647 1F3FE ; fully-qualified # 🙇🏾 person bowing: medium-dark skin tone +1F647 1F3FF ; fully-qualified # 🙇🏿 person bowing: dark skin tone +1F647 200D 2642 FE0F ; fully-qualified # 🙇‍♂️ man bowing +1F647 200D 2642 ; minimally-qualified # 🙇‍♂ man bowing +1F647 1F3FB 200D 2642 FE0F ; fully-qualified # 🙇🏻‍♂️ man bowing: light skin tone +1F647 1F3FB 200D 2642 ; minimally-qualified # 🙇🏻‍♂ man bowing: light skin tone +1F647 1F3FC 200D 2642 FE0F ; fully-qualified # 🙇🏼‍♂️ man bowing: medium-light skin tone +1F647 1F3FC 200D 2642 ; minimally-qualified # 🙇🏼‍♂ man bowing: medium-light skin tone +1F647 1F3FD 200D 2642 FE0F ; fully-qualified # 🙇🏽‍♂️ man bowing: medium skin tone +1F647 1F3FD 200D 2642 ; minimally-qualified # 🙇🏽‍♂ man bowing: medium skin tone +1F647 1F3FE 200D 2642 FE0F ; fully-qualified # 🙇🏾‍♂️ man bowing: medium-dark skin tone +1F647 1F3FE 200D 2642 ; minimally-qualified # 🙇🏾‍♂ man bowing: medium-dark skin tone +1F647 1F3FF 200D 2642 FE0F ; fully-qualified # 🙇🏿‍♂️ man bowing: dark skin tone +1F647 1F3FF 200D 2642 ; minimally-qualified # 🙇🏿‍♂ man bowing: dark skin tone +1F647 200D 2640 FE0F ; fully-qualified # 🙇‍♀️ woman bowing +1F647 200D 2640 ; minimally-qualified # 🙇‍♀ woman bowing +1F647 1F3FB 200D 2640 FE0F ; fully-qualified # 🙇🏻‍♀️ woman bowing: light skin tone +1F647 1F3FB 200D 2640 ; minimally-qualified # 🙇🏻‍♀ woman bowing: light skin tone +1F647 1F3FC 200D 2640 FE0F ; fully-qualified # 🙇🏼‍♀️ woman bowing: medium-light skin tone +1F647 1F3FC 200D 2640 ; minimally-qualified # 🙇🏼‍♀ woman bowing: medium-light skin tone +1F647 1F3FD 200D 2640 FE0F ; fully-qualified # 🙇🏽‍♀️ woman bowing: medium skin tone +1F647 1F3FD 200D 2640 ; minimally-qualified # 🙇🏽‍♀ woman bowing: medium skin tone +1F647 1F3FE 200D 2640 FE0F ; fully-qualified # 🙇🏾‍♀️ woman bowing: medium-dark skin tone +1F647 1F3FE 200D 2640 ; minimally-qualified # 🙇🏾‍♀ woman bowing: medium-dark skin tone +1F647 1F3FF 200D 2640 FE0F ; fully-qualified # 🙇🏿‍♀️ woman bowing: dark skin tone +1F647 1F3FF 200D 2640 ; minimally-qualified # 🙇🏿‍♀ woman bowing: dark skin tone +1F926 ; fully-qualified # 🤦 person facepalming +1F926 1F3FB ; fully-qualified # 🤦🏻 person facepalming: light skin tone +1F926 1F3FC ; fully-qualified # 🤦🏼 person facepalming: medium-light skin tone +1F926 1F3FD ; fully-qualified # 🤦🏽 person facepalming: medium skin tone +1F926 1F3FE ; fully-qualified # 🤦🏾 person facepalming: medium-dark skin tone +1F926 1F3FF ; fully-qualified # 🤦🏿 person facepalming: dark skin tone +1F926 200D 2642 FE0F ; fully-qualified # 🤦‍♂️ man facepalming +1F926 200D 2642 ; minimally-qualified # 🤦‍♂ man facepalming +1F926 1F3FB 200D 2642 FE0F ; fully-qualified # 🤦🏻‍♂️ man facepalming: light skin tone +1F926 1F3FB 200D 2642 ; minimally-qualified # 🤦🏻‍♂ man facepalming: light skin tone +1F926 1F3FC 200D 2642 FE0F ; fully-qualified # 🤦🏼‍♂️ man facepalming: medium-light skin tone +1F926 1F3FC 200D 2642 ; minimally-qualified # 🤦🏼‍♂ man facepalming: medium-light skin tone +1F926 1F3FD 200D 2642 FE0F ; fully-qualified # 🤦🏽‍♂️ man facepalming: medium skin tone +1F926 1F3FD 200D 2642 ; minimally-qualified # 🤦🏽‍♂ man facepalming: medium skin tone +1F926 1F3FE 200D 2642 FE0F ; fully-qualified # 🤦🏾‍♂️ man facepalming: medium-dark skin tone +1F926 1F3FE 200D 2642 ; minimally-qualified # 🤦🏾‍♂ man facepalming: medium-dark skin tone +1F926 1F3FF 200D 2642 FE0F ; fully-qualified # 🤦🏿‍♂️ man facepalming: dark skin tone +1F926 1F3FF 200D 2642 ; minimally-qualified # 🤦🏿‍♂ man facepalming: dark skin tone +1F926 200D 2640 FE0F ; fully-qualified # 🤦‍♀️ woman facepalming +1F926 200D 2640 ; minimally-qualified # 🤦‍♀ woman facepalming +1F926 1F3FB 200D 2640 FE0F ; fully-qualified # 🤦🏻‍♀️ woman facepalming: light skin tone +1F926 1F3FB 200D 2640 ; minimally-qualified # 🤦🏻‍♀ woman facepalming: light skin tone +1F926 1F3FC 200D 2640 FE0F ; fully-qualified # 🤦🏼‍♀️ woman facepalming: medium-light skin tone +1F926 1F3FC 200D 2640 ; minimally-qualified # 🤦🏼‍♀ woman facepalming: medium-light skin tone +1F926 1F3FD 200D 2640 FE0F ; fully-qualified # 🤦🏽‍♀️ woman facepalming: medium skin tone +1F926 1F3FD 200D 2640 ; minimally-qualified # 🤦🏽‍♀ woman facepalming: medium skin tone +1F926 1F3FE 200D 2640 FE0F ; fully-qualified # 🤦🏾‍♀️ woman facepalming: medium-dark skin tone +1F926 1F3FE 200D 2640 ; minimally-qualified # 🤦🏾‍♀ woman facepalming: medium-dark skin tone +1F926 1F3FF 200D 2640 FE0F ; fully-qualified # 🤦🏿‍♀️ woman facepalming: dark skin tone +1F926 1F3FF 200D 2640 ; minimally-qualified # 🤦🏿‍♀ woman facepalming: dark skin tone +1F937 ; fully-qualified # 🤷 person shrugging +1F937 1F3FB ; fully-qualified # 🤷🏻 person shrugging: light skin tone +1F937 1F3FC ; fully-qualified # 🤷🏼 person shrugging: medium-light skin tone +1F937 1F3FD ; fully-qualified # 🤷🏽 person shrugging: medium skin tone +1F937 1F3FE ; fully-qualified # 🤷🏾 person shrugging: medium-dark skin tone +1F937 1F3FF ; fully-qualified # 🤷🏿 person shrugging: dark skin tone +1F937 200D 2642 FE0F ; fully-qualified # 🤷‍♂️ man shrugging +1F937 200D 2642 ; minimally-qualified # 🤷‍♂ man shrugging +1F937 1F3FB 200D 2642 FE0F ; fully-qualified # 🤷🏻‍♂️ man shrugging: light skin tone +1F937 1F3FB 200D 2642 ; minimally-qualified # 🤷🏻‍♂ man shrugging: light skin tone +1F937 1F3FC 200D 2642 FE0F ; fully-qualified # 🤷🏼‍♂️ man shrugging: medium-light skin tone +1F937 1F3FC 200D 2642 ; minimally-qualified # 🤷🏼‍♂ man shrugging: medium-light skin tone +1F937 1F3FD 200D 2642 FE0F ; fully-qualified # 🤷🏽‍♂️ man shrugging: medium skin tone +1F937 1F3FD 200D 2642 ; minimally-qualified # 🤷🏽‍♂ man shrugging: medium skin tone +1F937 1F3FE 200D 2642 FE0F ; fully-qualified # 🤷🏾‍♂️ man shrugging: medium-dark skin tone +1F937 1F3FE 200D 2642 ; minimally-qualified # 🤷🏾‍♂ man shrugging: medium-dark skin tone +1F937 1F3FF 200D 2642 FE0F ; fully-qualified # 🤷🏿‍♂️ man shrugging: dark skin tone +1F937 1F3FF 200D 2642 ; minimally-qualified # 🤷🏿‍♂ man shrugging: dark skin tone +1F937 200D 2640 FE0F ; fully-qualified # 🤷‍♀️ woman shrugging +1F937 200D 2640 ; minimally-qualified # 🤷‍♀ woman shrugging +1F937 1F3FB 200D 2640 FE0F ; fully-qualified # 🤷🏻‍♀️ woman shrugging: light skin tone +1F937 1F3FB 200D 2640 ; minimally-qualified # 🤷🏻‍♀ woman shrugging: light skin tone +1F937 1F3FC 200D 2640 FE0F ; fully-qualified # 🤷🏼‍♀️ woman shrugging: medium-light skin tone +1F937 1F3FC 200D 2640 ; minimally-qualified # 🤷🏼‍♀ woman shrugging: medium-light skin tone +1F937 1F3FD 200D 2640 FE0F ; fully-qualified # 🤷🏽‍♀️ woman shrugging: medium skin tone +1F937 1F3FD 200D 2640 ; minimally-qualified # 🤷🏽‍♀ woman shrugging: medium skin tone +1F937 1F3FE 200D 2640 FE0F ; fully-qualified # 🤷🏾‍♀️ woman shrugging: medium-dark skin tone +1F937 1F3FE 200D 2640 ; minimally-qualified # 🤷🏾‍♀ woman shrugging: medium-dark skin tone +1F937 1F3FF 200D 2640 FE0F ; fully-qualified # 🤷🏿‍♀️ woman shrugging: dark skin tone +1F937 1F3FF 200D 2640 ; minimally-qualified # 🤷🏿‍♀ woman shrugging: dark skin tone + +# subgroup: person-role +1F468 200D 2695 FE0F ; fully-qualified # 👨‍⚕️ man health worker +1F468 200D 2695 ; minimally-qualified # 👨‍⚕ man health worker +1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻‍⚕️ man health worker: light skin tone +1F468 1F3FB 200D 2695 ; minimally-qualified # 👨🏻‍⚕ man health worker: light skin tone +1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼‍⚕️ man health worker: medium-light skin tone +1F468 1F3FC 200D 2695 ; minimally-qualified # 👨🏼‍⚕ man health worker: medium-light skin tone +1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽‍⚕️ man health worker: medium skin tone +1F468 1F3FD 200D 2695 ; minimally-qualified # 👨🏽‍⚕ man health worker: medium skin tone +1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾‍⚕️ man health worker: medium-dark skin tone +1F468 1F3FE 200D 2695 ; minimally-qualified # 👨🏾‍⚕ man health worker: medium-dark skin tone +1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿‍⚕️ man health worker: dark skin tone +1F468 1F3FF 200D 2695 ; minimally-qualified # 👨🏿‍⚕ man health worker: dark skin tone +1F469 200D 2695 FE0F ; fully-qualified # 👩‍⚕️ woman health worker +1F469 200D 2695 ; minimally-qualified # 👩‍⚕ woman health worker +1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻‍⚕️ woman health worker: light skin tone +1F469 1F3FB 200D 2695 ; minimally-qualified # 👩🏻‍⚕ woman health worker: light skin tone +1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼‍⚕️ woman health worker: medium-light skin tone +1F469 1F3FC 200D 2695 ; minimally-qualified # 👩🏼‍⚕ woman health worker: medium-light skin tone +1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽‍⚕️ woman health worker: medium skin tone +1F469 1F3FD 200D 2695 ; minimally-qualified # 👩🏽‍⚕ woman health worker: medium skin tone +1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾‍⚕️ woman health worker: medium-dark skin tone +1F469 1F3FE 200D 2695 ; minimally-qualified # 👩🏾‍⚕ woman health worker: medium-dark skin tone +1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿‍⚕️ woman health worker: dark skin tone +1F469 1F3FF 200D 2695 ; minimally-qualified # 👩🏿‍⚕ woman health worker: dark skin tone +1F468 200D 1F393 ; fully-qualified # 👨‍🎓 man student +1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻‍🎓 man student: light skin tone +1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼‍🎓 man student: medium-light skin tone +1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽‍🎓 man student: medium skin tone +1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾‍🎓 man student: medium-dark skin tone +1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿‍🎓 man student: dark skin tone +1F469 200D 1F393 ; fully-qualified # 👩‍🎓 woman student +1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻‍🎓 woman student: light skin tone +1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼‍🎓 woman student: medium-light skin tone +1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽‍🎓 woman student: medium skin tone +1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾‍🎓 woman student: medium-dark skin tone +1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿‍🎓 woman student: dark skin tone +1F468 200D 1F3EB ; fully-qualified # 👨‍🏫 man teacher +1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻‍🏫 man teacher: light skin tone +1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼‍🏫 man teacher: medium-light skin tone +1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽‍🏫 man teacher: medium skin tone +1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾‍🏫 man teacher: medium-dark skin tone +1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿‍🏫 man teacher: dark skin tone +1F469 200D 1F3EB ; fully-qualified # 👩‍🏫 woman teacher +1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻‍🏫 woman teacher: light skin tone +1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼‍🏫 woman teacher: medium-light skin tone +1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽‍🏫 woman teacher: medium skin tone +1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾‍🏫 woman teacher: medium-dark skin tone +1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿‍🏫 woman teacher: dark skin tone +1F468 200D 2696 FE0F ; fully-qualified # 👨‍⚖️ man judge +1F468 200D 2696 ; minimally-qualified # 👨‍⚖ man judge +1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻‍⚖️ man judge: light skin tone +1F468 1F3FB 200D 2696 ; minimally-qualified # 👨🏻‍⚖ man judge: light skin tone +1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼‍⚖️ man judge: medium-light skin tone +1F468 1F3FC 200D 2696 ; minimally-qualified # 👨🏼‍⚖ man judge: medium-light skin tone +1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽‍⚖️ man judge: medium skin tone +1F468 1F3FD 200D 2696 ; minimally-qualified # 👨🏽‍⚖ man judge: medium skin tone +1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾‍⚖️ man judge: medium-dark skin tone +1F468 1F3FE 200D 2696 ; minimally-qualified # 👨🏾‍⚖ man judge: medium-dark skin tone +1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿‍⚖️ man judge: dark skin tone +1F468 1F3FF 200D 2696 ; minimally-qualified # 👨🏿‍⚖ man judge: dark skin tone +1F469 200D 2696 FE0F ; fully-qualified # 👩‍⚖️ woman judge +1F469 200D 2696 ; minimally-qualified # 👩‍⚖ woman judge +1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻‍⚖️ woman judge: light skin tone +1F469 1F3FB 200D 2696 ; minimally-qualified # 👩🏻‍⚖ woman judge: light skin tone +1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼‍⚖️ woman judge: medium-light skin tone +1F469 1F3FC 200D 2696 ; minimally-qualified # 👩🏼‍⚖ woman judge: medium-light skin tone +1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽‍⚖️ woman judge: medium skin tone +1F469 1F3FD 200D 2696 ; minimally-qualified # 👩🏽‍⚖ woman judge: medium skin tone +1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾‍⚖️ woman judge: medium-dark skin tone +1F469 1F3FE 200D 2696 ; minimally-qualified # 👩🏾‍⚖ woman judge: medium-dark skin tone +1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿‍⚖️ woman judge: dark skin tone +1F469 1F3FF 200D 2696 ; minimally-qualified # 👩🏿‍⚖ woman judge: dark skin tone +1F468 200D 1F33E ; fully-qualified # 👨‍🌾 man farmer +1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻‍🌾 man farmer: light skin tone +1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼‍🌾 man farmer: medium-light skin tone +1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽‍🌾 man farmer: medium skin tone +1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾‍🌾 man farmer: medium-dark skin tone +1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿‍🌾 man farmer: dark skin tone +1F469 200D 1F33E ; fully-qualified # 👩‍🌾 woman farmer +1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻‍🌾 woman farmer: light skin tone +1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼‍🌾 woman farmer: medium-light skin tone +1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽‍🌾 woman farmer: medium skin tone +1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾‍🌾 woman farmer: medium-dark skin tone +1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿‍🌾 woman farmer: dark skin tone +1F468 200D 1F373 ; fully-qualified # 👨‍🍳 man cook +1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻‍🍳 man cook: light skin tone +1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼‍🍳 man cook: medium-light skin tone +1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽‍🍳 man cook: medium skin tone +1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾‍🍳 man cook: medium-dark skin tone +1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿‍🍳 man cook: dark skin tone +1F469 200D 1F373 ; fully-qualified # 👩‍🍳 woman cook +1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻‍🍳 woman cook: light skin tone +1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼‍🍳 woman cook: medium-light skin tone +1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽‍🍳 woman cook: medium skin tone +1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾‍🍳 woman cook: medium-dark skin tone +1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿‍🍳 woman cook: dark skin tone +1F468 200D 1F527 ; fully-qualified # 👨‍🔧 man mechanic +1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻‍🔧 man mechanic: light skin tone +1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼‍🔧 man mechanic: medium-light skin tone +1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽‍🔧 man mechanic: medium skin tone +1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾‍🔧 man mechanic: medium-dark skin tone +1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿‍🔧 man mechanic: dark skin tone +1F469 200D 1F527 ; fully-qualified # 👩‍🔧 woman mechanic +1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻‍🔧 woman mechanic: light skin tone +1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼‍🔧 woman mechanic: medium-light skin tone +1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽‍🔧 woman mechanic: medium skin tone +1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾‍🔧 woman mechanic: medium-dark skin tone +1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿‍🔧 woman mechanic: dark skin tone +1F468 200D 1F3ED ; fully-qualified # 👨‍🏭 man factory worker +1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻‍🏭 man factory worker: light skin tone +1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼‍🏭 man factory worker: medium-light skin tone +1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽‍🏭 man factory worker: medium skin tone +1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾‍🏭 man factory worker: medium-dark skin tone +1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿‍🏭 man factory worker: dark skin tone +1F469 200D 1F3ED ; fully-qualified # 👩‍🏭 woman factory worker +1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻‍🏭 woman factory worker: light skin tone +1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼‍🏭 woman factory worker: medium-light skin tone +1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽‍🏭 woman factory worker: medium skin tone +1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾‍🏭 woman factory worker: medium-dark skin tone +1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿‍🏭 woman factory worker: dark skin tone +1F468 200D 1F4BC ; fully-qualified # 👨‍💼 man office worker +1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻‍💼 man office worker: light skin tone +1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼‍💼 man office worker: medium-light skin tone +1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽‍💼 man office worker: medium skin tone +1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾‍💼 man office worker: medium-dark skin tone +1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿‍💼 man office worker: dark skin tone +1F469 200D 1F4BC ; fully-qualified # 👩‍💼 woman office worker +1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻‍💼 woman office worker: light skin tone +1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼‍💼 woman office worker: medium-light skin tone +1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽‍💼 woman office worker: medium skin tone +1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾‍💼 woman office worker: medium-dark skin tone +1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿‍💼 woman office worker: dark skin tone +1F468 200D 1F52C ; fully-qualified # 👨‍🔬 man scientist +1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻‍🔬 man scientist: light skin tone +1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼‍🔬 man scientist: medium-light skin tone +1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽‍🔬 man scientist: medium skin tone +1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾‍🔬 man scientist: medium-dark skin tone +1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿‍🔬 man scientist: dark skin tone +1F469 200D 1F52C ; fully-qualified # 👩‍🔬 woman scientist +1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻‍🔬 woman scientist: light skin tone +1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼‍🔬 woman scientist: medium-light skin tone +1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽‍🔬 woman scientist: medium skin tone +1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾‍🔬 woman scientist: medium-dark skin tone +1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿‍🔬 woman scientist: dark skin tone +1F468 200D 1F4BB ; fully-qualified # 👨‍💻 man technologist +1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻‍💻 man technologist: light skin tone +1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼‍💻 man technologist: medium-light skin tone +1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽‍💻 man technologist: medium skin tone +1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾‍💻 man technologist: medium-dark skin tone +1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿‍💻 man technologist: dark skin tone +1F469 200D 1F4BB ; fully-qualified # 👩‍💻 woman technologist +1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻‍💻 woman technologist: light skin tone +1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼‍💻 woman technologist: medium-light skin tone +1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽‍💻 woman technologist: medium skin tone +1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾‍💻 woman technologist: medium-dark skin tone +1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿‍💻 woman technologist: dark skin tone +1F468 200D 1F3A4 ; fully-qualified # 👨‍🎤 man singer +1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻‍🎤 man singer: light skin tone +1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼‍🎤 man singer: medium-light skin tone +1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽‍🎤 man singer: medium skin tone +1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾‍🎤 man singer: medium-dark skin tone +1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿‍🎤 man singer: dark skin tone +1F469 200D 1F3A4 ; fully-qualified # 👩‍🎤 woman singer +1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻‍🎤 woman singer: light skin tone +1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼‍🎤 woman singer: medium-light skin tone +1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽‍🎤 woman singer: medium skin tone +1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾‍🎤 woman singer: medium-dark skin tone +1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿‍🎤 woman singer: dark skin tone +1F468 200D 1F3A8 ; fully-qualified # 👨‍🎨 man artist +1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻‍🎨 man artist: light skin tone +1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼‍🎨 man artist: medium-light skin tone +1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽‍🎨 man artist: medium skin tone +1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾‍🎨 man artist: medium-dark skin tone +1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿‍🎨 man artist: dark skin tone +1F469 200D 1F3A8 ; fully-qualified # 👩‍🎨 woman artist +1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻‍🎨 woman artist: light skin tone +1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼‍🎨 woman artist: medium-light skin tone +1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽‍🎨 woman artist: medium skin tone +1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾‍🎨 woman artist: medium-dark skin tone +1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿‍🎨 woman artist: dark skin tone +1F468 200D 2708 FE0F ; fully-qualified # 👨‍✈️ man pilot +1F468 200D 2708 ; minimally-qualified # 👨‍✈ man pilot +1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻‍✈️ man pilot: light skin tone +1F468 1F3FB 200D 2708 ; minimally-qualified # 👨🏻‍✈ man pilot: light skin tone +1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼‍✈️ man pilot: medium-light skin tone +1F468 1F3FC 200D 2708 ; minimally-qualified # 👨🏼‍✈ man pilot: medium-light skin tone +1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽‍✈️ man pilot: medium skin tone +1F468 1F3FD 200D 2708 ; minimally-qualified # 👨🏽‍✈ man pilot: medium skin tone +1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾‍✈️ man pilot: medium-dark skin tone +1F468 1F3FE 200D 2708 ; minimally-qualified # 👨🏾‍✈ man pilot: medium-dark skin tone +1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿‍✈️ man pilot: dark skin tone +1F468 1F3FF 200D 2708 ; minimally-qualified # 👨🏿‍✈ man pilot: dark skin tone +1F469 200D 2708 FE0F ; fully-qualified # 👩‍✈️ woman pilot +1F469 200D 2708 ; minimally-qualified # 👩‍✈ woman pilot +1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻‍✈️ woman pilot: light skin tone +1F469 1F3FB 200D 2708 ; minimally-qualified # 👩🏻‍✈ woman pilot: light skin tone +1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼‍✈️ woman pilot: medium-light skin tone +1F469 1F3FC 200D 2708 ; minimally-qualified # 👩🏼‍✈ woman pilot: medium-light skin tone +1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽‍✈️ woman pilot: medium skin tone +1F469 1F3FD 200D 2708 ; minimally-qualified # 👩🏽‍✈ woman pilot: medium skin tone +1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾‍✈️ woman pilot: medium-dark skin tone +1F469 1F3FE 200D 2708 ; minimally-qualified # 👩🏾‍✈ woman pilot: medium-dark skin tone +1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿‍✈️ woman pilot: dark skin tone +1F469 1F3FF 200D 2708 ; minimally-qualified # 👩🏿‍✈ woman pilot: dark skin tone +1F468 200D 1F680 ; fully-qualified # 👨‍🚀 man astronaut +1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻‍🚀 man astronaut: light skin tone +1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼‍🚀 man astronaut: medium-light skin tone +1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽‍🚀 man astronaut: medium skin tone +1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾‍🚀 man astronaut: medium-dark skin tone +1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿‍🚀 man astronaut: dark skin tone +1F469 200D 1F680 ; fully-qualified # 👩‍🚀 woman astronaut +1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻‍🚀 woman astronaut: light skin tone +1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼‍🚀 woman astronaut: medium-light skin tone +1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽‍🚀 woman astronaut: medium skin tone +1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾‍🚀 woman astronaut: medium-dark skin tone +1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿‍🚀 woman astronaut: dark skin tone +1F468 200D 1F692 ; fully-qualified # 👨‍🚒 man firefighter +1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻‍🚒 man firefighter: light skin tone +1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼‍🚒 man firefighter: medium-light skin tone +1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽‍🚒 man firefighter: medium skin tone +1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾‍🚒 man firefighter: medium-dark skin tone +1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿‍🚒 man firefighter: dark skin tone +1F469 200D 1F692 ; fully-qualified # 👩‍🚒 woman firefighter +1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻‍🚒 woman firefighter: light skin tone +1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼‍🚒 woman firefighter: medium-light skin tone +1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽‍🚒 woman firefighter: medium skin tone +1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾‍🚒 woman firefighter: medium-dark skin tone +1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿‍🚒 woman firefighter: dark skin tone +1F46E ; fully-qualified # 👮 police officer +1F46E 1F3FB ; fully-qualified # 👮🏻 police officer: light skin tone +1F46E 1F3FC ; fully-qualified # 👮🏼 police officer: medium-light skin tone +1F46E 1F3FD ; fully-qualified # 👮🏽 police officer: medium skin tone +1F46E 1F3FE ; fully-qualified # 👮🏾 police officer: medium-dark skin tone +1F46E 1F3FF ; fully-qualified # 👮🏿 police officer: dark skin tone +1F46E 200D 2642 FE0F ; fully-qualified # 👮‍♂️ man police officer +1F46E 200D 2642 ; minimally-qualified # 👮‍♂ man police officer +1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻‍♂️ man police officer: light skin tone +1F46E 1F3FB 200D 2642 ; minimally-qualified # 👮🏻‍♂ man police officer: light skin tone +1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼‍♂️ man police officer: medium-light skin tone +1F46E 1F3FC 200D 2642 ; minimally-qualified # 👮🏼‍♂ man police officer: medium-light skin tone +1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽‍♂️ man police officer: medium skin tone +1F46E 1F3FD 200D 2642 ; minimally-qualified # 👮🏽‍♂ man police officer: medium skin tone +1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾‍♂️ man police officer: medium-dark skin tone +1F46E 1F3FE 200D 2642 ; minimally-qualified # 👮🏾‍♂ man police officer: medium-dark skin tone +1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿‍♂️ man police officer: dark skin tone +1F46E 1F3FF 200D 2642 ; minimally-qualified # 👮🏿‍♂ man police officer: dark skin tone +1F46E 200D 2640 FE0F ; fully-qualified # 👮‍♀️ woman police officer +1F46E 200D 2640 ; minimally-qualified # 👮‍♀ woman police officer +1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻‍♀️ woman police officer: light skin tone +1F46E 1F3FB 200D 2640 ; minimally-qualified # 👮🏻‍♀ woman police officer: light skin tone +1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼‍♀️ woman police officer: medium-light skin tone +1F46E 1F3FC 200D 2640 ; minimally-qualified # 👮🏼‍♀ woman police officer: medium-light skin tone +1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽‍♀️ woman police officer: medium skin tone +1F46E 1F3FD 200D 2640 ; minimally-qualified # 👮🏽‍♀ woman police officer: medium skin tone +1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾‍♀️ woman police officer: medium-dark skin tone +1F46E 1F3FE 200D 2640 ; minimally-qualified # 👮🏾‍♀ woman police officer: medium-dark skin tone +1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿‍♀️ woman police officer: dark skin tone +1F46E 1F3FF 200D 2640 ; minimally-qualified # 👮🏿‍♀ woman police officer: dark skin tone +1F575 FE0F ; fully-qualified # 🕵️ detective +1F575 ; unqualified # 🕵 detective +1F575 1F3FB ; fully-qualified # 🕵🏻 detective: light skin tone +1F575 1F3FC ; fully-qualified # 🕵🏼 detective: medium-light skin tone +1F575 1F3FD ; fully-qualified # 🕵🏽 detective: medium skin tone +1F575 1F3FE ; fully-qualified # 🕵🏾 detective: medium-dark skin tone +1F575 1F3FF ; fully-qualified # 🕵🏿 detective: dark skin tone +1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ man detective +1F575 200D 2642 FE0F ; unqualified # 🕵‍♂️ man detective +1F575 FE0F 200D 2642 ; unqualified # 🕵️‍♂ man detective +1F575 200D 2642 ; unqualified # 🕵‍♂ man detective +1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ man detective: light skin tone +1F575 1F3FB 200D 2642 ; minimally-qualified # 🕵🏻‍♂ man detective: light skin tone +1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼‍♂️ man detective: medium-light skin tone +1F575 1F3FC 200D 2642 ; minimally-qualified # 🕵🏼‍♂ man detective: medium-light skin tone +1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽‍♂️ man detective: medium skin tone +1F575 1F3FD 200D 2642 ; minimally-qualified # 🕵🏽‍♂ man detective: medium skin tone +1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾‍♂️ man detective: medium-dark skin tone +1F575 1F3FE 200D 2642 ; minimally-qualified # 🕵🏾‍♂ man detective: medium-dark skin tone +1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿‍♂️ man detective: dark skin tone +1F575 1F3FF 200D 2642 ; minimally-qualified # 🕵🏿‍♂ man detective: dark skin tone +1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ woman detective +1F575 200D 2640 FE0F ; unqualified # 🕵‍♀️ woman detective +1F575 FE0F 200D 2640 ; unqualified # 🕵️‍♀ woman detective +1F575 200D 2640 ; unqualified # 🕵‍♀ woman detective +1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ woman detective: light skin tone +1F575 1F3FB 200D 2640 ; minimally-qualified # 🕵🏻‍♀ woman detective: light skin tone +1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼‍♀️ woman detective: medium-light skin tone +1F575 1F3FC 200D 2640 ; minimally-qualified # 🕵🏼‍♀ woman detective: medium-light skin tone +1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽‍♀️ woman detective: medium skin tone +1F575 1F3FD 200D 2640 ; minimally-qualified # 🕵🏽‍♀ woman detective: medium skin tone +1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾‍♀️ woman detective: medium-dark skin tone +1F575 1F3FE 200D 2640 ; minimally-qualified # 🕵🏾‍♀ woman detective: medium-dark skin tone +1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿‍♀️ woman detective: dark skin tone +1F575 1F3FF 200D 2640 ; minimally-qualified # 🕵🏿‍♀ woman detective: dark skin tone +1F482 ; fully-qualified # 💂 guard +1F482 1F3FB ; fully-qualified # 💂🏻 guard: light skin tone +1F482 1F3FC ; fully-qualified # 💂🏼 guard: medium-light skin tone +1F482 1F3FD ; fully-qualified # 💂🏽 guard: medium skin tone +1F482 1F3FE ; fully-qualified # 💂🏾 guard: medium-dark skin tone +1F482 1F3FF ; fully-qualified # 💂🏿 guard: dark skin tone +1F482 200D 2642 FE0F ; fully-qualified # 💂‍♂️ man guard +1F482 200D 2642 ; minimally-qualified # 💂‍♂ man guard +1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻‍♂️ man guard: light skin tone +1F482 1F3FB 200D 2642 ; minimally-qualified # 💂🏻‍♂ man guard: light skin tone +1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼‍♂️ man guard: medium-light skin tone +1F482 1F3FC 200D 2642 ; minimally-qualified # 💂🏼‍♂ man guard: medium-light skin tone +1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽‍♂️ man guard: medium skin tone +1F482 1F3FD 200D 2642 ; minimally-qualified # 💂🏽‍♂ man guard: medium skin tone +1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾‍♂️ man guard: medium-dark skin tone +1F482 1F3FE 200D 2642 ; minimally-qualified # 💂🏾‍♂ man guard: medium-dark skin tone +1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿‍♂️ man guard: dark skin tone +1F482 1F3FF 200D 2642 ; minimally-qualified # 💂🏿‍♂ man guard: dark skin tone +1F482 200D 2640 FE0F ; fully-qualified # 💂‍♀️ woman guard +1F482 200D 2640 ; minimally-qualified # 💂‍♀ woman guard +1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻‍♀️ woman guard: light skin tone +1F482 1F3FB 200D 2640 ; minimally-qualified # 💂🏻‍♀ woman guard: light skin tone +1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼‍♀️ woman guard: medium-light skin tone +1F482 1F3FC 200D 2640 ; minimally-qualified # 💂🏼‍♀ woman guard: medium-light skin tone +1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽‍♀️ woman guard: medium skin tone +1F482 1F3FD 200D 2640 ; minimally-qualified # 💂🏽‍♀ woman guard: medium skin tone +1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾‍♀️ woman guard: medium-dark skin tone +1F482 1F3FE 200D 2640 ; minimally-qualified # 💂🏾‍♀ woman guard: medium-dark skin tone +1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿‍♀️ woman guard: dark skin tone +1F482 1F3FF 200D 2640 ; minimally-qualified # 💂🏿‍♀ woman guard: dark skin tone +1F477 ; fully-qualified # 👷 construction worker +1F477 1F3FB ; fully-qualified # 👷🏻 construction worker: light skin tone +1F477 1F3FC ; fully-qualified # 👷🏼 construction worker: medium-light skin tone +1F477 1F3FD ; fully-qualified # 👷🏽 construction worker: medium skin tone +1F477 1F3FE ; fully-qualified # 👷🏾 construction worker: medium-dark skin tone +1F477 1F3FF ; fully-qualified # 👷🏿 construction worker: dark skin tone +1F477 200D 2642 FE0F ; fully-qualified # 👷‍♂️ man construction worker +1F477 200D 2642 ; minimally-qualified # 👷‍♂ man construction worker +1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻‍♂️ man construction worker: light skin tone +1F477 1F3FB 200D 2642 ; minimally-qualified # 👷🏻‍♂ man construction worker: light skin tone +1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼‍♂️ man construction worker: medium-light skin tone +1F477 1F3FC 200D 2642 ; minimally-qualified # 👷🏼‍♂ man construction worker: medium-light skin tone +1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽‍♂️ man construction worker: medium skin tone +1F477 1F3FD 200D 2642 ; minimally-qualified # 👷🏽‍♂ man construction worker: medium skin tone +1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾‍♂️ man construction worker: medium-dark skin tone +1F477 1F3FE 200D 2642 ; minimally-qualified # 👷🏾‍♂ man construction worker: medium-dark skin tone +1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿‍♂️ man construction worker: dark skin tone +1F477 1F3FF 200D 2642 ; minimally-qualified # 👷🏿‍♂ man construction worker: dark skin tone +1F477 200D 2640 FE0F ; fully-qualified # 👷‍♀️ woman construction worker +1F477 200D 2640 ; minimally-qualified # 👷‍♀ woman construction worker +1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻‍♀️ woman construction worker: light skin tone +1F477 1F3FB 200D 2640 ; minimally-qualified # 👷🏻‍♀ woman construction worker: light skin tone +1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼‍♀️ woman construction worker: medium-light skin tone +1F477 1F3FC 200D 2640 ; minimally-qualified # 👷🏼‍♀ woman construction worker: medium-light skin tone +1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽‍♀️ woman construction worker: medium skin tone +1F477 1F3FD 200D 2640 ; minimally-qualified # 👷🏽‍♀ woman construction worker: medium skin tone +1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾‍♀️ woman construction worker: medium-dark skin tone +1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾‍♀ woman construction worker: medium-dark skin tone +1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ woman construction worker: dark skin tone +1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿‍♀ woman construction worker: dark skin tone +1F934 ; fully-qualified # 🤴 prince +1F934 1F3FB ; fully-qualified # 🤴🏻 prince: light skin tone +1F934 1F3FC ; fully-qualified # 🤴🏼 prince: medium-light skin tone +1F934 1F3FD ; fully-qualified # 🤴🏽 prince: medium skin tone +1F934 1F3FE ; fully-qualified # 🤴🏾 prince: medium-dark skin tone +1F934 1F3FF ; fully-qualified # 🤴🏿 prince: dark skin tone +1F478 ; fully-qualified # 👸 princess +1F478 1F3FB ; fully-qualified # 👸🏻 princess: light skin tone +1F478 1F3FC ; fully-qualified # 👸🏼 princess: medium-light skin tone +1F478 1F3FD ; fully-qualified # 👸🏽 princess: medium skin tone +1F478 1F3FE ; fully-qualified # 👸🏾 princess: medium-dark skin tone +1F478 1F3FF ; fully-qualified # 👸🏿 princess: dark skin tone +1F473 ; fully-qualified # 👳 person wearing turban +1F473 1F3FB ; fully-qualified # 👳🏻 person wearing turban: light skin tone +1F473 1F3FC ; fully-qualified # 👳🏼 person wearing turban: medium-light skin tone +1F473 1F3FD ; fully-qualified # 👳🏽 person wearing turban: medium skin tone +1F473 1F3FE ; fully-qualified # 👳🏾 person wearing turban: medium-dark skin tone +1F473 1F3FF ; fully-qualified # 👳🏿 person wearing turban: dark skin tone +1F473 200D 2642 FE0F ; fully-qualified # 👳‍♂️ man wearing turban +1F473 200D 2642 ; minimally-qualified # 👳‍♂ man wearing turban +1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻‍♂️ man wearing turban: light skin tone +1F473 1F3FB 200D 2642 ; minimally-qualified # 👳🏻‍♂ man wearing turban: light skin tone +1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼‍♂️ man wearing turban: medium-light skin tone +1F473 1F3FC 200D 2642 ; minimally-qualified # 👳🏼‍♂ man wearing turban: medium-light skin tone +1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽‍♂️ man wearing turban: medium skin tone +1F473 1F3FD 200D 2642 ; minimally-qualified # 👳🏽‍♂ man wearing turban: medium skin tone +1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾‍♂️ man wearing turban: medium-dark skin tone +1F473 1F3FE 200D 2642 ; minimally-qualified # 👳🏾‍♂ man wearing turban: medium-dark skin tone +1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿‍♂️ man wearing turban: dark skin tone +1F473 1F3FF 200D 2642 ; minimally-qualified # 👳🏿‍♂ man wearing turban: dark skin tone +1F473 200D 2640 FE0F ; fully-qualified # 👳‍♀️ woman wearing turban +1F473 200D 2640 ; minimally-qualified # 👳‍♀ woman wearing turban +1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻‍♀️ woman wearing turban: light skin tone +1F473 1F3FB 200D 2640 ; minimally-qualified # 👳🏻‍♀ woman wearing turban: light skin tone +1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼‍♀️ woman wearing turban: medium-light skin tone +1F473 1F3FC 200D 2640 ; minimally-qualified # 👳🏼‍♀ woman wearing turban: medium-light skin tone +1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽‍♀️ woman wearing turban: medium skin tone +1F473 1F3FD 200D 2640 ; minimally-qualified # 👳🏽‍♀ woman wearing turban: medium skin tone +1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾‍♀️ woman wearing turban: medium-dark skin tone +1F473 1F3FE 200D 2640 ; minimally-qualified # 👳🏾‍♀ woman wearing turban: medium-dark skin tone +1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿‍♀️ woman wearing turban: dark skin tone +1F473 1F3FF 200D 2640 ; minimally-qualified # 👳🏿‍♀ woman wearing turban: dark skin tone +1F472 ; fully-qualified # 👲 man with Chinese cap +1F472 1F3FB ; fully-qualified # 👲🏻 man with Chinese cap: light skin tone +1F472 1F3FC ; fully-qualified # 👲🏼 man with Chinese cap: medium-light skin tone +1F472 1F3FD ; fully-qualified # 👲🏽 man with Chinese cap: medium skin tone +1F472 1F3FE ; fully-qualified # 👲🏾 man with Chinese cap: medium-dark skin tone +1F472 1F3FF ; fully-qualified # 👲🏿 man with Chinese cap: dark skin tone +1F9D5 ; fully-qualified # 🧕 woman with headscarf +1F9D5 1F3FB ; fully-qualified # 🧕🏻 woman with headscarf: light skin tone +1F9D5 1F3FC ; fully-qualified # 🧕🏼 woman with headscarf: medium-light skin tone +1F9D5 1F3FD ; fully-qualified # 🧕🏽 woman with headscarf: medium skin tone +1F9D5 1F3FE ; fully-qualified # 🧕🏾 woman with headscarf: medium-dark skin tone +1F9D5 1F3FF ; fully-qualified # 🧕🏿 woman with headscarf: dark skin tone +1F935 ; fully-qualified # 🤵 man in tuxedo +1F935 1F3FB ; fully-qualified # 🤵🏻 man in tuxedo: light skin tone +1F935 1F3FC ; fully-qualified # 🤵🏼 man in tuxedo: medium-light skin tone +1F935 1F3FD ; fully-qualified # 🤵🏽 man in tuxedo: medium skin tone +1F935 1F3FE ; fully-qualified # 🤵🏾 man in tuxedo: medium-dark skin tone +1F935 1F3FF ; fully-qualified # 🤵🏿 man in tuxedo: dark skin tone +1F470 ; fully-qualified # 👰 bride with veil +1F470 1F3FB ; fully-qualified # 👰🏻 bride with veil: light skin tone +1F470 1F3FC ; fully-qualified # 👰🏼 bride with veil: medium-light skin tone +1F470 1F3FD ; fully-qualified # 👰🏽 bride with veil: medium skin tone +1F470 1F3FE ; fully-qualified # 👰🏾 bride with veil: medium-dark skin tone +1F470 1F3FF ; fully-qualified # 👰🏿 bride with veil: dark skin tone +1F930 ; fully-qualified # 🤰 pregnant woman +1F930 1F3FB ; fully-qualified # 🤰🏻 pregnant woman: light skin tone +1F930 1F3FC ; fully-qualified # 🤰🏼 pregnant woman: medium-light skin tone +1F930 1F3FD ; fully-qualified # 🤰🏽 pregnant woman: medium skin tone +1F930 1F3FE ; fully-qualified # 🤰🏾 pregnant woman: medium-dark skin tone +1F930 1F3FF ; fully-qualified # 🤰🏿 pregnant woman: dark skin tone +1F931 ; fully-qualified # 🤱 breast-feeding +1F931 1F3FB ; fully-qualified # 🤱🏻 breast-feeding: light skin tone +1F931 1F3FC ; fully-qualified # 🤱🏼 breast-feeding: medium-light skin tone +1F931 1F3FD ; fully-qualified # 🤱🏽 breast-feeding: medium skin tone +1F931 1F3FE ; fully-qualified # 🤱🏾 breast-feeding: medium-dark skin tone +1F931 1F3FF ; fully-qualified # 🤱🏿 breast-feeding: dark skin tone + +# subgroup: person-fantasy +1F47C ; fully-qualified # 👼 baby angel +1F47C 1F3FB ; fully-qualified # 👼🏻 baby angel: light skin tone +1F47C 1F3FC ; fully-qualified # 👼🏼 baby angel: medium-light skin tone +1F47C 1F3FD ; fully-qualified # 👼🏽 baby angel: medium skin tone +1F47C 1F3FE ; fully-qualified # 👼🏾 baby angel: medium-dark skin tone +1F47C 1F3FF ; fully-qualified # 👼🏿 baby angel: dark skin tone +1F385 ; fully-qualified # 🎅 Santa Claus +1F385 1F3FB ; fully-qualified # 🎅🏻 Santa Claus: light skin tone +1F385 1F3FC ; fully-qualified # 🎅🏼 Santa Claus: medium-light skin tone +1F385 1F3FD ; fully-qualified # 🎅🏽 Santa Claus: medium skin tone +1F385 1F3FE ; fully-qualified # 🎅🏾 Santa Claus: medium-dark skin tone +1F385 1F3FF ; fully-qualified # 🎅🏿 Santa Claus: dark skin tone +1F936 ; fully-qualified # 🤶 Mrs. Claus +1F936 1F3FB ; fully-qualified # 🤶🏻 Mrs. Claus: light skin tone +1F936 1F3FC ; fully-qualified # 🤶🏼 Mrs. Claus: medium-light skin tone +1F936 1F3FD ; fully-qualified # 🤶🏽 Mrs. Claus: medium skin tone +1F936 1F3FE ; fully-qualified # 🤶🏾 Mrs. Claus: medium-dark skin tone +1F936 1F3FF ; fully-qualified # 🤶🏿 Mrs. Claus: dark skin tone +1F9B8 ; fully-qualified # 🦸 superhero +1F9B8 1F3FB ; fully-qualified # 🦸🏻 superhero: light skin tone +1F9B8 1F3FC ; fully-qualified # 🦸🏼 superhero: medium-light skin tone +1F9B8 1F3FD ; fully-qualified # 🦸🏽 superhero: medium skin tone +1F9B8 1F3FE ; fully-qualified # 🦸🏾 superhero: medium-dark skin tone +1F9B8 1F3FF ; fully-qualified # 🦸🏿 superhero: dark skin tone +1F9B8 200D 2642 FE0F ; fully-qualified # 🦸‍♂️ man superhero +1F9B8 200D 2642 ; minimally-qualified # 🦸‍♂ man superhero +1F9B8 1F3FB 200D 2642 FE0F ; fully-qualified # 🦸🏻‍♂️ man superhero: light skin tone +1F9B8 1F3FB 200D 2642 ; minimally-qualified # 🦸🏻‍♂ man superhero: light skin tone +1F9B8 1F3FC 200D 2642 FE0F ; fully-qualified # 🦸🏼‍♂️ man superhero: medium-light skin tone +1F9B8 1F3FC 200D 2642 ; minimally-qualified # 🦸🏼‍♂ man superhero: medium-light skin tone +1F9B8 1F3FD 200D 2642 FE0F ; fully-qualified # 🦸🏽‍♂️ man superhero: medium skin tone +1F9B8 1F3FD 200D 2642 ; minimally-qualified # 🦸🏽‍♂ man superhero: medium skin tone +1F9B8 1F3FE 200D 2642 FE0F ; fully-qualified # 🦸🏾‍♂️ man superhero: medium-dark skin tone +1F9B8 1F3FE 200D 2642 ; minimally-qualified # 🦸🏾‍♂ man superhero: medium-dark skin tone +1F9B8 1F3FF 200D 2642 FE0F ; fully-qualified # 🦸🏿‍♂️ man superhero: dark skin tone +1F9B8 1F3FF 200D 2642 ; minimally-qualified # 🦸🏿‍♂ man superhero: dark skin tone +1F9B8 200D 2640 FE0F ; fully-qualified # 🦸‍♀️ woman superhero +1F9B8 200D 2640 ; minimally-qualified # 🦸‍♀ woman superhero +1F9B8 1F3FB 200D 2640 FE0F ; fully-qualified # 🦸🏻‍♀️ woman superhero: light skin tone +1F9B8 1F3FB 200D 2640 ; minimally-qualified # 🦸🏻‍♀ woman superhero: light skin tone +1F9B8 1F3FC 200D 2640 FE0F ; fully-qualified # 🦸🏼‍♀️ woman superhero: medium-light skin tone +1F9B8 1F3FC 200D 2640 ; minimally-qualified # 🦸🏼‍♀ woman superhero: medium-light skin tone +1F9B8 1F3FD 200D 2640 FE0F ; fully-qualified # 🦸🏽‍♀️ woman superhero: medium skin tone +1F9B8 1F3FD 200D 2640 ; minimally-qualified # 🦸🏽‍♀ woman superhero: medium skin tone +1F9B8 1F3FE 200D 2640 FE0F ; fully-qualified # 🦸🏾‍♀️ woman superhero: medium-dark skin tone +1F9B8 1F3FE 200D 2640 ; minimally-qualified # 🦸🏾‍♀ woman superhero: medium-dark skin tone +1F9B8 1F3FF 200D 2640 FE0F ; fully-qualified # 🦸🏿‍♀️ woman superhero: dark skin tone +1F9B8 1F3FF 200D 2640 ; minimally-qualified # 🦸🏿‍♀ woman superhero: dark skin tone +1F9B9 ; fully-qualified # 🦹 supervillain +1F9B9 1F3FB ; fully-qualified # 🦹🏻 supervillain: light skin tone +1F9B9 1F3FC ; fully-qualified # 🦹🏼 supervillain: medium-light skin tone +1F9B9 1F3FD ; fully-qualified # 🦹🏽 supervillain: medium skin tone +1F9B9 1F3FE ; fully-qualified # 🦹🏾 supervillain: medium-dark skin tone +1F9B9 1F3FF ; fully-qualified # 🦹🏿 supervillain: dark skin tone +1F9B9 200D 2642 FE0F ; fully-qualified # 🦹‍♂️ man supervillain +1F9B9 200D 2642 ; minimally-qualified # 🦹‍♂ man supervillain +1F9B9 1F3FB 200D 2642 FE0F ; fully-qualified # 🦹🏻‍♂️ man supervillain: light skin tone +1F9B9 1F3FB 200D 2642 ; minimally-qualified # 🦹🏻‍♂ man supervillain: light skin tone +1F9B9 1F3FC 200D 2642 FE0F ; fully-qualified # 🦹🏼‍♂️ man supervillain: medium-light skin tone +1F9B9 1F3FC 200D 2642 ; minimally-qualified # 🦹🏼‍♂ man supervillain: medium-light skin tone +1F9B9 1F3FD 200D 2642 FE0F ; fully-qualified # 🦹🏽‍♂️ man supervillain: medium skin tone +1F9B9 1F3FD 200D 2642 ; minimally-qualified # 🦹🏽‍♂ man supervillain: medium skin tone +1F9B9 1F3FE 200D 2642 FE0F ; fully-qualified # 🦹🏾‍♂️ man supervillain: medium-dark skin tone +1F9B9 1F3FE 200D 2642 ; minimally-qualified # 🦹🏾‍♂ man supervillain: medium-dark skin tone +1F9B9 1F3FF 200D 2642 FE0F ; fully-qualified # 🦹🏿‍♂️ man supervillain: dark skin tone +1F9B9 1F3FF 200D 2642 ; minimally-qualified # 🦹🏿‍♂ man supervillain: dark skin tone +1F9B9 200D 2640 FE0F ; fully-qualified # 🦹‍♀️ woman supervillain +1F9B9 200D 2640 ; minimally-qualified # 🦹‍♀ woman supervillain +1F9B9 1F3FB 200D 2640 FE0F ; fully-qualified # 🦹🏻‍♀️ woman supervillain: light skin tone +1F9B9 1F3FB 200D 2640 ; minimally-qualified # 🦹🏻‍♀ woman supervillain: light skin tone +1F9B9 1F3FC 200D 2640 FE0F ; fully-qualified # 🦹🏼‍♀️ woman supervillain: medium-light skin tone +1F9B9 1F3FC 200D 2640 ; minimally-qualified # 🦹🏼‍♀ woman supervillain: medium-light skin tone +1F9B9 1F3FD 200D 2640 FE0F ; fully-qualified # 🦹🏽‍♀️ woman supervillain: medium skin tone +1F9B9 1F3FD 200D 2640 ; minimally-qualified # 🦹🏽‍♀ woman supervillain: medium skin tone +1F9B9 1F3FE 200D 2640 FE0F ; fully-qualified # 🦹🏾‍♀️ woman supervillain: medium-dark skin tone +1F9B9 1F3FE 200D 2640 ; minimally-qualified # 🦹🏾‍♀ woman supervillain: medium-dark skin tone +1F9B9 1F3FF 200D 2640 FE0F ; fully-qualified # 🦹🏿‍♀️ woman supervillain: dark skin tone +1F9B9 1F3FF 200D 2640 ; minimally-qualified # 🦹🏿‍♀ woman supervillain: dark skin tone +1F9D9 ; fully-qualified # 🧙 mage +1F9D9 1F3FB ; fully-qualified # 🧙🏻 mage: light skin tone +1F9D9 1F3FC ; fully-qualified # 🧙🏼 mage: medium-light skin tone +1F9D9 1F3FD ; fully-qualified # 🧙🏽 mage: medium skin tone +1F9D9 1F3FE ; fully-qualified # 🧙🏾 mage: medium-dark skin tone +1F9D9 1F3FF ; fully-qualified # 🧙🏿 mage: dark skin tone +1F9D9 200D 2642 FE0F ; fully-qualified # 🧙‍♂️ man mage +1F9D9 200D 2642 ; minimally-qualified # 🧙‍♂ man mage +1F9D9 1F3FB 200D 2642 FE0F ; fully-qualified # 🧙🏻‍♂️ man mage: light skin tone +1F9D9 1F3FB 200D 2642 ; minimally-qualified # 🧙🏻‍♂ man mage: light skin tone +1F9D9 1F3FC 200D 2642 FE0F ; fully-qualified # 🧙🏼‍♂️ man mage: medium-light skin tone +1F9D9 1F3FC 200D 2642 ; minimally-qualified # 🧙🏼‍♂ man mage: medium-light skin tone +1F9D9 1F3FD 200D 2642 FE0F ; fully-qualified # 🧙🏽‍♂️ man mage: medium skin tone +1F9D9 1F3FD 200D 2642 ; minimally-qualified # 🧙🏽‍♂ man mage: medium skin tone +1F9D9 1F3FE 200D 2642 FE0F ; fully-qualified # 🧙🏾‍♂️ man mage: medium-dark skin tone +1F9D9 1F3FE 200D 2642 ; minimally-qualified # 🧙🏾‍♂ man mage: medium-dark skin tone +1F9D9 1F3FF 200D 2642 FE0F ; fully-qualified # 🧙🏿‍♂️ man mage: dark skin tone +1F9D9 1F3FF 200D 2642 ; minimally-qualified # 🧙🏿‍♂ man mage: dark skin tone +1F9D9 200D 2640 FE0F ; fully-qualified # 🧙‍♀️ woman mage +1F9D9 200D 2640 ; minimally-qualified # 🧙‍♀ woman mage +1F9D9 1F3FB 200D 2640 FE0F ; fully-qualified # 🧙🏻‍♀️ woman mage: light skin tone +1F9D9 1F3FB 200D 2640 ; minimally-qualified # 🧙🏻‍♀ woman mage: light skin tone +1F9D9 1F3FC 200D 2640 FE0F ; fully-qualified # 🧙🏼‍♀️ woman mage: medium-light skin tone +1F9D9 1F3FC 200D 2640 ; minimally-qualified # 🧙🏼‍♀ woman mage: medium-light skin tone +1F9D9 1F3FD 200D 2640 FE0F ; fully-qualified # 🧙🏽‍♀️ woman mage: medium skin tone +1F9D9 1F3FD 200D 2640 ; minimally-qualified # 🧙🏽‍♀ woman mage: medium skin tone +1F9D9 1F3FE 200D 2640 FE0F ; fully-qualified # 🧙🏾‍♀️ woman mage: medium-dark skin tone +1F9D9 1F3FE 200D 2640 ; minimally-qualified # 🧙🏾‍♀ woman mage: medium-dark skin tone +1F9D9 1F3FF 200D 2640 FE0F ; fully-qualified # 🧙🏿‍♀️ woman mage: dark skin tone +1F9D9 1F3FF 200D 2640 ; minimally-qualified # 🧙🏿‍♀ woman mage: dark skin tone +1F9DA ; fully-qualified # 🧚 fairy +1F9DA 1F3FB ; fully-qualified # 🧚🏻 fairy: light skin tone +1F9DA 1F3FC ; fully-qualified # 🧚🏼 fairy: medium-light skin tone +1F9DA 1F3FD ; fully-qualified # 🧚🏽 fairy: medium skin tone +1F9DA 1F3FE ; fully-qualified # 🧚🏾 fairy: medium-dark skin tone +1F9DA 1F3FF ; fully-qualified # 🧚🏿 fairy: dark skin tone +1F9DA 200D 2642 FE0F ; fully-qualified # 🧚‍♂️ man fairy +1F9DA 200D 2642 ; minimally-qualified # 🧚‍♂ man fairy +1F9DA 1F3FB 200D 2642 FE0F ; fully-qualified # 🧚🏻‍♂️ man fairy: light skin tone +1F9DA 1F3FB 200D 2642 ; minimally-qualified # 🧚🏻‍♂ man fairy: light skin tone +1F9DA 1F3FC 200D 2642 FE0F ; fully-qualified # 🧚🏼‍♂️ man fairy: medium-light skin tone +1F9DA 1F3FC 200D 2642 ; minimally-qualified # 🧚🏼‍♂ man fairy: medium-light skin tone +1F9DA 1F3FD 200D 2642 FE0F ; fully-qualified # 🧚🏽‍♂️ man fairy: medium skin tone +1F9DA 1F3FD 200D 2642 ; minimally-qualified # 🧚🏽‍♂ man fairy: medium skin tone +1F9DA 1F3FE 200D 2642 FE0F ; fully-qualified # 🧚🏾‍♂️ man fairy: medium-dark skin tone +1F9DA 1F3FE 200D 2642 ; minimally-qualified # 🧚🏾‍♂ man fairy: medium-dark skin tone +1F9DA 1F3FF 200D 2642 FE0F ; fully-qualified # 🧚🏿‍♂️ man fairy: dark skin tone +1F9DA 1F3FF 200D 2642 ; minimally-qualified # 🧚🏿‍♂ man fairy: dark skin tone +1F9DA 200D 2640 FE0F ; fully-qualified # 🧚‍♀️ woman fairy +1F9DA 200D 2640 ; minimally-qualified # 🧚‍♀ woman fairy +1F9DA 1F3FB 200D 2640 FE0F ; fully-qualified # 🧚🏻‍♀️ woman fairy: light skin tone +1F9DA 1F3FB 200D 2640 ; minimally-qualified # 🧚🏻‍♀ woman fairy: light skin tone +1F9DA 1F3FC 200D 2640 FE0F ; fully-qualified # 🧚🏼‍♀️ woman fairy: medium-light skin tone +1F9DA 1F3FC 200D 2640 ; minimally-qualified # 🧚🏼‍♀ woman fairy: medium-light skin tone +1F9DA 1F3FD 200D 2640 FE0F ; fully-qualified # 🧚🏽‍♀️ woman fairy: medium skin tone +1F9DA 1F3FD 200D 2640 ; minimally-qualified # 🧚🏽‍♀ woman fairy: medium skin tone +1F9DA 1F3FE 200D 2640 FE0F ; fully-qualified # 🧚🏾‍♀️ woman fairy: medium-dark skin tone +1F9DA 1F3FE 200D 2640 ; minimally-qualified # 🧚🏾‍♀ woman fairy: medium-dark skin tone +1F9DA 1F3FF 200D 2640 FE0F ; fully-qualified # 🧚🏿‍♀️ woman fairy: dark skin tone +1F9DA 1F3FF 200D 2640 ; minimally-qualified # 🧚🏿‍♀ woman fairy: dark skin tone +1F9DB ; fully-qualified # 🧛 vampire +1F9DB 1F3FB ; fully-qualified # 🧛🏻 vampire: light skin tone +1F9DB 1F3FC ; fully-qualified # 🧛🏼 vampire: medium-light skin tone +1F9DB 1F3FD ; fully-qualified # 🧛🏽 vampire: medium skin tone +1F9DB 1F3FE ; fully-qualified # 🧛🏾 vampire: medium-dark skin tone +1F9DB 1F3FF ; fully-qualified # 🧛🏿 vampire: dark skin tone +1F9DB 200D 2642 FE0F ; fully-qualified # 🧛‍♂️ man vampire +1F9DB 200D 2642 ; minimally-qualified # 🧛‍♂ man vampire +1F9DB 1F3FB 200D 2642 FE0F ; fully-qualified # 🧛🏻‍♂️ man vampire: light skin tone +1F9DB 1F3FB 200D 2642 ; minimally-qualified # 🧛🏻‍♂ man vampire: light skin tone +1F9DB 1F3FC 200D 2642 FE0F ; fully-qualified # 🧛🏼‍♂️ man vampire: medium-light skin tone +1F9DB 1F3FC 200D 2642 ; minimally-qualified # 🧛🏼‍♂ man vampire: medium-light skin tone +1F9DB 1F3FD 200D 2642 FE0F ; fully-qualified # 🧛🏽‍♂️ man vampire: medium skin tone +1F9DB 1F3FD 200D 2642 ; minimally-qualified # 🧛🏽‍♂ man vampire: medium skin tone +1F9DB 1F3FE 200D 2642 FE0F ; fully-qualified # 🧛🏾‍♂️ man vampire: medium-dark skin tone +1F9DB 1F3FE 200D 2642 ; minimally-qualified # 🧛🏾‍♂ man vampire: medium-dark skin tone +1F9DB 1F3FF 200D 2642 FE0F ; fully-qualified # 🧛🏿‍♂️ man vampire: dark skin tone +1F9DB 1F3FF 200D 2642 ; minimally-qualified # 🧛🏿‍♂ man vampire: dark skin tone +1F9DB 200D 2640 FE0F ; fully-qualified # 🧛‍♀️ woman vampire +1F9DB 200D 2640 ; minimally-qualified # 🧛‍♀ woman vampire +1F9DB 1F3FB 200D 2640 FE0F ; fully-qualified # 🧛🏻‍♀️ woman vampire: light skin tone +1F9DB 1F3FB 200D 2640 ; minimally-qualified # 🧛🏻‍♀ woman vampire: light skin tone +1F9DB 1F3FC 200D 2640 FE0F ; fully-qualified # 🧛🏼‍♀️ woman vampire: medium-light skin tone +1F9DB 1F3FC 200D 2640 ; minimally-qualified # 🧛🏼‍♀ woman vampire: medium-light skin tone +1F9DB 1F3FD 200D 2640 FE0F ; fully-qualified # 🧛🏽‍♀️ woman vampire: medium skin tone +1F9DB 1F3FD 200D 2640 ; minimally-qualified # 🧛🏽‍♀ woman vampire: medium skin tone +1F9DB 1F3FE 200D 2640 FE0F ; fully-qualified # 🧛🏾‍♀️ woman vampire: medium-dark skin tone +1F9DB 1F3FE 200D 2640 ; minimally-qualified # 🧛🏾‍♀ woman vampire: medium-dark skin tone +1F9DB 1F3FF 200D 2640 FE0F ; fully-qualified # 🧛🏿‍♀️ woman vampire: dark skin tone +1F9DB 1F3FF 200D 2640 ; minimally-qualified # 🧛🏿‍♀ woman vampire: dark skin tone +1F9DC ; fully-qualified # 🧜 merperson +1F9DC 1F3FB ; fully-qualified # 🧜🏻 merperson: light skin tone +1F9DC 1F3FC ; fully-qualified # 🧜🏼 merperson: medium-light skin tone +1F9DC 1F3FD ; fully-qualified # 🧜🏽 merperson: medium skin tone +1F9DC 1F3FE ; fully-qualified # 🧜🏾 merperson: medium-dark skin tone +1F9DC 1F3FF ; fully-qualified # 🧜🏿 merperson: dark skin tone +1F9DC 200D 2642 FE0F ; fully-qualified # 🧜‍♂️ merman +1F9DC 200D 2642 ; minimally-qualified # 🧜‍♂ merman +1F9DC 1F3FB 200D 2642 FE0F ; fully-qualified # 🧜🏻‍♂️ merman: light skin tone +1F9DC 1F3FB 200D 2642 ; minimally-qualified # 🧜🏻‍♂ merman: light skin tone +1F9DC 1F3FC 200D 2642 FE0F ; fully-qualified # 🧜🏼‍♂️ merman: medium-light skin tone +1F9DC 1F3FC 200D 2642 ; minimally-qualified # 🧜🏼‍♂ merman: medium-light skin tone +1F9DC 1F3FD 200D 2642 FE0F ; fully-qualified # 🧜🏽‍♂️ merman: medium skin tone +1F9DC 1F3FD 200D 2642 ; minimally-qualified # 🧜🏽‍♂ merman: medium skin tone +1F9DC 1F3FE 200D 2642 FE0F ; fully-qualified # 🧜🏾‍♂️ merman: medium-dark skin tone +1F9DC 1F3FE 200D 2642 ; minimally-qualified # 🧜🏾‍♂ merman: medium-dark skin tone +1F9DC 1F3FF 200D 2642 FE0F ; fully-qualified # 🧜🏿‍♂️ merman: dark skin tone +1F9DC 1F3FF 200D 2642 ; minimally-qualified # 🧜🏿‍♂ merman: dark skin tone +1F9DC 200D 2640 FE0F ; fully-qualified # 🧜‍♀️ mermaid +1F9DC 200D 2640 ; minimally-qualified # 🧜‍♀ mermaid +1F9DC 1F3FB 200D 2640 FE0F ; fully-qualified # 🧜🏻‍♀️ mermaid: light skin tone +1F9DC 1F3FB 200D 2640 ; minimally-qualified # 🧜🏻‍♀ mermaid: light skin tone +1F9DC 1F3FC 200D 2640 FE0F ; fully-qualified # 🧜🏼‍♀️ mermaid: medium-light skin tone +1F9DC 1F3FC 200D 2640 ; minimally-qualified # 🧜🏼‍♀ mermaid: medium-light skin tone +1F9DC 1F3FD 200D 2640 FE0F ; fully-qualified # 🧜🏽‍♀️ mermaid: medium skin tone +1F9DC 1F3FD 200D 2640 ; minimally-qualified # 🧜🏽‍♀ mermaid: medium skin tone +1F9DC 1F3FE 200D 2640 FE0F ; fully-qualified # 🧜🏾‍♀️ mermaid: medium-dark skin tone +1F9DC 1F3FE 200D 2640 ; minimally-qualified # 🧜🏾‍♀ mermaid: medium-dark skin tone +1F9DC 1F3FF 200D 2640 FE0F ; fully-qualified # 🧜🏿‍♀️ mermaid: dark skin tone +1F9DC 1F3FF 200D 2640 ; minimally-qualified # 🧜🏿‍♀ mermaid: dark skin tone +1F9DD ; fully-qualified # 🧝 elf +1F9DD 1F3FB ; fully-qualified # 🧝🏻 elf: light skin tone +1F9DD 1F3FC ; fully-qualified # 🧝🏼 elf: medium-light skin tone +1F9DD 1F3FD ; fully-qualified # 🧝🏽 elf: medium skin tone +1F9DD 1F3FE ; fully-qualified # 🧝🏾 elf: medium-dark skin tone +1F9DD 1F3FF ; fully-qualified # 🧝🏿 elf: dark skin tone +1F9DD 200D 2642 FE0F ; fully-qualified # 🧝‍♂️ man elf +1F9DD 200D 2642 ; minimally-qualified # 🧝‍♂ man elf +1F9DD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧝🏻‍♂️ man elf: light skin tone +1F9DD 1F3FB 200D 2642 ; minimally-qualified # 🧝🏻‍♂ man elf: light skin tone +1F9DD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧝🏼‍♂️ man elf: medium-light skin tone +1F9DD 1F3FC 200D 2642 ; minimally-qualified # 🧝🏼‍♂ man elf: medium-light skin tone +1F9DD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧝🏽‍♂️ man elf: medium skin tone +1F9DD 1F3FD 200D 2642 ; minimally-qualified # 🧝🏽‍♂ man elf: medium skin tone +1F9DD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧝🏾‍♂️ man elf: medium-dark skin tone +1F9DD 1F3FE 200D 2642 ; minimally-qualified # 🧝🏾‍♂ man elf: medium-dark skin tone +1F9DD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧝🏿‍♂️ man elf: dark skin tone +1F9DD 1F3FF 200D 2642 ; minimally-qualified # 🧝🏿‍♂ man elf: dark skin tone +1F9DD 200D 2640 FE0F ; fully-qualified # 🧝‍♀️ woman elf +1F9DD 200D 2640 ; minimally-qualified # 🧝‍♀ woman elf +1F9DD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧝🏻‍♀️ woman elf: light skin tone +1F9DD 1F3FB 200D 2640 ; minimally-qualified # 🧝🏻‍♀ woman elf: light skin tone +1F9DD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧝🏼‍♀️ woman elf: medium-light skin tone +1F9DD 1F3FC 200D 2640 ; minimally-qualified # 🧝🏼‍♀ woman elf: medium-light skin tone +1F9DD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧝🏽‍♀️ woman elf: medium skin tone +1F9DD 1F3FD 200D 2640 ; minimally-qualified # 🧝🏽‍♀ woman elf: medium skin tone +1F9DD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧝🏾‍♀️ woman elf: medium-dark skin tone +1F9DD 1F3FE 200D 2640 ; minimally-qualified # 🧝🏾‍♀ woman elf: medium-dark skin tone +1F9DD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧝🏿‍♀️ woman elf: dark skin tone +1F9DD 1F3FF 200D 2640 ; minimally-qualified # 🧝🏿‍♀ woman elf: dark skin tone +1F9DE ; fully-qualified # 🧞 genie +1F9DE 200D 2642 FE0F ; fully-qualified # 🧞‍♂️ man genie +1F9DE 200D 2642 ; minimally-qualified # 🧞‍♂ man genie +1F9DE 200D 2640 FE0F ; fully-qualified # 🧞‍♀️ woman genie +1F9DE 200D 2640 ; minimally-qualified # 🧞‍♀ woman genie +1F9DF ; fully-qualified # 🧟 zombie +1F9DF 200D 2642 FE0F ; fully-qualified # 🧟‍♂️ man zombie +1F9DF 200D 2642 ; minimally-qualified # 🧟‍♂ man zombie +1F9DF 200D 2640 FE0F ; fully-qualified # 🧟‍♀️ woman zombie +1F9DF 200D 2640 ; minimally-qualified # 🧟‍♀ woman zombie + +# subgroup: person-activity +1F486 ; fully-qualified # 💆 person getting massage +1F486 1F3FB ; fully-qualified # 💆🏻 person getting massage: light skin tone +1F486 1F3FC ; fully-qualified # 💆🏼 person getting massage: medium-light skin tone +1F486 1F3FD ; fully-qualified # 💆🏽 person getting massage: medium skin tone +1F486 1F3FE ; fully-qualified # 💆🏾 person getting massage: medium-dark skin tone +1F486 1F3FF ; fully-qualified # 💆🏿 person getting massage: dark skin tone +1F486 200D 2642 FE0F ; fully-qualified # 💆‍♂️ man getting massage +1F486 200D 2642 ; minimally-qualified # 💆‍♂ man getting massage +1F486 1F3FB 200D 2642 FE0F ; fully-qualified # 💆🏻‍♂️ man getting massage: light skin tone +1F486 1F3FB 200D 2642 ; minimally-qualified # 💆🏻‍♂ man getting massage: light skin tone +1F486 1F3FC 200D 2642 FE0F ; fully-qualified # 💆🏼‍♂️ man getting massage: medium-light skin tone +1F486 1F3FC 200D 2642 ; minimally-qualified # 💆🏼‍♂ man getting massage: medium-light skin tone +1F486 1F3FD 200D 2642 FE0F ; fully-qualified # 💆🏽‍♂️ man getting massage: medium skin tone +1F486 1F3FD 200D 2642 ; minimally-qualified # 💆🏽‍♂ man getting massage: medium skin tone +1F486 1F3FE 200D 2642 FE0F ; fully-qualified # 💆🏾‍♂️ man getting massage: medium-dark skin tone +1F486 1F3FE 200D 2642 ; minimally-qualified # 💆🏾‍♂ man getting massage: medium-dark skin tone +1F486 1F3FF 200D 2642 FE0F ; fully-qualified # 💆🏿‍♂️ man getting massage: dark skin tone +1F486 1F3FF 200D 2642 ; minimally-qualified # 💆🏿‍♂ man getting massage: dark skin tone +1F486 200D 2640 FE0F ; fully-qualified # 💆‍♀️ woman getting massage +1F486 200D 2640 ; minimally-qualified # 💆‍♀ woman getting massage +1F486 1F3FB 200D 2640 FE0F ; fully-qualified # 💆🏻‍♀️ woman getting massage: light skin tone +1F486 1F3FB 200D 2640 ; minimally-qualified # 💆🏻‍♀ woman getting massage: light skin tone +1F486 1F3FC 200D 2640 FE0F ; fully-qualified # 💆🏼‍♀️ woman getting massage: medium-light skin tone +1F486 1F3FC 200D 2640 ; minimally-qualified # 💆🏼‍♀ woman getting massage: medium-light skin tone +1F486 1F3FD 200D 2640 FE0F ; fully-qualified # 💆🏽‍♀️ woman getting massage: medium skin tone +1F486 1F3FD 200D 2640 ; minimally-qualified # 💆🏽‍♀ woman getting massage: medium skin tone +1F486 1F3FE 200D 2640 FE0F ; fully-qualified # 💆🏾‍♀️ woman getting massage: medium-dark skin tone +1F486 1F3FE 200D 2640 ; minimally-qualified # 💆🏾‍♀ woman getting massage: medium-dark skin tone +1F486 1F3FF 200D 2640 FE0F ; fully-qualified # 💆🏿‍♀️ woman getting massage: dark skin tone +1F486 1F3FF 200D 2640 ; minimally-qualified # 💆🏿‍♀ woman getting massage: dark skin tone +1F487 ; fully-qualified # 💇 person getting haircut +1F487 1F3FB ; fully-qualified # 💇🏻 person getting haircut: light skin tone +1F487 1F3FC ; fully-qualified # 💇🏼 person getting haircut: medium-light skin tone +1F487 1F3FD ; fully-qualified # 💇🏽 person getting haircut: medium skin tone +1F487 1F3FE ; fully-qualified # 💇🏾 person getting haircut: medium-dark skin tone +1F487 1F3FF ; fully-qualified # 💇🏿 person getting haircut: dark skin tone +1F487 200D 2642 FE0F ; fully-qualified # 💇‍♂️ man getting haircut +1F487 200D 2642 ; minimally-qualified # 💇‍♂ man getting haircut +1F487 1F3FB 200D 2642 FE0F ; fully-qualified # 💇🏻‍♂️ man getting haircut: light skin tone +1F487 1F3FB 200D 2642 ; minimally-qualified # 💇🏻‍♂ man getting haircut: light skin tone +1F487 1F3FC 200D 2642 FE0F ; fully-qualified # 💇🏼‍♂️ man getting haircut: medium-light skin tone +1F487 1F3FC 200D 2642 ; minimally-qualified # 💇🏼‍♂ man getting haircut: medium-light skin tone +1F487 1F3FD 200D 2642 FE0F ; fully-qualified # 💇🏽‍♂️ man getting haircut: medium skin tone +1F487 1F3FD 200D 2642 ; minimally-qualified # 💇🏽‍♂ man getting haircut: medium skin tone +1F487 1F3FE 200D 2642 FE0F ; fully-qualified # 💇🏾‍♂️ man getting haircut: medium-dark skin tone +1F487 1F3FE 200D 2642 ; minimally-qualified # 💇🏾‍♂ man getting haircut: medium-dark skin tone +1F487 1F3FF 200D 2642 FE0F ; fully-qualified # 💇🏿‍♂️ man getting haircut: dark skin tone +1F487 1F3FF 200D 2642 ; minimally-qualified # 💇🏿‍♂ man getting haircut: dark skin tone +1F487 200D 2640 FE0F ; fully-qualified # 💇‍♀️ woman getting haircut +1F487 200D 2640 ; minimally-qualified # 💇‍♀ woman getting haircut +1F487 1F3FB 200D 2640 FE0F ; fully-qualified # 💇🏻‍♀️ woman getting haircut: light skin tone +1F487 1F3FB 200D 2640 ; minimally-qualified # 💇🏻‍♀ woman getting haircut: light skin tone +1F487 1F3FC 200D 2640 FE0F ; fully-qualified # 💇🏼‍♀️ woman getting haircut: medium-light skin tone +1F487 1F3FC 200D 2640 ; minimally-qualified # 💇🏼‍♀ woman getting haircut: medium-light skin tone +1F487 1F3FD 200D 2640 FE0F ; fully-qualified # 💇🏽‍♀️ woman getting haircut: medium skin tone +1F487 1F3FD 200D 2640 ; minimally-qualified # 💇🏽‍♀ woman getting haircut: medium skin tone +1F487 1F3FE 200D 2640 FE0F ; fully-qualified # 💇🏾‍♀️ woman getting haircut: medium-dark skin tone +1F487 1F3FE 200D 2640 ; minimally-qualified # 💇🏾‍♀ woman getting haircut: medium-dark skin tone +1F487 1F3FF 200D 2640 FE0F ; fully-qualified # 💇🏿‍♀️ woman getting haircut: dark skin tone +1F487 1F3FF 200D 2640 ; minimally-qualified # 💇🏿‍♀ woman getting haircut: dark skin tone +1F6B6 ; fully-qualified # 🚶 person walking +1F6B6 1F3FB ; fully-qualified # 🚶🏻 person walking: light skin tone +1F6B6 1F3FC ; fully-qualified # 🚶🏼 person walking: medium-light skin tone +1F6B6 1F3FD ; fully-qualified # 🚶🏽 person walking: medium skin tone +1F6B6 1F3FE ; fully-qualified # 🚶🏾 person walking: medium-dark skin tone +1F6B6 1F3FF ; fully-qualified # 🚶🏿 person walking: dark skin tone +1F6B6 200D 2642 FE0F ; fully-qualified # 🚶‍♂️ man walking +1F6B6 200D 2642 ; minimally-qualified # 🚶‍♂ man walking +1F6B6 1F3FB 200D 2642 FE0F ; fully-qualified # 🚶🏻‍♂️ man walking: light skin tone +1F6B6 1F3FB 200D 2642 ; minimally-qualified # 🚶🏻‍♂ man walking: light skin tone +1F6B6 1F3FC 200D 2642 FE0F ; fully-qualified # 🚶🏼‍♂️ man walking: medium-light skin tone +1F6B6 1F3FC 200D 2642 ; minimally-qualified # 🚶🏼‍♂ man walking: medium-light skin tone +1F6B6 1F3FD 200D 2642 FE0F ; fully-qualified # 🚶🏽‍♂️ man walking: medium skin tone +1F6B6 1F3FD 200D 2642 ; minimally-qualified # 🚶🏽‍♂ man walking: medium skin tone +1F6B6 1F3FE 200D 2642 FE0F ; fully-qualified # 🚶🏾‍♂️ man walking: medium-dark skin tone +1F6B6 1F3FE 200D 2642 ; minimally-qualified # 🚶🏾‍♂ man walking: medium-dark skin tone +1F6B6 1F3FF 200D 2642 FE0F ; fully-qualified # 🚶🏿‍♂️ man walking: dark skin tone +1F6B6 1F3FF 200D 2642 ; minimally-qualified # 🚶🏿‍♂ man walking: dark skin tone +1F6B6 200D 2640 FE0F ; fully-qualified # 🚶‍♀️ woman walking +1F6B6 200D 2640 ; minimally-qualified # 🚶‍♀ woman walking +1F6B6 1F3FB 200D 2640 FE0F ; fully-qualified # 🚶🏻‍♀️ woman walking: light skin tone +1F6B6 1F3FB 200D 2640 ; minimally-qualified # 🚶🏻‍♀ woman walking: light skin tone +1F6B6 1F3FC 200D 2640 FE0F ; fully-qualified # 🚶🏼‍♀️ woman walking: medium-light skin tone +1F6B6 1F3FC 200D 2640 ; minimally-qualified # 🚶🏼‍♀ woman walking: medium-light skin tone +1F6B6 1F3FD 200D 2640 FE0F ; fully-qualified # 🚶🏽‍♀️ woman walking: medium skin tone +1F6B6 1F3FD 200D 2640 ; minimally-qualified # 🚶🏽‍♀ woman walking: medium skin tone +1F6B6 1F3FE 200D 2640 FE0F ; fully-qualified # 🚶🏾‍♀️ woman walking: medium-dark skin tone +1F6B6 1F3FE 200D 2640 ; minimally-qualified # 🚶🏾‍♀ woman walking: medium-dark skin tone +1F6B6 1F3FF 200D 2640 FE0F ; fully-qualified # 🚶🏿‍♀️ woman walking: dark skin tone +1F6B6 1F3FF 200D 2640 ; minimally-qualified # 🚶🏿‍♀ woman walking: dark skin tone +1F9CD ; fully-qualified # 🧍 person standing +1F9CD 1F3FB ; fully-qualified # 🧍🏻 person standing: light skin tone +1F9CD 1F3FC ; fully-qualified # 🧍🏼 person standing: medium-light skin tone +1F9CD 1F3FD ; fully-qualified # 🧍🏽 person standing: medium skin tone +1F9CD 1F3FE ; fully-qualified # 🧍🏾 person standing: medium-dark skin tone +1F9CD 1F3FF ; fully-qualified # 🧍🏿 person standing: dark skin tone +1F9CD 200D 2642 FE0F ; fully-qualified # 🧍‍♂️ man standing +1F9CD 200D 2642 ; minimally-qualified # 🧍‍♂ man standing +1F9CD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧍🏻‍♂️ man standing: light skin tone +1F9CD 1F3FB 200D 2642 ; minimally-qualified # 🧍🏻‍♂ man standing: light skin tone +1F9CD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧍🏼‍♂️ man standing: medium-light skin tone +1F9CD 1F3FC 200D 2642 ; minimally-qualified # 🧍🏼‍♂ man standing: medium-light skin tone +1F9CD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧍🏽‍♂️ man standing: medium skin tone +1F9CD 1F3FD 200D 2642 ; minimally-qualified # 🧍🏽‍♂ man standing: medium skin tone +1F9CD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧍🏾‍♂️ man standing: medium-dark skin tone +1F9CD 1F3FE 200D 2642 ; minimally-qualified # 🧍🏾‍♂ man standing: medium-dark skin tone +1F9CD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧍🏿‍♂️ man standing: dark skin tone +1F9CD 1F3FF 200D 2642 ; minimally-qualified # 🧍🏿‍♂ man standing: dark skin tone +1F9CD 200D 2640 FE0F ; fully-qualified # 🧍‍♀️ woman standing +1F9CD 200D 2640 ; minimally-qualified # 🧍‍♀ woman standing +1F9CD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧍🏻‍♀️ woman standing: light skin tone +1F9CD 1F3FB 200D 2640 ; minimally-qualified # 🧍🏻‍♀ woman standing: light skin tone +1F9CD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧍🏼‍♀️ woman standing: medium-light skin tone +1F9CD 1F3FC 200D 2640 ; minimally-qualified # 🧍🏼‍♀ woman standing: medium-light skin tone +1F9CD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧍🏽‍♀️ woman standing: medium skin tone +1F9CD 1F3FD 200D 2640 ; minimally-qualified # 🧍🏽‍♀ woman standing: medium skin tone +1F9CD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧍🏾‍♀️ woman standing: medium-dark skin tone +1F9CD 1F3FE 200D 2640 ; minimally-qualified # 🧍🏾‍♀ woman standing: medium-dark skin tone +1F9CD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧍🏿‍♀️ woman standing: dark skin tone +1F9CD 1F3FF 200D 2640 ; minimally-qualified # 🧍🏿‍♀ woman standing: dark skin tone +1F9CE ; fully-qualified # 🧎 person kneeling +1F9CE 1F3FB ; fully-qualified # 🧎🏻 person kneeling: light skin tone +1F9CE 1F3FC ; fully-qualified # 🧎🏼 person kneeling: medium-light skin tone +1F9CE 1F3FD ; fully-qualified # 🧎🏽 person kneeling: medium skin tone +1F9CE 1F3FE ; fully-qualified # 🧎🏾 person kneeling: medium-dark skin tone +1F9CE 1F3FF ; fully-qualified # 🧎🏿 person kneeling: dark skin tone +1F9CE 200D 2642 FE0F ; fully-qualified # 🧎‍♂️ man kneeling +1F9CE 200D 2642 ; minimally-qualified # 🧎‍♂ man kneeling +1F9CE 1F3FB 200D 2642 FE0F ; fully-qualified # 🧎🏻‍♂️ man kneeling: light skin tone +1F9CE 1F3FB 200D 2642 ; minimally-qualified # 🧎🏻‍♂ man kneeling: light skin tone +1F9CE 1F3FC 200D 2642 FE0F ; fully-qualified # 🧎🏼‍♂️ man kneeling: medium-light skin tone +1F9CE 1F3FC 200D 2642 ; minimally-qualified # 🧎🏼‍♂ man kneeling: medium-light skin tone +1F9CE 1F3FD 200D 2642 FE0F ; fully-qualified # 🧎🏽‍♂️ man kneeling: medium skin tone +1F9CE 1F3FD 200D 2642 ; minimally-qualified # 🧎🏽‍♂ man kneeling: medium skin tone +1F9CE 1F3FE 200D 2642 FE0F ; fully-qualified # 🧎🏾‍♂️ man kneeling: medium-dark skin tone +1F9CE 1F3FE 200D 2642 ; minimally-qualified # 🧎🏾‍♂ man kneeling: medium-dark skin tone +1F9CE 1F3FF 200D 2642 FE0F ; fully-qualified # 🧎🏿‍♂️ man kneeling: dark skin tone +1F9CE 1F3FF 200D 2642 ; minimally-qualified # 🧎🏿‍♂ man kneeling: dark skin tone +1F9CE 200D 2640 FE0F ; fully-qualified # 🧎‍♀️ woman kneeling +1F9CE 200D 2640 ; minimally-qualified # 🧎‍♀ woman kneeling +1F9CE 1F3FB 200D 2640 FE0F ; fully-qualified # 🧎🏻‍♀️ woman kneeling: light skin tone +1F9CE 1F3FB 200D 2640 ; minimally-qualified # 🧎🏻‍♀ woman kneeling: light skin tone +1F9CE 1F3FC 200D 2640 FE0F ; fully-qualified # 🧎🏼‍♀️ woman kneeling: medium-light skin tone +1F9CE 1F3FC 200D 2640 ; minimally-qualified # 🧎🏼‍♀ woman kneeling: medium-light skin tone +1F9CE 1F3FD 200D 2640 FE0F ; fully-qualified # 🧎🏽‍♀️ woman kneeling: medium skin tone +1F9CE 1F3FD 200D 2640 ; minimally-qualified # 🧎🏽‍♀ woman kneeling: medium skin tone +1F9CE 1F3FE 200D 2640 FE0F ; fully-qualified # 🧎🏾‍♀️ woman kneeling: medium-dark skin tone +1F9CE 1F3FE 200D 2640 ; minimally-qualified # 🧎🏾‍♀ woman kneeling: medium-dark skin tone +1F9CE 1F3FF 200D 2640 FE0F ; fully-qualified # 🧎🏿‍♀️ woman kneeling: dark skin tone +1F9CE 1F3FF 200D 2640 ; minimally-qualified # 🧎🏿‍♀ woman kneeling: dark skin tone +1F468 200D 1F9AF ; fully-qualified # 👨‍🦯 man with probing cane +1F468 1F3FB 200D 1F9AF ; fully-qualified # 👨🏻‍🦯 man with probing cane: light skin tone +1F468 1F3FC 200D 1F9AF ; fully-qualified # 👨🏼‍🦯 man with probing cane: medium-light skin tone +1F468 1F3FD 200D 1F9AF ; fully-qualified # 👨🏽‍🦯 man with probing cane: medium skin tone +1F468 1F3FE 200D 1F9AF ; fully-qualified # 👨🏾‍🦯 man with probing cane: medium-dark skin tone +1F468 1F3FF 200D 1F9AF ; fully-qualified # 👨🏿‍🦯 man with probing cane: dark skin tone +1F469 200D 1F9AF ; fully-qualified # 👩‍🦯 woman with probing cane +1F469 1F3FB 200D 1F9AF ; fully-qualified # 👩🏻‍🦯 woman with probing cane: light skin tone +1F469 1F3FC 200D 1F9AF ; fully-qualified # 👩🏼‍🦯 woman with probing cane: medium-light skin tone +1F469 1F3FD 200D 1F9AF ; fully-qualified # 👩🏽‍🦯 woman with probing cane: medium skin tone +1F469 1F3FE 200D 1F9AF ; fully-qualified # 👩🏾‍🦯 woman with probing cane: medium-dark skin tone +1F469 1F3FF 200D 1F9AF ; fully-qualified # 👩🏿‍🦯 woman with probing cane: dark skin tone +1F468 200D 1F9BC ; fully-qualified # 👨‍🦼 man in motorized wheelchair +1F468 1F3FB 200D 1F9BC ; fully-qualified # 👨🏻‍🦼 man in motorized wheelchair: light skin tone +1F468 1F3FC 200D 1F9BC ; fully-qualified # 👨🏼‍🦼 man in motorized wheelchair: medium-light skin tone +1F468 1F3FD 200D 1F9BC ; fully-qualified # 👨🏽‍🦼 man in motorized wheelchair: medium skin tone +1F468 1F3FE 200D 1F9BC ; fully-qualified # 👨🏾‍🦼 man in motorized wheelchair: medium-dark skin tone +1F468 1F3FF 200D 1F9BC ; fully-qualified # 👨🏿‍🦼 man in motorized wheelchair: dark skin tone +1F469 200D 1F9BC ; fully-qualified # 👩‍🦼 woman in motorized wheelchair +1F469 1F3FB 200D 1F9BC ; fully-qualified # 👩🏻‍🦼 woman in motorized wheelchair: light skin tone +1F469 1F3FC 200D 1F9BC ; fully-qualified # 👩🏼‍🦼 woman in motorized wheelchair: medium-light skin tone +1F469 1F3FD 200D 1F9BC ; fully-qualified # 👩🏽‍🦼 woman in motorized wheelchair: medium skin tone +1F469 1F3FE 200D 1F9BC ; fully-qualified # 👩🏾‍🦼 woman in motorized wheelchair: medium-dark skin tone +1F469 1F3FF 200D 1F9BC ; fully-qualified # 👩🏿‍🦼 woman in motorized wheelchair: dark skin tone +1F468 200D 1F9BD ; fully-qualified # 👨‍🦽 man in manual wheelchair +1F468 1F3FB 200D 1F9BD ; fully-qualified # 👨🏻‍🦽 man in manual wheelchair: light skin tone +1F468 1F3FC 200D 1F9BD ; fully-qualified # 👨🏼‍🦽 man in manual wheelchair: medium-light skin tone +1F468 1F3FD 200D 1F9BD ; fully-qualified # 👨🏽‍🦽 man in manual wheelchair: medium skin tone +1F468 1F3FE 200D 1F9BD ; fully-qualified # 👨🏾‍🦽 man in manual wheelchair: medium-dark skin tone +1F468 1F3FF 200D 1F9BD ; fully-qualified # 👨🏿‍🦽 man in manual wheelchair: dark skin tone +1F469 200D 1F9BD ; fully-qualified # 👩‍🦽 woman in manual wheelchair +1F469 1F3FB 200D 1F9BD ; fully-qualified # 👩🏻‍🦽 woman in manual wheelchair: light skin tone +1F469 1F3FC 200D 1F9BD ; fully-qualified # 👩🏼‍🦽 woman in manual wheelchair: medium-light skin tone +1F469 1F3FD 200D 1F9BD ; fully-qualified # 👩🏽‍🦽 woman in manual wheelchair: medium skin tone +1F469 1F3FE 200D 1F9BD ; fully-qualified # 👩🏾‍🦽 woman in manual wheelchair: medium-dark skin tone +1F469 1F3FF 200D 1F9BD ; fully-qualified # 👩🏿‍🦽 woman in manual wheelchair: dark skin tone +1F3C3 ; fully-qualified # 🏃 person running +1F3C3 1F3FB ; fully-qualified # 🏃🏻 person running: light skin tone +1F3C3 1F3FC ; fully-qualified # 🏃🏼 person running: medium-light skin tone +1F3C3 1F3FD ; fully-qualified # 🏃🏽 person running: medium skin tone +1F3C3 1F3FE ; fully-qualified # 🏃🏾 person running: medium-dark skin tone +1F3C3 1F3FF ; fully-qualified # 🏃🏿 person running: dark skin tone +1F3C3 200D 2642 FE0F ; fully-qualified # 🏃‍♂️ man running +1F3C3 200D 2642 ; minimally-qualified # 🏃‍♂ man running +1F3C3 1F3FB 200D 2642 FE0F ; fully-qualified # 🏃🏻‍♂️ man running: light skin tone +1F3C3 1F3FB 200D 2642 ; minimally-qualified # 🏃🏻‍♂ man running: light skin tone +1F3C3 1F3FC 200D 2642 FE0F ; fully-qualified # 🏃🏼‍♂️ man running: medium-light skin tone +1F3C3 1F3FC 200D 2642 ; minimally-qualified # 🏃🏼‍♂ man running: medium-light skin tone +1F3C3 1F3FD 200D 2642 FE0F ; fully-qualified # 🏃🏽‍♂️ man running: medium skin tone +1F3C3 1F3FD 200D 2642 ; minimally-qualified # 🏃🏽‍♂ man running: medium skin tone +1F3C3 1F3FE 200D 2642 FE0F ; fully-qualified # 🏃🏾‍♂️ man running: medium-dark skin tone +1F3C3 1F3FE 200D 2642 ; minimally-qualified # 🏃🏾‍♂ man running: medium-dark skin tone +1F3C3 1F3FF 200D 2642 FE0F ; fully-qualified # 🏃🏿‍♂️ man running: dark skin tone +1F3C3 1F3FF 200D 2642 ; minimally-qualified # 🏃🏿‍♂ man running: dark skin tone +1F3C3 200D 2640 FE0F ; fully-qualified # 🏃‍♀️ woman running +1F3C3 200D 2640 ; minimally-qualified # 🏃‍♀ woman running +1F3C3 1F3FB 200D 2640 FE0F ; fully-qualified # 🏃🏻‍♀️ woman running: light skin tone +1F3C3 1F3FB 200D 2640 ; minimally-qualified # 🏃🏻‍♀ woman running: light skin tone +1F3C3 1F3FC 200D 2640 FE0F ; fully-qualified # 🏃🏼‍♀️ woman running: medium-light skin tone +1F3C3 1F3FC 200D 2640 ; minimally-qualified # 🏃🏼‍♀ woman running: medium-light skin tone +1F3C3 1F3FD 200D 2640 FE0F ; fully-qualified # 🏃🏽‍♀️ woman running: medium skin tone +1F3C3 1F3FD 200D 2640 ; minimally-qualified # 🏃🏽‍♀ woman running: medium skin tone +1F3C3 1F3FE 200D 2640 FE0F ; fully-qualified # 🏃🏾‍♀️ woman running: medium-dark skin tone +1F3C3 1F3FE 200D 2640 ; minimally-qualified # 🏃🏾‍♀ woman running: medium-dark skin tone +1F3C3 1F3FF 200D 2640 FE0F ; fully-qualified # 🏃🏿‍♀️ woman running: dark skin tone +1F3C3 1F3FF 200D 2640 ; minimally-qualified # 🏃🏿‍♀ woman running: dark skin tone +1F483 ; fully-qualified # 💃 woman dancing +1F483 1F3FB ; fully-qualified # 💃🏻 woman dancing: light skin tone +1F483 1F3FC ; fully-qualified # 💃🏼 woman dancing: medium-light skin tone +1F483 1F3FD ; fully-qualified # 💃🏽 woman dancing: medium skin tone +1F483 1F3FE ; fully-qualified # 💃🏾 woman dancing: medium-dark skin tone +1F483 1F3FF ; fully-qualified # 💃🏿 woman dancing: dark skin tone +1F57A ; fully-qualified # 🕺 man dancing +1F57A 1F3FB ; fully-qualified # 🕺🏻 man dancing: light skin tone +1F57A 1F3FC ; fully-qualified # 🕺🏼 man dancing: medium-light skin tone +1F57A 1F3FD ; fully-qualified # 🕺🏽 man dancing: medium skin tone +1F57A 1F3FE ; fully-qualified # 🕺🏾 man dancing: medium-dark skin tone +1F57A 1F3FF ; fully-qualified # 🕺🏿 man dancing: dark skin tone +1F574 FE0F ; fully-qualified # 🕴️ man in suit levitating +1F574 ; unqualified # 🕴 man in suit levitating +1F574 1F3FB ; fully-qualified # 🕴🏻 man in suit levitating: light skin tone +1F574 1F3FC ; fully-qualified # 🕴🏼 man in suit levitating: medium-light skin tone +1F574 1F3FD ; fully-qualified # 🕴🏽 man in suit levitating: medium skin tone +1F574 1F3FE ; fully-qualified # 🕴🏾 man in suit levitating: medium-dark skin tone +1F574 1F3FF ; fully-qualified # 🕴🏿 man in suit levitating: dark skin tone +1F46F ; fully-qualified # 👯 people with bunny ears +1F46F 200D 2642 FE0F ; fully-qualified # 👯‍♂️ men with bunny ears +1F46F 200D 2642 ; minimally-qualified # 👯‍♂ men with bunny ears +1F46F 200D 2640 FE0F ; fully-qualified # 👯‍♀️ women with bunny ears +1F46F 200D 2640 ; minimally-qualified # 👯‍♀ women with bunny ears +1F9D6 ; fully-qualified # 🧖 person in steamy room +1F9D6 1F3FB ; fully-qualified # 🧖🏻 person in steamy room: light skin tone +1F9D6 1F3FC ; fully-qualified # 🧖🏼 person in steamy room: medium-light skin tone +1F9D6 1F3FD ; fully-qualified # 🧖🏽 person in steamy room: medium skin tone +1F9D6 1F3FE ; fully-qualified # 🧖🏾 person in steamy room: medium-dark skin tone +1F9D6 1F3FF ; fully-qualified # 🧖🏿 person in steamy room: dark skin tone +1F9D6 200D 2642 FE0F ; fully-qualified # 🧖‍♂️ man in steamy room +1F9D6 200D 2642 ; minimally-qualified # 🧖‍♂ man in steamy room +1F9D6 1F3FB 200D 2642 FE0F ; fully-qualified # 🧖🏻‍♂️ man in steamy room: light skin tone +1F9D6 1F3FB 200D 2642 ; minimally-qualified # 🧖🏻‍♂ man in steamy room: light skin tone +1F9D6 1F3FC 200D 2642 FE0F ; fully-qualified # 🧖🏼‍♂️ man in steamy room: medium-light skin tone +1F9D6 1F3FC 200D 2642 ; minimally-qualified # 🧖🏼‍♂ man in steamy room: medium-light skin tone +1F9D6 1F3FD 200D 2642 FE0F ; fully-qualified # 🧖🏽‍♂️ man in steamy room: medium skin tone +1F9D6 1F3FD 200D 2642 ; minimally-qualified # 🧖🏽‍♂ man in steamy room: medium skin tone +1F9D6 1F3FE 200D 2642 FE0F ; fully-qualified # 🧖🏾‍♂️ man in steamy room: medium-dark skin tone +1F9D6 1F3FE 200D 2642 ; minimally-qualified # 🧖🏾‍♂ man in steamy room: medium-dark skin tone +1F9D6 1F3FF 200D 2642 FE0F ; fully-qualified # 🧖🏿‍♂️ man in steamy room: dark skin tone +1F9D6 1F3FF 200D 2642 ; minimally-qualified # 🧖🏿‍♂ man in steamy room: dark skin tone +1F9D6 200D 2640 FE0F ; fully-qualified # 🧖‍♀️ woman in steamy room +1F9D6 200D 2640 ; minimally-qualified # 🧖‍♀ woman in steamy room +1F9D6 1F3FB 200D 2640 FE0F ; fully-qualified # 🧖🏻‍♀️ woman in steamy room: light skin tone +1F9D6 1F3FB 200D 2640 ; minimally-qualified # 🧖🏻‍♀ woman in steamy room: light skin tone +1F9D6 1F3FC 200D 2640 FE0F ; fully-qualified # 🧖🏼‍♀️ woman in steamy room: medium-light skin tone +1F9D6 1F3FC 200D 2640 ; minimally-qualified # 🧖🏼‍♀ woman in steamy room: medium-light skin tone +1F9D6 1F3FD 200D 2640 FE0F ; fully-qualified # 🧖🏽‍♀️ woman in steamy room: medium skin tone +1F9D6 1F3FD 200D 2640 ; minimally-qualified # 🧖🏽‍♀ woman in steamy room: medium skin tone +1F9D6 1F3FE 200D 2640 FE0F ; fully-qualified # 🧖🏾‍♀️ woman in steamy room: medium-dark skin tone +1F9D6 1F3FE 200D 2640 ; minimally-qualified # 🧖🏾‍♀ woman in steamy room: medium-dark skin tone +1F9D6 1F3FF 200D 2640 FE0F ; fully-qualified # 🧖🏿‍♀️ woman in steamy room: dark skin tone +1F9D6 1F3FF 200D 2640 ; minimally-qualified # 🧖🏿‍♀ woman in steamy room: dark skin tone +1F9D7 ; fully-qualified # 🧗 person climbing +1F9D7 1F3FB ; fully-qualified # 🧗🏻 person climbing: light skin tone +1F9D7 1F3FC ; fully-qualified # 🧗🏼 person climbing: medium-light skin tone +1F9D7 1F3FD ; fully-qualified # 🧗🏽 person climbing: medium skin tone +1F9D7 1F3FE ; fully-qualified # 🧗🏾 person climbing: medium-dark skin tone +1F9D7 1F3FF ; fully-qualified # 🧗🏿 person climbing: dark skin tone +1F9D7 200D 2642 FE0F ; fully-qualified # 🧗‍♂️ man climbing +1F9D7 200D 2642 ; minimally-qualified # 🧗‍♂ man climbing +1F9D7 1F3FB 200D 2642 FE0F ; fully-qualified # 🧗🏻‍♂️ man climbing: light skin tone +1F9D7 1F3FB 200D 2642 ; minimally-qualified # 🧗🏻‍♂ man climbing: light skin tone +1F9D7 1F3FC 200D 2642 FE0F ; fully-qualified # 🧗🏼‍♂️ man climbing: medium-light skin tone +1F9D7 1F3FC 200D 2642 ; minimally-qualified # 🧗🏼‍♂ man climbing: medium-light skin tone +1F9D7 1F3FD 200D 2642 FE0F ; fully-qualified # 🧗🏽‍♂️ man climbing: medium skin tone +1F9D7 1F3FD 200D 2642 ; minimally-qualified # 🧗🏽‍♂ man climbing: medium skin tone +1F9D7 1F3FE 200D 2642 FE0F ; fully-qualified # 🧗🏾‍♂️ man climbing: medium-dark skin tone +1F9D7 1F3FE 200D 2642 ; minimally-qualified # 🧗🏾‍♂ man climbing: medium-dark skin tone +1F9D7 1F3FF 200D 2642 FE0F ; fully-qualified # 🧗🏿‍♂️ man climbing: dark skin tone +1F9D7 1F3FF 200D 2642 ; minimally-qualified # 🧗🏿‍♂ man climbing: dark skin tone +1F9D7 200D 2640 FE0F ; fully-qualified # 🧗‍♀️ woman climbing +1F9D7 200D 2640 ; minimally-qualified # 🧗‍♀ woman climbing +1F9D7 1F3FB 200D 2640 FE0F ; fully-qualified # 🧗🏻‍♀️ woman climbing: light skin tone +1F9D7 1F3FB 200D 2640 ; minimally-qualified # 🧗🏻‍♀ woman climbing: light skin tone +1F9D7 1F3FC 200D 2640 FE0F ; fully-qualified # 🧗🏼‍♀️ woman climbing: medium-light skin tone +1F9D7 1F3FC 200D 2640 ; minimally-qualified # 🧗🏼‍♀ woman climbing: medium-light skin tone +1F9D7 1F3FD 200D 2640 FE0F ; fully-qualified # 🧗🏽‍♀️ woman climbing: medium skin tone +1F9D7 1F3FD 200D 2640 ; minimally-qualified # 🧗🏽‍♀ woman climbing: medium skin tone +1F9D7 1F3FE 200D 2640 FE0F ; fully-qualified # 🧗🏾‍♀️ woman climbing: medium-dark skin tone +1F9D7 1F3FE 200D 2640 ; minimally-qualified # 🧗🏾‍♀ woman climbing: medium-dark skin tone +1F9D7 1F3FF 200D 2640 FE0F ; fully-qualified # 🧗🏿‍♀️ woman climbing: dark skin tone +1F9D7 1F3FF 200D 2640 ; minimally-qualified # 🧗🏿‍♀ woman climbing: dark skin tone + +# subgroup: person-sport +1F93A ; fully-qualified # 🤺 person fencing +1F3C7 ; fully-qualified # 🏇 horse racing +1F3C7 1F3FB ; fully-qualified # 🏇🏻 horse racing: light skin tone +1F3C7 1F3FC ; fully-qualified # 🏇🏼 horse racing: medium-light skin tone +1F3C7 1F3FD ; fully-qualified # 🏇🏽 horse racing: medium skin tone +1F3C7 1F3FE ; fully-qualified # 🏇🏾 horse racing: medium-dark skin tone +1F3C7 1F3FF ; fully-qualified # 🏇🏿 horse racing: dark skin tone +26F7 FE0F ; fully-qualified # ⛷️ skier +26F7 ; unqualified # ⛷ skier +1F3C2 ; fully-qualified # 🏂 snowboarder +1F3C2 1F3FB ; fully-qualified # 🏂🏻 snowboarder: light skin tone +1F3C2 1F3FC ; fully-qualified # 🏂🏼 snowboarder: medium-light skin tone +1F3C2 1F3FD ; fully-qualified # 🏂🏽 snowboarder: medium skin tone +1F3C2 1F3FE ; fully-qualified # 🏂🏾 snowboarder: medium-dark skin tone +1F3C2 1F3FF ; fully-qualified # 🏂🏿 snowboarder: dark skin tone +1F3CC FE0F ; fully-qualified # 🏌️ person golfing +1F3CC ; unqualified # 🏌 person golfing +1F3CC 1F3FB ; fully-qualified # 🏌🏻 person golfing: light skin tone +1F3CC 1F3FC ; fully-qualified # 🏌🏼 person golfing: medium-light skin tone +1F3CC 1F3FD ; fully-qualified # 🏌🏽 person golfing: medium skin tone +1F3CC 1F3FE ; fully-qualified # 🏌🏾 person golfing: medium-dark skin tone +1F3CC 1F3FF ; fully-qualified # 🏌🏿 person golfing: dark skin tone +1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ man golfing +1F3CC 200D 2642 FE0F ; unqualified # 🏌‍♂️ man golfing +1F3CC FE0F 200D 2642 ; unqualified # 🏌️‍♂ man golfing +1F3CC 200D 2642 ; unqualified # 🏌‍♂ man golfing +1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ man golfing: light skin tone +1F3CC 1F3FB 200D 2642 ; minimally-qualified # 🏌🏻‍♂ man golfing: light skin tone +1F3CC 1F3FC 200D 2642 FE0F ; fully-qualified # 🏌🏼‍♂️ man golfing: medium-light skin tone +1F3CC 1F3FC 200D 2642 ; minimally-qualified # 🏌🏼‍♂ man golfing: medium-light skin tone +1F3CC 1F3FD 200D 2642 FE0F ; fully-qualified # 🏌🏽‍♂️ man golfing: medium skin tone +1F3CC 1F3FD 200D 2642 ; minimally-qualified # 🏌🏽‍♂ man golfing: medium skin tone +1F3CC 1F3FE 200D 2642 FE0F ; fully-qualified # 🏌🏾‍♂️ man golfing: medium-dark skin tone +1F3CC 1F3FE 200D 2642 ; minimally-qualified # 🏌🏾‍♂ man golfing: medium-dark skin tone +1F3CC 1F3FF 200D 2642 FE0F ; fully-qualified # 🏌🏿‍♂️ man golfing: dark skin tone +1F3CC 1F3FF 200D 2642 ; minimally-qualified # 🏌🏿‍♂ man golfing: dark skin tone +1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ woman golfing +1F3CC 200D 2640 FE0F ; unqualified # 🏌‍♀️ woman golfing +1F3CC FE0F 200D 2640 ; unqualified # 🏌️‍♀ woman golfing +1F3CC 200D 2640 ; unqualified # 🏌‍♀ woman golfing +1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ woman golfing: light skin tone +1F3CC 1F3FB 200D 2640 ; minimally-qualified # 🏌🏻‍♀ woman golfing: light skin tone +1F3CC 1F3FC 200D 2640 FE0F ; fully-qualified # 🏌🏼‍♀️ woman golfing: medium-light skin tone +1F3CC 1F3FC 200D 2640 ; minimally-qualified # 🏌🏼‍♀ woman golfing: medium-light skin tone +1F3CC 1F3FD 200D 2640 FE0F ; fully-qualified # 🏌🏽‍♀️ woman golfing: medium skin tone +1F3CC 1F3FD 200D 2640 ; minimally-qualified # 🏌🏽‍♀ woman golfing: medium skin tone +1F3CC 1F3FE 200D 2640 FE0F ; fully-qualified # 🏌🏾‍♀️ woman golfing: medium-dark skin tone +1F3CC 1F3FE 200D 2640 ; minimally-qualified # 🏌🏾‍♀ woman golfing: medium-dark skin tone +1F3CC 1F3FF 200D 2640 FE0F ; fully-qualified # 🏌🏿‍♀️ woman golfing: dark skin tone +1F3CC 1F3FF 200D 2640 ; minimally-qualified # 🏌🏿‍♀ woman golfing: dark skin tone +1F3C4 ; fully-qualified # 🏄 person surfing +1F3C4 1F3FB ; fully-qualified # 🏄🏻 person surfing: light skin tone +1F3C4 1F3FC ; fully-qualified # 🏄🏼 person surfing: medium-light skin tone +1F3C4 1F3FD ; fully-qualified # 🏄🏽 person surfing: medium skin tone +1F3C4 1F3FE ; fully-qualified # 🏄🏾 person surfing: medium-dark skin tone +1F3C4 1F3FF ; fully-qualified # 🏄🏿 person surfing: dark skin tone +1F3C4 200D 2642 FE0F ; fully-qualified # 🏄‍♂️ man surfing +1F3C4 200D 2642 ; minimally-qualified # 🏄‍♂ man surfing +1F3C4 1F3FB 200D 2642 FE0F ; fully-qualified # 🏄🏻‍♂️ man surfing: light skin tone +1F3C4 1F3FB 200D 2642 ; minimally-qualified # 🏄🏻‍♂ man surfing: light skin tone +1F3C4 1F3FC 200D 2642 FE0F ; fully-qualified # 🏄🏼‍♂️ man surfing: medium-light skin tone +1F3C4 1F3FC 200D 2642 ; minimally-qualified # 🏄🏼‍♂ man surfing: medium-light skin tone +1F3C4 1F3FD 200D 2642 FE0F ; fully-qualified # 🏄🏽‍♂️ man surfing: medium skin tone +1F3C4 1F3FD 200D 2642 ; minimally-qualified # 🏄🏽‍♂ man surfing: medium skin tone +1F3C4 1F3FE 200D 2642 FE0F ; fully-qualified # 🏄🏾‍♂️ man surfing: medium-dark skin tone +1F3C4 1F3FE 200D 2642 ; minimally-qualified # 🏄🏾‍♂ man surfing: medium-dark skin tone +1F3C4 1F3FF 200D 2642 FE0F ; fully-qualified # 🏄🏿‍♂️ man surfing: dark skin tone +1F3C4 1F3FF 200D 2642 ; minimally-qualified # 🏄🏿‍♂ man surfing: dark skin tone +1F3C4 200D 2640 FE0F ; fully-qualified # 🏄‍♀️ woman surfing +1F3C4 200D 2640 ; minimally-qualified # 🏄‍♀ woman surfing +1F3C4 1F3FB 200D 2640 FE0F ; fully-qualified # 🏄🏻‍♀️ woman surfing: light skin tone +1F3C4 1F3FB 200D 2640 ; minimally-qualified # 🏄🏻‍♀ woman surfing: light skin tone +1F3C4 1F3FC 200D 2640 FE0F ; fully-qualified # 🏄🏼‍♀️ woman surfing: medium-light skin tone +1F3C4 1F3FC 200D 2640 ; minimally-qualified # 🏄🏼‍♀ woman surfing: medium-light skin tone +1F3C4 1F3FD 200D 2640 FE0F ; fully-qualified # 🏄🏽‍♀️ woman surfing: medium skin tone +1F3C4 1F3FD 200D 2640 ; minimally-qualified # 🏄🏽‍♀ woman surfing: medium skin tone +1F3C4 1F3FE 200D 2640 FE0F ; fully-qualified # 🏄🏾‍♀️ woman surfing: medium-dark skin tone +1F3C4 1F3FE 200D 2640 ; minimally-qualified # 🏄🏾‍♀ woman surfing: medium-dark skin tone +1F3C4 1F3FF 200D 2640 FE0F ; fully-qualified # 🏄🏿‍♀️ woman surfing: dark skin tone +1F3C4 1F3FF 200D 2640 ; minimally-qualified # 🏄🏿‍♀ woman surfing: dark skin tone +1F6A3 ; fully-qualified # 🚣 person rowing boat +1F6A3 1F3FB ; fully-qualified # 🚣🏻 person rowing boat: light skin tone +1F6A3 1F3FC ; fully-qualified # 🚣🏼 person rowing boat: medium-light skin tone +1F6A3 1F3FD ; fully-qualified # 🚣🏽 person rowing boat: medium skin tone +1F6A3 1F3FE ; fully-qualified # 🚣🏾 person rowing boat: medium-dark skin tone +1F6A3 1F3FF ; fully-qualified # 🚣🏿 person rowing boat: dark skin tone +1F6A3 200D 2642 FE0F ; fully-qualified # 🚣‍♂️ man rowing boat +1F6A3 200D 2642 ; minimally-qualified # 🚣‍♂ man rowing boat +1F6A3 1F3FB 200D 2642 FE0F ; fully-qualified # 🚣🏻‍♂️ man rowing boat: light skin tone +1F6A3 1F3FB 200D 2642 ; minimally-qualified # 🚣🏻‍♂ man rowing boat: light skin tone +1F6A3 1F3FC 200D 2642 FE0F ; fully-qualified # 🚣🏼‍♂️ man rowing boat: medium-light skin tone +1F6A3 1F3FC 200D 2642 ; minimally-qualified # 🚣🏼‍♂ man rowing boat: medium-light skin tone +1F6A3 1F3FD 200D 2642 FE0F ; fully-qualified # 🚣🏽‍♂️ man rowing boat: medium skin tone +1F6A3 1F3FD 200D 2642 ; minimally-qualified # 🚣🏽‍♂ man rowing boat: medium skin tone +1F6A3 1F3FE 200D 2642 FE0F ; fully-qualified # 🚣🏾‍♂️ man rowing boat: medium-dark skin tone +1F6A3 1F3FE 200D 2642 ; minimally-qualified # 🚣🏾‍♂ man rowing boat: medium-dark skin tone +1F6A3 1F3FF 200D 2642 FE0F ; fully-qualified # 🚣🏿‍♂️ man rowing boat: dark skin tone +1F6A3 1F3FF 200D 2642 ; minimally-qualified # 🚣🏿‍♂ man rowing boat: dark skin tone +1F6A3 200D 2640 FE0F ; fully-qualified # 🚣‍♀️ woman rowing boat +1F6A3 200D 2640 ; minimally-qualified # 🚣‍♀ woman rowing boat +1F6A3 1F3FB 200D 2640 FE0F ; fully-qualified # 🚣🏻‍♀️ woman rowing boat: light skin tone +1F6A3 1F3FB 200D 2640 ; minimally-qualified # 🚣🏻‍♀ woman rowing boat: light skin tone +1F6A3 1F3FC 200D 2640 FE0F ; fully-qualified # 🚣🏼‍♀️ woman rowing boat: medium-light skin tone +1F6A3 1F3FC 200D 2640 ; minimally-qualified # 🚣🏼‍♀ woman rowing boat: medium-light skin tone +1F6A3 1F3FD 200D 2640 FE0F ; fully-qualified # 🚣🏽‍♀️ woman rowing boat: medium skin tone +1F6A3 1F3FD 200D 2640 ; minimally-qualified # 🚣🏽‍♀ woman rowing boat: medium skin tone +1F6A3 1F3FE 200D 2640 FE0F ; fully-qualified # 🚣🏾‍♀️ woman rowing boat: medium-dark skin tone +1F6A3 1F3FE 200D 2640 ; minimally-qualified # 🚣🏾‍♀ woman rowing boat: medium-dark skin tone +1F6A3 1F3FF 200D 2640 FE0F ; fully-qualified # 🚣🏿‍♀️ woman rowing boat: dark skin tone +1F6A3 1F3FF 200D 2640 ; minimally-qualified # 🚣🏿‍♀ woman rowing boat: dark skin tone +1F3CA ; fully-qualified # 🏊 person swimming +1F3CA 1F3FB ; fully-qualified # 🏊🏻 person swimming: light skin tone +1F3CA 1F3FC ; fully-qualified # 🏊🏼 person swimming: medium-light skin tone +1F3CA 1F3FD ; fully-qualified # 🏊🏽 person swimming: medium skin tone +1F3CA 1F3FE ; fully-qualified # 🏊🏾 person swimming: medium-dark skin tone +1F3CA 1F3FF ; fully-qualified # 🏊🏿 person swimming: dark skin tone +1F3CA 200D 2642 FE0F ; fully-qualified # 🏊‍♂️ man swimming +1F3CA 200D 2642 ; minimally-qualified # 🏊‍♂ man swimming +1F3CA 1F3FB 200D 2642 FE0F ; fully-qualified # 🏊🏻‍♂️ man swimming: light skin tone +1F3CA 1F3FB 200D 2642 ; minimally-qualified # 🏊🏻‍♂ man swimming: light skin tone +1F3CA 1F3FC 200D 2642 FE0F ; fully-qualified # 🏊🏼‍♂️ man swimming: medium-light skin tone +1F3CA 1F3FC 200D 2642 ; minimally-qualified # 🏊🏼‍♂ man swimming: medium-light skin tone +1F3CA 1F3FD 200D 2642 FE0F ; fully-qualified # 🏊🏽‍♂️ man swimming: medium skin tone +1F3CA 1F3FD 200D 2642 ; minimally-qualified # 🏊🏽‍♂ man swimming: medium skin tone +1F3CA 1F3FE 200D 2642 FE0F ; fully-qualified # 🏊🏾‍♂️ man swimming: medium-dark skin tone +1F3CA 1F3FE 200D 2642 ; minimally-qualified # 🏊🏾‍♂ man swimming: medium-dark skin tone +1F3CA 1F3FF 200D 2642 FE0F ; fully-qualified # 🏊🏿‍♂️ man swimming: dark skin tone +1F3CA 1F3FF 200D 2642 ; minimally-qualified # 🏊🏿‍♂ man swimming: dark skin tone +1F3CA 200D 2640 FE0F ; fully-qualified # 🏊‍♀️ woman swimming +1F3CA 200D 2640 ; minimally-qualified # 🏊‍♀ woman swimming +1F3CA 1F3FB 200D 2640 FE0F ; fully-qualified # 🏊🏻‍♀️ woman swimming: light skin tone +1F3CA 1F3FB 200D 2640 ; minimally-qualified # 🏊🏻‍♀ woman swimming: light skin tone +1F3CA 1F3FC 200D 2640 FE0F ; fully-qualified # 🏊🏼‍♀️ woman swimming: medium-light skin tone +1F3CA 1F3FC 200D 2640 ; minimally-qualified # 🏊🏼‍♀ woman swimming: medium-light skin tone +1F3CA 1F3FD 200D 2640 FE0F ; fully-qualified # 🏊🏽‍♀️ woman swimming: medium skin tone +1F3CA 1F3FD 200D 2640 ; minimally-qualified # 🏊🏽‍♀ woman swimming: medium skin tone +1F3CA 1F3FE 200D 2640 FE0F ; fully-qualified # 🏊🏾‍♀️ woman swimming: medium-dark skin tone +1F3CA 1F3FE 200D 2640 ; minimally-qualified # 🏊🏾‍♀ woman swimming: medium-dark skin tone +1F3CA 1F3FF 200D 2640 FE0F ; fully-qualified # 🏊🏿‍♀️ woman swimming: dark skin tone +1F3CA 1F3FF 200D 2640 ; minimally-qualified # 🏊🏿‍♀ woman swimming: dark skin tone +26F9 FE0F ; fully-qualified # ⛹️ person bouncing ball +26F9 ; unqualified # ⛹ person bouncing ball +26F9 1F3FB ; fully-qualified # ⛹🏻 person bouncing ball: light skin tone +26F9 1F3FC ; fully-qualified # ⛹🏼 person bouncing ball: medium-light skin tone +26F9 1F3FD ; fully-qualified # ⛹🏽 person bouncing ball: medium skin tone +26F9 1F3FE ; fully-qualified # ⛹🏾 person bouncing ball: medium-dark skin tone +26F9 1F3FF ; fully-qualified # ⛹🏿 person bouncing ball: dark skin tone +26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ man bouncing ball +26F9 200D 2642 FE0F ; unqualified # ⛹‍♂️ man bouncing ball +26F9 FE0F 200D 2642 ; unqualified # ⛹️‍♂ man bouncing ball +26F9 200D 2642 ; unqualified # ⛹‍♂ man bouncing ball +26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ man bouncing ball: light skin tone +26F9 1F3FB 200D 2642 ; minimally-qualified # ⛹🏻‍♂ man bouncing ball: light skin tone +26F9 1F3FC 200D 2642 FE0F ; fully-qualified # ⛹🏼‍♂️ man bouncing ball: medium-light skin tone +26F9 1F3FC 200D 2642 ; minimally-qualified # ⛹🏼‍♂ man bouncing ball: medium-light skin tone +26F9 1F3FD 200D 2642 FE0F ; fully-qualified # ⛹🏽‍♂️ man bouncing ball: medium skin tone +26F9 1F3FD 200D 2642 ; minimally-qualified # ⛹🏽‍♂ man bouncing ball: medium skin tone +26F9 1F3FE 200D 2642 FE0F ; fully-qualified # ⛹🏾‍♂️ man bouncing ball: medium-dark skin tone +26F9 1F3FE 200D 2642 ; minimally-qualified # ⛹🏾‍♂ man bouncing ball: medium-dark skin tone +26F9 1F3FF 200D 2642 FE0F ; fully-qualified # ⛹🏿‍♂️ man bouncing ball: dark skin tone +26F9 1F3FF 200D 2642 ; minimally-qualified # ⛹🏿‍♂ man bouncing ball: dark skin tone +26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ woman bouncing ball +26F9 200D 2640 FE0F ; unqualified # ⛹‍♀️ woman bouncing ball +26F9 FE0F 200D 2640 ; unqualified # ⛹️‍♀ woman bouncing ball +26F9 200D 2640 ; unqualified # ⛹‍♀ woman bouncing ball +26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ woman bouncing ball: light skin tone +26F9 1F3FB 200D 2640 ; minimally-qualified # ⛹🏻‍♀ woman bouncing ball: light skin tone +26F9 1F3FC 200D 2640 FE0F ; fully-qualified # ⛹🏼‍♀️ woman bouncing ball: medium-light skin tone +26F9 1F3FC 200D 2640 ; minimally-qualified # ⛹🏼‍♀ woman bouncing ball: medium-light skin tone +26F9 1F3FD 200D 2640 FE0F ; fully-qualified # ⛹🏽‍♀️ woman bouncing ball: medium skin tone +26F9 1F3FD 200D 2640 ; minimally-qualified # ⛹🏽‍♀ woman bouncing ball: medium skin tone +26F9 1F3FE 200D 2640 FE0F ; fully-qualified # ⛹🏾‍♀️ woman bouncing ball: medium-dark skin tone +26F9 1F3FE 200D 2640 ; minimally-qualified # ⛹🏾‍♀ woman bouncing ball: medium-dark skin tone +26F9 1F3FF 200D 2640 FE0F ; fully-qualified # ⛹🏿‍♀️ woman bouncing ball: dark skin tone +26F9 1F3FF 200D 2640 ; minimally-qualified # ⛹🏿‍♀ woman bouncing ball: dark skin tone +1F3CB FE0F ; fully-qualified # 🏋️ person lifting weights +1F3CB ; unqualified # 🏋 person lifting weights +1F3CB 1F3FB ; fully-qualified # 🏋🏻 person lifting weights: light skin tone +1F3CB 1F3FC ; fully-qualified # 🏋🏼 person lifting weights: medium-light skin tone +1F3CB 1F3FD ; fully-qualified # 🏋🏽 person lifting weights: medium skin tone +1F3CB 1F3FE ; fully-qualified # 🏋🏾 person lifting weights: medium-dark skin tone +1F3CB 1F3FF ; fully-qualified # 🏋🏿 person lifting weights: dark skin tone +1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ man lifting weights +1F3CB 200D 2642 FE0F ; unqualified # 🏋‍♂️ man lifting weights +1F3CB FE0F 200D 2642 ; unqualified # 🏋️‍♂ man lifting weights +1F3CB 200D 2642 ; unqualified # 🏋‍♂ man lifting weights +1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ man lifting weights: light skin tone +1F3CB 1F3FB 200D 2642 ; minimally-qualified # 🏋🏻‍♂ man lifting weights: light skin tone +1F3CB 1F3FC 200D 2642 FE0F ; fully-qualified # 🏋🏼‍♂️ man lifting weights: medium-light skin tone +1F3CB 1F3FC 200D 2642 ; minimally-qualified # 🏋🏼‍♂ man lifting weights: medium-light skin tone +1F3CB 1F3FD 200D 2642 FE0F ; fully-qualified # 🏋🏽‍♂️ man lifting weights: medium skin tone +1F3CB 1F3FD 200D 2642 ; minimally-qualified # 🏋🏽‍♂ man lifting weights: medium skin tone +1F3CB 1F3FE 200D 2642 FE0F ; fully-qualified # 🏋🏾‍♂️ man lifting weights: medium-dark skin tone +1F3CB 1F3FE 200D 2642 ; minimally-qualified # 🏋🏾‍♂ man lifting weights: medium-dark skin tone +1F3CB 1F3FF 200D 2642 FE0F ; fully-qualified # 🏋🏿‍♂️ man lifting weights: dark skin tone +1F3CB 1F3FF 200D 2642 ; minimally-qualified # 🏋🏿‍♂ man lifting weights: dark skin tone +1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ woman lifting weights +1F3CB 200D 2640 FE0F ; unqualified # 🏋‍♀️ woman lifting weights +1F3CB FE0F 200D 2640 ; unqualified # 🏋️‍♀ woman lifting weights +1F3CB 200D 2640 ; unqualified # 🏋‍♀ woman lifting weights +1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ woman lifting weights: light skin tone +1F3CB 1F3FB 200D 2640 ; minimally-qualified # 🏋🏻‍♀ woman lifting weights: light skin tone +1F3CB 1F3FC 200D 2640 FE0F ; fully-qualified # 🏋🏼‍♀️ woman lifting weights: medium-light skin tone +1F3CB 1F3FC 200D 2640 ; minimally-qualified # 🏋🏼‍♀ woman lifting weights: medium-light skin tone +1F3CB 1F3FD 200D 2640 FE0F ; fully-qualified # 🏋🏽‍♀️ woman lifting weights: medium skin tone +1F3CB 1F3FD 200D 2640 ; minimally-qualified # 🏋🏽‍♀ woman lifting weights: medium skin tone +1F3CB 1F3FE 200D 2640 FE0F ; fully-qualified # 🏋🏾‍♀️ woman lifting weights: medium-dark skin tone +1F3CB 1F3FE 200D 2640 ; minimally-qualified # 🏋🏾‍♀ woman lifting weights: medium-dark skin tone +1F3CB 1F3FF 200D 2640 FE0F ; fully-qualified # 🏋🏿‍♀️ woman lifting weights: dark skin tone +1F3CB 1F3FF 200D 2640 ; minimally-qualified # 🏋🏿‍♀ woman lifting weights: dark skin tone +1F6B4 ; fully-qualified # 🚴 person biking +1F6B4 1F3FB ; fully-qualified # 🚴🏻 person biking: light skin tone +1F6B4 1F3FC ; fully-qualified # 🚴🏼 person biking: medium-light skin tone +1F6B4 1F3FD ; fully-qualified # 🚴🏽 person biking: medium skin tone +1F6B4 1F3FE ; fully-qualified # 🚴🏾 person biking: medium-dark skin tone +1F6B4 1F3FF ; fully-qualified # 🚴🏿 person biking: dark skin tone +1F6B4 200D 2642 FE0F ; fully-qualified # 🚴‍♂️ man biking +1F6B4 200D 2642 ; minimally-qualified # 🚴‍♂ man biking +1F6B4 1F3FB 200D 2642 FE0F ; fully-qualified # 🚴🏻‍♂️ man biking: light skin tone +1F6B4 1F3FB 200D 2642 ; minimally-qualified # 🚴🏻‍♂ man biking: light skin tone +1F6B4 1F3FC 200D 2642 FE0F ; fully-qualified # 🚴🏼‍♂️ man biking: medium-light skin tone +1F6B4 1F3FC 200D 2642 ; minimally-qualified # 🚴🏼‍♂ man biking: medium-light skin tone +1F6B4 1F3FD 200D 2642 FE0F ; fully-qualified # 🚴🏽‍♂️ man biking: medium skin tone +1F6B4 1F3FD 200D 2642 ; minimally-qualified # 🚴🏽‍♂ man biking: medium skin tone +1F6B4 1F3FE 200D 2642 FE0F ; fully-qualified # 🚴🏾‍♂️ man biking: medium-dark skin tone +1F6B4 1F3FE 200D 2642 ; minimally-qualified # 🚴🏾‍♂ man biking: medium-dark skin tone +1F6B4 1F3FF 200D 2642 FE0F ; fully-qualified # 🚴🏿‍♂️ man biking: dark skin tone +1F6B4 1F3FF 200D 2642 ; minimally-qualified # 🚴🏿‍♂ man biking: dark skin tone +1F6B4 200D 2640 FE0F ; fully-qualified # 🚴‍♀️ woman biking +1F6B4 200D 2640 ; minimally-qualified # 🚴‍♀ woman biking +1F6B4 1F3FB 200D 2640 FE0F ; fully-qualified # 🚴🏻‍♀️ woman biking: light skin tone +1F6B4 1F3FB 200D 2640 ; minimally-qualified # 🚴🏻‍♀ woman biking: light skin tone +1F6B4 1F3FC 200D 2640 FE0F ; fully-qualified # 🚴🏼‍♀️ woman biking: medium-light skin tone +1F6B4 1F3FC 200D 2640 ; minimally-qualified # 🚴🏼‍♀ woman biking: medium-light skin tone +1F6B4 1F3FD 200D 2640 FE0F ; fully-qualified # 🚴🏽‍♀️ woman biking: medium skin tone +1F6B4 1F3FD 200D 2640 ; minimally-qualified # 🚴🏽‍♀ woman biking: medium skin tone +1F6B4 1F3FE 200D 2640 FE0F ; fully-qualified # 🚴🏾‍♀️ woman biking: medium-dark skin tone +1F6B4 1F3FE 200D 2640 ; minimally-qualified # 🚴🏾‍♀ woman biking: medium-dark skin tone +1F6B4 1F3FF 200D 2640 FE0F ; fully-qualified # 🚴🏿‍♀️ woman biking: dark skin tone +1F6B4 1F3FF 200D 2640 ; minimally-qualified # 🚴🏿‍♀ woman biking: dark skin tone +1F6B5 ; fully-qualified # 🚵 person mountain biking +1F6B5 1F3FB ; fully-qualified # 🚵🏻 person mountain biking: light skin tone +1F6B5 1F3FC ; fully-qualified # 🚵🏼 person mountain biking: medium-light skin tone +1F6B5 1F3FD ; fully-qualified # 🚵🏽 person mountain biking: medium skin tone +1F6B5 1F3FE ; fully-qualified # 🚵🏾 person mountain biking: medium-dark skin tone +1F6B5 1F3FF ; fully-qualified # 🚵🏿 person mountain biking: dark skin tone +1F6B5 200D 2642 FE0F ; fully-qualified # 🚵‍♂️ man mountain biking +1F6B5 200D 2642 ; minimally-qualified # 🚵‍♂ man mountain biking +1F6B5 1F3FB 200D 2642 FE0F ; fully-qualified # 🚵🏻‍♂️ man mountain biking: light skin tone +1F6B5 1F3FB 200D 2642 ; minimally-qualified # 🚵🏻‍♂ man mountain biking: light skin tone +1F6B5 1F3FC 200D 2642 FE0F ; fully-qualified # 🚵🏼‍♂️ man mountain biking: medium-light skin tone +1F6B5 1F3FC 200D 2642 ; minimally-qualified # 🚵🏼‍♂ man mountain biking: medium-light skin tone +1F6B5 1F3FD 200D 2642 FE0F ; fully-qualified # 🚵🏽‍♂️ man mountain biking: medium skin tone +1F6B5 1F3FD 200D 2642 ; minimally-qualified # 🚵🏽‍♂ man mountain biking: medium skin tone +1F6B5 1F3FE 200D 2642 FE0F ; fully-qualified # 🚵🏾‍♂️ man mountain biking: medium-dark skin tone +1F6B5 1F3FE 200D 2642 ; minimally-qualified # 🚵🏾‍♂ man mountain biking: medium-dark skin tone +1F6B5 1F3FF 200D 2642 FE0F ; fully-qualified # 🚵🏿‍♂️ man mountain biking: dark skin tone +1F6B5 1F3FF 200D 2642 ; minimally-qualified # 🚵🏿‍♂ man mountain biking: dark skin tone +1F6B5 200D 2640 FE0F ; fully-qualified # 🚵‍♀️ woman mountain biking +1F6B5 200D 2640 ; minimally-qualified # 🚵‍♀ woman mountain biking +1F6B5 1F3FB 200D 2640 FE0F ; fully-qualified # 🚵🏻‍♀️ woman mountain biking: light skin tone +1F6B5 1F3FB 200D 2640 ; minimally-qualified # 🚵🏻‍♀ woman mountain biking: light skin tone +1F6B5 1F3FC 200D 2640 FE0F ; fully-qualified # 🚵🏼‍♀️ woman mountain biking: medium-light skin tone +1F6B5 1F3FC 200D 2640 ; minimally-qualified # 🚵🏼‍♀ woman mountain biking: medium-light skin tone +1F6B5 1F3FD 200D 2640 FE0F ; fully-qualified # 🚵🏽‍♀️ woman mountain biking: medium skin tone +1F6B5 1F3FD 200D 2640 ; minimally-qualified # 🚵🏽‍♀ woman mountain biking: medium skin tone +1F6B5 1F3FE 200D 2640 FE0F ; fully-qualified # 🚵🏾‍♀️ woman mountain biking: medium-dark skin tone +1F6B5 1F3FE 200D 2640 ; minimally-qualified # 🚵🏾‍♀ woman mountain biking: medium-dark skin tone +1F6B5 1F3FF 200D 2640 FE0F ; fully-qualified # 🚵🏿‍♀️ woman mountain biking: dark skin tone +1F6B5 1F3FF 200D 2640 ; minimally-qualified # 🚵🏿‍♀ woman mountain biking: dark skin tone +1F938 ; fully-qualified # 🤸 person cartwheeling +1F938 1F3FB ; fully-qualified # 🤸🏻 person cartwheeling: light skin tone +1F938 1F3FC ; fully-qualified # 🤸🏼 person cartwheeling: medium-light skin tone +1F938 1F3FD ; fully-qualified # 🤸🏽 person cartwheeling: medium skin tone +1F938 1F3FE ; fully-qualified # 🤸🏾 person cartwheeling: medium-dark skin tone +1F938 1F3FF ; fully-qualified # 🤸🏿 person cartwheeling: dark skin tone +1F938 200D 2642 FE0F ; fully-qualified # 🤸‍♂️ man cartwheeling +1F938 200D 2642 ; minimally-qualified # 🤸‍♂ man cartwheeling +1F938 1F3FB 200D 2642 FE0F ; fully-qualified # 🤸🏻‍♂️ man cartwheeling: light skin tone +1F938 1F3FB 200D 2642 ; minimally-qualified # 🤸🏻‍♂ man cartwheeling: light skin tone +1F938 1F3FC 200D 2642 FE0F ; fully-qualified # 🤸🏼‍♂️ man cartwheeling: medium-light skin tone +1F938 1F3FC 200D 2642 ; minimally-qualified # 🤸🏼‍♂ man cartwheeling: medium-light skin tone +1F938 1F3FD 200D 2642 FE0F ; fully-qualified # 🤸🏽‍♂️ man cartwheeling: medium skin tone +1F938 1F3FD 200D 2642 ; minimally-qualified # 🤸🏽‍♂ man cartwheeling: medium skin tone +1F938 1F3FE 200D 2642 FE0F ; fully-qualified # 🤸🏾‍♂️ man cartwheeling: medium-dark skin tone +1F938 1F3FE 200D 2642 ; minimally-qualified # 🤸🏾‍♂ man cartwheeling: medium-dark skin tone +1F938 1F3FF 200D 2642 FE0F ; fully-qualified # 🤸🏿‍♂️ man cartwheeling: dark skin tone +1F938 1F3FF 200D 2642 ; minimally-qualified # 🤸🏿‍♂ man cartwheeling: dark skin tone +1F938 200D 2640 FE0F ; fully-qualified # 🤸‍♀️ woman cartwheeling +1F938 200D 2640 ; minimally-qualified # 🤸‍♀ woman cartwheeling +1F938 1F3FB 200D 2640 FE0F ; fully-qualified # 🤸🏻‍♀️ woman cartwheeling: light skin tone +1F938 1F3FB 200D 2640 ; minimally-qualified # 🤸🏻‍♀ woman cartwheeling: light skin tone +1F938 1F3FC 200D 2640 FE0F ; fully-qualified # 🤸🏼‍♀️ woman cartwheeling: medium-light skin tone +1F938 1F3FC 200D 2640 ; minimally-qualified # 🤸🏼‍♀ woman cartwheeling: medium-light skin tone +1F938 1F3FD 200D 2640 FE0F ; fully-qualified # 🤸🏽‍♀️ woman cartwheeling: medium skin tone +1F938 1F3FD 200D 2640 ; minimally-qualified # 🤸🏽‍♀ woman cartwheeling: medium skin tone +1F938 1F3FE 200D 2640 FE0F ; fully-qualified # 🤸🏾‍♀️ woman cartwheeling: medium-dark skin tone +1F938 1F3FE 200D 2640 ; minimally-qualified # 🤸🏾‍♀ woman cartwheeling: medium-dark skin tone +1F938 1F3FF 200D 2640 FE0F ; fully-qualified # 🤸🏿‍♀️ woman cartwheeling: dark skin tone +1F938 1F3FF 200D 2640 ; minimally-qualified # 🤸🏿‍♀ woman cartwheeling: dark skin tone +1F93C ; fully-qualified # 🤼 people wrestling +1F93C 200D 2642 FE0F ; fully-qualified # 🤼‍♂️ men wrestling +1F93C 200D 2642 ; minimally-qualified # 🤼‍♂ men wrestling +1F93C 200D 2640 FE0F ; fully-qualified # 🤼‍♀️ women wrestling +1F93C 200D 2640 ; minimally-qualified # 🤼‍♀ women wrestling +1F93D ; fully-qualified # 🤽 person playing water polo +1F93D 1F3FB ; fully-qualified # 🤽🏻 person playing water polo: light skin tone +1F93D 1F3FC ; fully-qualified # 🤽🏼 person playing water polo: medium-light skin tone +1F93D 1F3FD ; fully-qualified # 🤽🏽 person playing water polo: medium skin tone +1F93D 1F3FE ; fully-qualified # 🤽🏾 person playing water polo: medium-dark skin tone +1F93D 1F3FF ; fully-qualified # 🤽🏿 person playing water polo: dark skin tone +1F93D 200D 2642 FE0F ; fully-qualified # 🤽‍♂️ man playing water polo +1F93D 200D 2642 ; minimally-qualified # 🤽‍♂ man playing water polo +1F93D 1F3FB 200D 2642 FE0F ; fully-qualified # 🤽🏻‍♂️ man playing water polo: light skin tone +1F93D 1F3FB 200D 2642 ; minimally-qualified # 🤽🏻‍♂ man playing water polo: light skin tone +1F93D 1F3FC 200D 2642 FE0F ; fully-qualified # 🤽🏼‍♂️ man playing water polo: medium-light skin tone +1F93D 1F3FC 200D 2642 ; minimally-qualified # 🤽🏼‍♂ man playing water polo: medium-light skin tone +1F93D 1F3FD 200D 2642 FE0F ; fully-qualified # 🤽🏽‍♂️ man playing water polo: medium skin tone +1F93D 1F3FD 200D 2642 ; minimally-qualified # 🤽🏽‍♂ man playing water polo: medium skin tone +1F93D 1F3FE 200D 2642 FE0F ; fully-qualified # 🤽🏾‍♂️ man playing water polo: medium-dark skin tone +1F93D 1F3FE 200D 2642 ; minimally-qualified # 🤽🏾‍♂ man playing water polo: medium-dark skin tone +1F93D 1F3FF 200D 2642 FE0F ; fully-qualified # 🤽🏿‍♂️ man playing water polo: dark skin tone +1F93D 1F3FF 200D 2642 ; minimally-qualified # 🤽🏿‍♂ man playing water polo: dark skin tone +1F93D 200D 2640 FE0F ; fully-qualified # 🤽‍♀️ woman playing water polo +1F93D 200D 2640 ; minimally-qualified # 🤽‍♀ woman playing water polo +1F93D 1F3FB 200D 2640 FE0F ; fully-qualified # 🤽🏻‍♀️ woman playing water polo: light skin tone +1F93D 1F3FB 200D 2640 ; minimally-qualified # 🤽🏻‍♀ woman playing water polo: light skin tone +1F93D 1F3FC 200D 2640 FE0F ; fully-qualified # 🤽🏼‍♀️ woman playing water polo: medium-light skin tone +1F93D 1F3FC 200D 2640 ; minimally-qualified # 🤽🏼‍♀ woman playing water polo: medium-light skin tone +1F93D 1F3FD 200D 2640 FE0F ; fully-qualified # 🤽🏽‍♀️ woman playing water polo: medium skin tone +1F93D 1F3FD 200D 2640 ; minimally-qualified # 🤽🏽‍♀ woman playing water polo: medium skin tone +1F93D 1F3FE 200D 2640 FE0F ; fully-qualified # 🤽🏾‍♀️ woman playing water polo: medium-dark skin tone +1F93D 1F3FE 200D 2640 ; minimally-qualified # 🤽🏾‍♀ woman playing water polo: medium-dark skin tone +1F93D 1F3FF 200D 2640 FE0F ; fully-qualified # 🤽🏿‍♀️ woman playing water polo: dark skin tone +1F93D 1F3FF 200D 2640 ; minimally-qualified # 🤽🏿‍♀ woman playing water polo: dark skin tone +1F93E ; fully-qualified # 🤾 person playing handball +1F93E 1F3FB ; fully-qualified # 🤾🏻 person playing handball: light skin tone +1F93E 1F3FC ; fully-qualified # 🤾🏼 person playing handball: medium-light skin tone +1F93E 1F3FD ; fully-qualified # 🤾🏽 person playing handball: medium skin tone +1F93E 1F3FE ; fully-qualified # 🤾🏾 person playing handball: medium-dark skin tone +1F93E 1F3FF ; fully-qualified # 🤾🏿 person playing handball: dark skin tone +1F93E 200D 2642 FE0F ; fully-qualified # 🤾‍♂️ man playing handball +1F93E 200D 2642 ; minimally-qualified # 🤾‍♂ man playing handball +1F93E 1F3FB 200D 2642 FE0F ; fully-qualified # 🤾🏻‍♂️ man playing handball: light skin tone +1F93E 1F3FB 200D 2642 ; minimally-qualified # 🤾🏻‍♂ man playing handball: light skin tone +1F93E 1F3FC 200D 2642 FE0F ; fully-qualified # 🤾🏼‍♂️ man playing handball: medium-light skin tone +1F93E 1F3FC 200D 2642 ; minimally-qualified # 🤾🏼‍♂ man playing handball: medium-light skin tone +1F93E 1F3FD 200D 2642 FE0F ; fully-qualified # 🤾🏽‍♂️ man playing handball: medium skin tone +1F93E 1F3FD 200D 2642 ; minimally-qualified # 🤾🏽‍♂ man playing handball: medium skin tone +1F93E 1F3FE 200D 2642 FE0F ; fully-qualified # 🤾🏾‍♂️ man playing handball: medium-dark skin tone +1F93E 1F3FE 200D 2642 ; minimally-qualified # 🤾🏾‍♂ man playing handball: medium-dark skin tone +1F93E 1F3FF 200D 2642 FE0F ; fully-qualified # 🤾🏿‍♂️ man playing handball: dark skin tone +1F93E 1F3FF 200D 2642 ; minimally-qualified # 🤾🏿‍♂ man playing handball: dark skin tone +1F93E 200D 2640 FE0F ; fully-qualified # 🤾‍♀️ woman playing handball +1F93E 200D 2640 ; minimally-qualified # 🤾‍♀ woman playing handball +1F93E 1F3FB 200D 2640 FE0F ; fully-qualified # 🤾🏻‍♀️ woman playing handball: light skin tone +1F93E 1F3FB 200D 2640 ; minimally-qualified # 🤾🏻‍♀ woman playing handball: light skin tone +1F93E 1F3FC 200D 2640 FE0F ; fully-qualified # 🤾🏼‍♀️ woman playing handball: medium-light skin tone +1F93E 1F3FC 200D 2640 ; minimally-qualified # 🤾🏼‍♀ woman playing handball: medium-light skin tone +1F93E 1F3FD 200D 2640 FE0F ; fully-qualified # 🤾🏽‍♀️ woman playing handball: medium skin tone +1F93E 1F3FD 200D 2640 ; minimally-qualified # 🤾🏽‍♀ woman playing handball: medium skin tone +1F93E 1F3FE 200D 2640 FE0F ; fully-qualified # 🤾🏾‍♀️ woman playing handball: medium-dark skin tone +1F93E 1F3FE 200D 2640 ; minimally-qualified # 🤾🏾‍♀ woman playing handball: medium-dark skin tone +1F93E 1F3FF 200D 2640 FE0F ; fully-qualified # 🤾🏿‍♀️ woman playing handball: dark skin tone +1F93E 1F3FF 200D 2640 ; minimally-qualified # 🤾🏿‍♀ woman playing handball: dark skin tone +1F939 ; fully-qualified # 🤹 person juggling +1F939 1F3FB ; fully-qualified # 🤹🏻 person juggling: light skin tone +1F939 1F3FC ; fully-qualified # 🤹🏼 person juggling: medium-light skin tone +1F939 1F3FD ; fully-qualified # 🤹🏽 person juggling: medium skin tone +1F939 1F3FE ; fully-qualified # 🤹🏾 person juggling: medium-dark skin tone +1F939 1F3FF ; fully-qualified # 🤹🏿 person juggling: dark skin tone +1F939 200D 2642 FE0F ; fully-qualified # 🤹‍♂️ man juggling +1F939 200D 2642 ; minimally-qualified # 🤹‍♂ man juggling +1F939 1F3FB 200D 2642 FE0F ; fully-qualified # 🤹🏻‍♂️ man juggling: light skin tone +1F939 1F3FB 200D 2642 ; minimally-qualified # 🤹🏻‍♂ man juggling: light skin tone +1F939 1F3FC 200D 2642 FE0F ; fully-qualified # 🤹🏼‍♂️ man juggling: medium-light skin tone +1F939 1F3FC 200D 2642 ; minimally-qualified # 🤹🏼‍♂ man juggling: medium-light skin tone +1F939 1F3FD 200D 2642 FE0F ; fully-qualified # 🤹🏽‍♂️ man juggling: medium skin tone +1F939 1F3FD 200D 2642 ; minimally-qualified # 🤹🏽‍♂ man juggling: medium skin tone +1F939 1F3FE 200D 2642 FE0F ; fully-qualified # 🤹🏾‍♂️ man juggling: medium-dark skin tone +1F939 1F3FE 200D 2642 ; minimally-qualified # 🤹🏾‍♂ man juggling: medium-dark skin tone +1F939 1F3FF 200D 2642 FE0F ; fully-qualified # 🤹🏿‍♂️ man juggling: dark skin tone +1F939 1F3FF 200D 2642 ; minimally-qualified # 🤹🏿‍♂ man juggling: dark skin tone +1F939 200D 2640 FE0F ; fully-qualified # 🤹‍♀️ woman juggling +1F939 200D 2640 ; minimally-qualified # 🤹‍♀ woman juggling +1F939 1F3FB 200D 2640 FE0F ; fully-qualified # 🤹🏻‍♀️ woman juggling: light skin tone +1F939 1F3FB 200D 2640 ; minimally-qualified # 🤹🏻‍♀ woman juggling: light skin tone +1F939 1F3FC 200D 2640 FE0F ; fully-qualified # 🤹🏼‍♀️ woman juggling: medium-light skin tone +1F939 1F3FC 200D 2640 ; minimally-qualified # 🤹🏼‍♀ woman juggling: medium-light skin tone +1F939 1F3FD 200D 2640 FE0F ; fully-qualified # 🤹🏽‍♀️ woman juggling: medium skin tone +1F939 1F3FD 200D 2640 ; minimally-qualified # 🤹🏽‍♀ woman juggling: medium skin tone +1F939 1F3FE 200D 2640 FE0F ; fully-qualified # 🤹🏾‍♀️ woman juggling: medium-dark skin tone +1F939 1F3FE 200D 2640 ; minimally-qualified # 🤹🏾‍♀ woman juggling: medium-dark skin tone +1F939 1F3FF 200D 2640 FE0F ; fully-qualified # 🤹🏿‍♀️ woman juggling: dark skin tone +1F939 1F3FF 200D 2640 ; minimally-qualified # 🤹🏿‍♀ woman juggling: dark skin tone + +# subgroup: person-resting +1F9D8 ; fully-qualified # 🧘 person in lotus position +1F9D8 1F3FB ; fully-qualified # 🧘🏻 person in lotus position: light skin tone +1F9D8 1F3FC ; fully-qualified # 🧘🏼 person in lotus position: medium-light skin tone +1F9D8 1F3FD ; fully-qualified # 🧘🏽 person in lotus position: medium skin tone +1F9D8 1F3FE ; fully-qualified # 🧘🏾 person in lotus position: medium-dark skin tone +1F9D8 1F3FF ; fully-qualified # 🧘🏿 person in lotus position: dark skin tone +1F9D8 200D 2642 FE0F ; fully-qualified # 🧘‍♂️ man in lotus position +1F9D8 200D 2642 ; minimally-qualified # 🧘‍♂ man in lotus position +1F9D8 1F3FB 200D 2642 FE0F ; fully-qualified # 🧘🏻‍♂️ man in lotus position: light skin tone +1F9D8 1F3FB 200D 2642 ; minimally-qualified # 🧘🏻‍♂ man in lotus position: light skin tone +1F9D8 1F3FC 200D 2642 FE0F ; fully-qualified # 🧘🏼‍♂️ man in lotus position: medium-light skin tone +1F9D8 1F3FC 200D 2642 ; minimally-qualified # 🧘🏼‍♂ man in lotus position: medium-light skin tone +1F9D8 1F3FD 200D 2642 FE0F ; fully-qualified # 🧘🏽‍♂️ man in lotus position: medium skin tone +1F9D8 1F3FD 200D 2642 ; minimally-qualified # 🧘🏽‍♂ man in lotus position: medium skin tone +1F9D8 1F3FE 200D 2642 FE0F ; fully-qualified # 🧘🏾‍♂️ man in lotus position: medium-dark skin tone +1F9D8 1F3FE 200D 2642 ; minimally-qualified # 🧘🏾‍♂ man in lotus position: medium-dark skin tone +1F9D8 1F3FF 200D 2642 FE0F ; fully-qualified # 🧘🏿‍♂️ man in lotus position: dark skin tone +1F9D8 1F3FF 200D 2642 ; minimally-qualified # 🧘🏿‍♂ man in lotus position: dark skin tone +1F9D8 200D 2640 FE0F ; fully-qualified # 🧘‍♀️ woman in lotus position +1F9D8 200D 2640 ; minimally-qualified # 🧘‍♀ woman in lotus position +1F9D8 1F3FB 200D 2640 FE0F ; fully-qualified # 🧘🏻‍♀️ woman in lotus position: light skin tone +1F9D8 1F3FB 200D 2640 ; minimally-qualified # 🧘🏻‍♀ woman in lotus position: light skin tone +1F9D8 1F3FC 200D 2640 FE0F ; fully-qualified # 🧘🏼‍♀️ woman in lotus position: medium-light skin tone +1F9D8 1F3FC 200D 2640 ; minimally-qualified # 🧘🏼‍♀ woman in lotus position: medium-light skin tone +1F9D8 1F3FD 200D 2640 FE0F ; fully-qualified # 🧘🏽‍♀️ woman in lotus position: medium skin tone +1F9D8 1F3FD 200D 2640 ; minimally-qualified # 🧘🏽‍♀ woman in lotus position: medium skin tone +1F9D8 1F3FE 200D 2640 FE0F ; fully-qualified # 🧘🏾‍♀️ woman in lotus position: medium-dark skin tone +1F9D8 1F3FE 200D 2640 ; minimally-qualified # 🧘🏾‍♀ woman in lotus position: medium-dark skin tone +1F9D8 1F3FF 200D 2640 FE0F ; fully-qualified # 🧘🏿‍♀️ woman in lotus position: dark skin tone +1F9D8 1F3FF 200D 2640 ; minimally-qualified # 🧘🏿‍♀ woman in lotus position: dark skin tone +1F6C0 ; fully-qualified # 🛀 person taking bath +1F6C0 1F3FB ; fully-qualified # 🛀🏻 person taking bath: light skin tone +1F6C0 1F3FC ; fully-qualified # 🛀🏼 person taking bath: medium-light skin tone +1F6C0 1F3FD ; fully-qualified # 🛀🏽 person taking bath: medium skin tone +1F6C0 1F3FE ; fully-qualified # 🛀🏾 person taking bath: medium-dark skin tone +1F6C0 1F3FF ; fully-qualified # 🛀🏿 person taking bath: dark skin tone +1F6CC ; fully-qualified # 🛌 person in bed +1F6CC 1F3FB ; fully-qualified # 🛌🏻 person in bed: light skin tone +1F6CC 1F3FC ; fully-qualified # 🛌🏼 person in bed: medium-light skin tone +1F6CC 1F3FD ; fully-qualified # 🛌🏽 person in bed: medium skin tone +1F6CC 1F3FE ; fully-qualified # 🛌🏾 person in bed: medium-dark skin tone +1F6CC 1F3FF ; fully-qualified # 🛌🏿 person in bed: dark skin tone + +# subgroup: family +1F9D1 200D 1F91D 200D 1F9D1 ; fully-qualified # 🧑‍🤝‍🧑 people holding hands +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏻‍🤝‍🧑🏻 people holding hands: light skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍🤝‍🧑🏻 people holding hands: medium-light skin tone, light skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏼‍🤝‍🧑🏼 people holding hands: medium-light skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍🤝‍🧑🏻 people holding hands: medium skin tone, light skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍🤝‍🧑🏼 people holding hands: medium skin tone, medium-light skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏽‍🤝‍🧑🏽 people holding hands: medium skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍🤝‍🧑🏻 people holding hands: medium-dark skin tone, light skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍🤝‍🧑🏼 people holding hands: medium-dark skin tone, medium-light skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍🤝‍🧑🏽 people holding hands: medium-dark skin tone, medium skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏾‍🤝‍🧑🏾 people holding hands: medium-dark skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍🤝‍🧑🏻 people holding hands: dark skin tone, light skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍🤝‍🧑🏼 people holding hands: dark skin tone, medium-light skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍🤝‍🧑🏽 people holding hands: dark skin tone, medium skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍🤝‍🧑🏾 people holding hands: dark skin tone, medium-dark skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏿‍🤝‍🧑🏿 people holding hands: dark skin tone +1F46D ; fully-qualified # 👭 women holding hands +1F46D 1F3FB ; fully-qualified # 👭🏻 women holding hands: light skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍🤝‍👩🏻 women holding hands: medium-light skin tone, light skin tone +1F46D 1F3FC ; fully-qualified # 👭🏼 women holding hands: medium-light skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍🤝‍👩🏻 women holding hands: medium skin tone, light skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍🤝‍👩🏼 women holding hands: medium skin tone, medium-light skin tone +1F46D 1F3FD ; fully-qualified # 👭🏽 women holding hands: medium skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍🤝‍👩🏻 women holding hands: medium-dark skin tone, light skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍🤝‍👩🏼 women holding hands: medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍🤝‍👩🏽 women holding hands: medium-dark skin tone, medium skin tone +1F46D 1F3FE ; fully-qualified # 👭🏾 women holding hands: medium-dark skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍🤝‍👩🏻 women holding hands: dark skin tone, light skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍🤝‍👩🏼 women holding hands: dark skin tone, medium-light skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍🤝‍👩🏽 women holding hands: dark skin tone, medium skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍🤝‍👩🏾 women holding hands: dark skin tone, medium-dark skin tone +1F46D 1F3FF ; fully-qualified # 👭🏿 women holding hands: dark skin tone +1F46B ; fully-qualified # 👫 woman and man holding hands +1F46B 1F3FB ; fully-qualified # 👫🏻 woman and man holding hands: light skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍🤝‍👨🏼 woman and man holding hands: light skin tone, medium-light skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍🤝‍👨🏽 woman and man holding hands: light skin tone, medium skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍🤝‍👨🏾 woman and man holding hands: light skin tone, medium-dark skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍🤝‍👨🏿 woman and man holding hands: light skin tone, dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍🤝‍👨🏻 woman and man holding hands: medium-light skin tone, light skin tone +1F46B 1F3FC ; fully-qualified # 👫🏼 woman and man holding hands: medium-light skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍🤝‍👨🏽 woman and man holding hands: medium-light skin tone, medium skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍🤝‍👨🏾 woman and man holding hands: medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍🤝‍👨🏿 woman and man holding hands: medium-light skin tone, dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍🤝‍👨🏻 woman and man holding hands: medium skin tone, light skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍🤝‍👨🏼 woman and man holding hands: medium skin tone, medium-light skin tone +1F46B 1F3FD ; fully-qualified # 👫🏽 woman and man holding hands: medium skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍🤝‍👨🏾 woman and man holding hands: medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍🤝‍👨🏿 woman and man holding hands: medium skin tone, dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍🤝‍👨🏻 woman and man holding hands: medium-dark skin tone, light skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍🤝‍👨🏼 woman and man holding hands: medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍🤝‍👨🏽 woman and man holding hands: medium-dark skin tone, medium skin tone +1F46B 1F3FE ; fully-qualified # 👫🏾 woman and man holding hands: medium-dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍🤝‍👨🏿 woman and man holding hands: medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍🤝‍👨🏻 woman and man holding hands: dark skin tone, light skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍🤝‍👨🏼 woman and man holding hands: dark skin tone, medium-light skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍🤝‍👨🏽 woman and man holding hands: dark skin tone, medium skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍🤝‍👨🏾 woman and man holding hands: dark skin tone, medium-dark skin tone +1F46B 1F3FF ; fully-qualified # 👫🏿 woman and man holding hands: dark skin tone +1F46C ; fully-qualified # 👬 men holding hands +1F46C 1F3FB ; fully-qualified # 👬🏻 men holding hands: light skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍🤝‍👨🏻 men holding hands: medium-light skin tone, light skin tone +1F46C 1F3FC ; fully-qualified # 👬🏼 men holding hands: medium-light skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍🤝‍👨🏻 men holding hands: medium skin tone, light skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍🤝‍👨🏼 men holding hands: medium skin tone, medium-light skin tone +1F46C 1F3FD ; fully-qualified # 👬🏽 men holding hands: medium skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍🤝‍👨🏻 men holding hands: medium-dark skin tone, light skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍🤝‍👨🏼 men holding hands: medium-dark skin tone, medium-light skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍🤝‍👨🏽 men holding hands: medium-dark skin tone, medium skin tone +1F46C 1F3FE ; fully-qualified # 👬🏾 men holding hands: medium-dark skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍🤝‍👨🏻 men holding hands: dark skin tone, light skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍🤝‍👨🏼 men holding hands: dark skin tone, medium-light skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍🤝‍👨🏽 men holding hands: dark skin tone, medium skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍🤝‍👨🏾 men holding hands: dark skin tone, medium-dark skin tone +1F46C 1F3FF ; fully-qualified # 👬🏿 men holding hands: dark skin tone +1F48F ; fully-qualified # 💏 kiss +1F469 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👩‍❤️‍💋‍👨 kiss: woman, man +1F469 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👩‍❤‍💋‍👨 kiss: woman, man +1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👨‍❤️‍💋‍👨 kiss: man, man +1F468 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👨‍❤‍💋‍👨 kiss: man, man +1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 ; fully-qualified # 👩‍❤️‍💋‍👩 kiss: woman, woman +1F469 200D 2764 200D 1F48B 200D 1F469 ; minimally-qualified # 👩‍❤‍💋‍👩 kiss: woman, woman +1F491 ; fully-qualified # 💑 couple with heart +1F469 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👩‍❤️‍👨 couple with heart: woman, man +1F469 200D 2764 200D 1F468 ; minimally-qualified # 👩‍❤‍👨 couple with heart: woman, man +1F468 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👨‍❤️‍👨 couple with heart: man, man +1F468 200D 2764 200D 1F468 ; minimally-qualified # 👨‍❤‍👨 couple with heart: man, man +1F469 200D 2764 FE0F 200D 1F469 ; fully-qualified # 👩‍❤️‍👩 couple with heart: woman, woman +1F469 200D 2764 200D 1F469 ; minimally-qualified # 👩‍❤‍👩 couple with heart: woman, woman +1F46A ; fully-qualified # 👪 family +1F468 200D 1F469 200D 1F466 ; fully-qualified # 👨‍👩‍👦 family: man, woman, boy +1F468 200D 1F469 200D 1F467 ; fully-qualified # 👨‍👩‍👧 family: man, woman, girl +1F468 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👩‍👧‍👦 family: man, woman, girl, boy +1F468 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👩‍👦‍👦 family: man, woman, boy, boy +1F468 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👩‍👧‍👧 family: man, woman, girl, girl +1F468 200D 1F468 200D 1F466 ; fully-qualified # 👨‍👨‍👦 family: man, man, boy +1F468 200D 1F468 200D 1F467 ; fully-qualified # 👨‍👨‍👧 family: man, man, girl +1F468 200D 1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👨‍👧‍👦 family: man, man, girl, boy +1F468 200D 1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👨‍👦‍👦 family: man, man, boy, boy +1F468 200D 1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👨‍👧‍👧 family: man, man, girl, girl +1F469 200D 1F469 200D 1F466 ; fully-qualified # 👩‍👩‍👦 family: woman, woman, boy +1F469 200D 1F469 200D 1F467 ; fully-qualified # 👩‍👩‍👧 family: woman, woman, girl +1F469 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👩‍👧‍👦 family: woman, woman, girl, boy +1F469 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👩‍👦‍👦 family: woman, woman, boy, boy +1F469 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👩‍👧‍👧 family: woman, woman, girl, girl +1F468 200D 1F466 ; fully-qualified # 👨‍👦 family: man, boy +1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👦‍👦 family: man, boy, boy +1F468 200D 1F467 ; fully-qualified # 👨‍👧 family: man, girl +1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👧‍👦 family: man, girl, boy +1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👧‍👧 family: man, girl, girl +1F469 200D 1F466 ; fully-qualified # 👩‍👦 family: woman, boy +1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👦‍👦 family: woman, boy, boy +1F469 200D 1F467 ; fully-qualified # 👩‍👧 family: woman, girl +1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👧‍👦 family: woman, girl, boy +1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👧‍👧 family: woman, girl, girl + +# subgroup: person-symbol +1F5E3 FE0F ; fully-qualified # 🗣️ speaking head +1F5E3 ; unqualified # 🗣 speaking head +1F464 ; fully-qualified # 👤 bust in silhouette +1F465 ; fully-qualified # 👥 busts in silhouette +1F463 ; fully-qualified # 👣 footprints + +# People & Body subtotal: 2212 +# People & Body subtotal: 447 w/o modifiers + +# group: Component + +# subgroup: skin-tone +1F3FB ; component # 🏻 light skin tone +1F3FC ; component # 🏼 medium-light skin tone +1F3FD ; component # 🏽 medium skin tone +1F3FE ; component # 🏾 medium-dark skin tone +1F3FF ; component # 🏿 dark skin tone + +# subgroup: hair-style +1F9B0 ; component # 🦰 red hair +1F9B1 ; component # 🦱 curly hair +1F9B3 ; component # 🦳 white hair +1F9B2 ; component # 🦲 bald + +# Component subtotal: 9 +# Component subtotal: 4 w/o modifiers + +# group: Animals & Nature + +# subgroup: animal-mammal +1F435 ; fully-qualified # 🐵 monkey face +1F412 ; fully-qualified # 🐒 monkey +1F98D ; fully-qualified # 🦍 gorilla +1F9A7 ; fully-qualified # 🦧 orangutan +1F436 ; fully-qualified # 🐶 dog face +1F415 ; fully-qualified # 🐕 dog +1F9AE ; fully-qualified # 🦮 guide dog +1F415 200D 1F9BA ; fully-qualified # 🐕‍🦺 service dog +1F429 ; fully-qualified # 🐩 poodle +1F43A ; fully-qualified # 🐺 wolf +1F98A ; fully-qualified # 🦊 fox +1F99D ; fully-qualified # 🦝 raccoon +1F431 ; fully-qualified # 🐱 cat face +1F408 ; fully-qualified # 🐈 cat +1F981 ; fully-qualified # 🦁 lion +1F42F ; fully-qualified # 🐯 tiger face +1F405 ; fully-qualified # 🐅 tiger +1F406 ; fully-qualified # 🐆 leopard +1F434 ; fully-qualified # 🐴 horse face +1F40E ; fully-qualified # 🐎 horse +1F984 ; fully-qualified # 🦄 unicorn +1F993 ; fully-qualified # 🦓 zebra +1F98C ; fully-qualified # 🦌 deer +1F42E ; fully-qualified # 🐮 cow face +1F402 ; fully-qualified # 🐂 ox +1F403 ; fully-qualified # 🐃 water buffalo +1F404 ; fully-qualified # 🐄 cow +1F437 ; fully-qualified # 🐷 pig face +1F416 ; fully-qualified # 🐖 pig +1F417 ; fully-qualified # 🐗 boar +1F43D ; fully-qualified # 🐽 pig nose +1F40F ; fully-qualified # 🐏 ram +1F411 ; fully-qualified # 🐑 ewe +1F410 ; fully-qualified # 🐐 goat +1F42A ; fully-qualified # 🐪 camel +1F42B ; fully-qualified # 🐫 two-hump camel +1F999 ; fully-qualified # 🦙 llama +1F992 ; fully-qualified # 🦒 giraffe +1F418 ; fully-qualified # 🐘 elephant +1F98F ; fully-qualified # 🦏 rhinoceros +1F99B ; fully-qualified # 🦛 hippopotamus +1F42D ; fully-qualified # 🐭 mouse face +1F401 ; fully-qualified # 🐁 mouse +1F400 ; fully-qualified # 🐀 rat +1F439 ; fully-qualified # 🐹 hamster +1F430 ; fully-qualified # 🐰 rabbit face +1F407 ; fully-qualified # 🐇 rabbit +1F43F FE0F ; fully-qualified # 🐿️ chipmunk +1F43F ; unqualified # 🐿 chipmunk +1F994 ; fully-qualified # 🦔 hedgehog +1F987 ; fully-qualified # 🦇 bat +1F43B ; fully-qualified # 🐻 bear +1F428 ; fully-qualified # 🐨 koala +1F43C ; fully-qualified # 🐼 panda +1F9A5 ; fully-qualified # 🦥 sloth +1F9A6 ; fully-qualified # 🦦 otter +1F9A8 ; fully-qualified # 🦨 skunk +1F998 ; fully-qualified # 🦘 kangaroo +1F9A1 ; fully-qualified # 🦡 badger +1F43E ; fully-qualified # 🐾 paw prints + +# subgroup: animal-bird +1F983 ; fully-qualified # 🦃 turkey +1F414 ; fully-qualified # 🐔 chicken +1F413 ; fully-qualified # 🐓 rooster +1F423 ; fully-qualified # 🐣 hatching chick +1F424 ; fully-qualified # 🐤 baby chick +1F425 ; fully-qualified # 🐥 front-facing baby chick +1F426 ; fully-qualified # 🐦 bird +1F427 ; fully-qualified # 🐧 penguin +1F54A FE0F ; fully-qualified # 🕊️ dove +1F54A ; unqualified # 🕊 dove +1F985 ; fully-qualified # 🦅 eagle +1F986 ; fully-qualified # 🦆 duck +1F9A2 ; fully-qualified # 🦢 swan +1F989 ; fully-qualified # 🦉 owl +1F9A9 ; fully-qualified # 🦩 flamingo +1F99A ; fully-qualified # 🦚 peacock +1F99C ; fully-qualified # 🦜 parrot + +# subgroup: animal-amphibian +1F438 ; fully-qualified # 🐸 frog + +# subgroup: animal-reptile +1F40A ; fully-qualified # 🐊 crocodile +1F422 ; fully-qualified # 🐢 turtle +1F98E ; fully-qualified # 🦎 lizard +1F40D ; fully-qualified # 🐍 snake +1F432 ; fully-qualified # 🐲 dragon face +1F409 ; fully-qualified # 🐉 dragon +1F995 ; fully-qualified # 🦕 sauropod +1F996 ; fully-qualified # 🦖 T-Rex + +# subgroup: animal-marine +1F433 ; fully-qualified # 🐳 spouting whale +1F40B ; fully-qualified # 🐋 whale +1F42C ; fully-qualified # 🐬 dolphin +1F41F ; fully-qualified # 🐟 fish +1F420 ; fully-qualified # 🐠 tropical fish +1F421 ; fully-qualified # 🐡 blowfish +1F988 ; fully-qualified # 🦈 shark +1F419 ; fully-qualified # 🐙 octopus +1F41A ; fully-qualified # 🐚 spiral shell + +# subgroup: animal-bug +1F40C ; fully-qualified # 🐌 snail +1F98B ; fully-qualified # 🦋 butterfly +1F41B ; fully-qualified # 🐛 bug +1F41C ; fully-qualified # 🐜 ant +1F41D ; fully-qualified # 🐝 honeybee +1F41E ; fully-qualified # 🐞 lady beetle +1F997 ; fully-qualified # 🦗 cricket +1F577 FE0F ; fully-qualified # 🕷️ spider +1F577 ; unqualified # 🕷 spider +1F578 FE0F ; fully-qualified # 🕸️ spider web +1F578 ; unqualified # 🕸 spider web +1F982 ; fully-qualified # 🦂 scorpion +1F99F ; fully-qualified # 🦟 mosquito +1F9A0 ; fully-qualified # 🦠 microbe + +# subgroup: plant-flower +1F490 ; fully-qualified # 💐 bouquet +1F338 ; fully-qualified # 🌸 cherry blossom +1F4AE ; fully-qualified # 💮 white flower +1F3F5 FE0F ; fully-qualified # 🏵️ rosette +1F3F5 ; unqualified # 🏵 rosette +1F339 ; fully-qualified # 🌹 rose +1F940 ; fully-qualified # 🥀 wilted flower +1F33A ; fully-qualified # 🌺 hibiscus +1F33B ; fully-qualified # 🌻 sunflower +1F33C ; fully-qualified # 🌼 blossom +1F337 ; fully-qualified # 🌷 tulip + +# subgroup: plant-other +1F331 ; fully-qualified # 🌱 seedling +1F332 ; fully-qualified # 🌲 evergreen tree +1F333 ; fully-qualified # 🌳 deciduous tree +1F334 ; fully-qualified # 🌴 palm tree +1F335 ; fully-qualified # 🌵 cactus +1F33E ; fully-qualified # 🌾 sheaf of rice +1F33F ; fully-qualified # 🌿 herb +2618 FE0F ; fully-qualified # ☘️ shamrock +2618 ; unqualified # ☘ shamrock +1F340 ; fully-qualified # 🍀 four leaf clover +1F341 ; fully-qualified # 🍁 maple leaf +1F342 ; fully-qualified # 🍂 fallen leaf +1F343 ; fully-qualified # 🍃 leaf fluttering in wind + +# Animals & Nature subtotal: 133 +# Animals & Nature subtotal: 133 w/o modifiers + +# group: Food & Drink + +# subgroup: food-fruit +1F347 ; fully-qualified # 🍇 grapes +1F348 ; fully-qualified # 🍈 melon +1F349 ; fully-qualified # 🍉 watermelon +1F34A ; fully-qualified # 🍊 tangerine +1F34B ; fully-qualified # 🍋 lemon +1F34C ; fully-qualified # 🍌 banana +1F34D ; fully-qualified # 🍍 pineapple +1F96D ; fully-qualified # 🥭 mango +1F34E ; fully-qualified # 🍎 red apple +1F34F ; fully-qualified # 🍏 green apple +1F350 ; fully-qualified # 🍐 pear +1F351 ; fully-qualified # 🍑 peach +1F352 ; fully-qualified # 🍒 cherries +1F353 ; fully-qualified # 🍓 strawberry +1F95D ; fully-qualified # 🥝 kiwi fruit +1F345 ; fully-qualified # 🍅 tomato +1F965 ; fully-qualified # 🥥 coconut + +# subgroup: food-vegetable +1F951 ; fully-qualified # 🥑 avocado +1F346 ; fully-qualified # 🍆 eggplant +1F954 ; fully-qualified # 🥔 potato +1F955 ; fully-qualified # 🥕 carrot +1F33D ; fully-qualified # 🌽 ear of corn +1F336 FE0F ; fully-qualified # 🌶️ hot pepper +1F336 ; unqualified # 🌶 hot pepper +1F952 ; fully-qualified # 🥒 cucumber +1F96C ; fully-qualified # 🥬 leafy green +1F966 ; fully-qualified # 🥦 broccoli +1F9C4 ; fully-qualified # 🧄 garlic +1F9C5 ; fully-qualified # 🧅 onion +1F344 ; fully-qualified # 🍄 mushroom +1F95C ; fully-qualified # 🥜 peanuts +1F330 ; fully-qualified # 🌰 chestnut + +# subgroup: food-prepared +1F35E ; fully-qualified # 🍞 bread +1F950 ; fully-qualified # 🥐 croissant +1F956 ; fully-qualified # 🥖 baguette bread +1F968 ; fully-qualified # 🥨 pretzel +1F96F ; fully-qualified # 🥯 bagel +1F95E ; fully-qualified # 🥞 pancakes +1F9C7 ; fully-qualified # 🧇 waffle +1F9C0 ; fully-qualified # 🧀 cheese wedge +1F356 ; fully-qualified # 🍖 meat on bone +1F357 ; fully-qualified # 🍗 poultry leg +1F969 ; fully-qualified # 🥩 cut of meat +1F953 ; fully-qualified # 🥓 bacon +1F354 ; fully-qualified # 🍔 hamburger +1F35F ; fully-qualified # 🍟 french fries +1F355 ; fully-qualified # 🍕 pizza +1F32D ; fully-qualified # 🌭 hot dog +1F96A ; fully-qualified # 🥪 sandwich +1F32E ; fully-qualified # 🌮 taco +1F32F ; fully-qualified # 🌯 burrito +1F959 ; fully-qualified # 🥙 stuffed flatbread +1F9C6 ; fully-qualified # 🧆 falafel +1F95A ; fully-qualified # 🥚 egg +1F373 ; fully-qualified # 🍳 cooking +1F958 ; fully-qualified # 🥘 shallow pan of food +1F372 ; fully-qualified # 🍲 pot of food +1F963 ; fully-qualified # 🥣 bowl with spoon +1F957 ; fully-qualified # 🥗 green salad +1F37F ; fully-qualified # 🍿 popcorn +1F9C8 ; fully-qualified # 🧈 butter +1F9C2 ; fully-qualified # 🧂 salt +1F96B ; fully-qualified # 🥫 canned food + +# subgroup: food-asian +1F371 ; fully-qualified # 🍱 bento box +1F358 ; fully-qualified # 🍘 rice cracker +1F359 ; fully-qualified # 🍙 rice ball +1F35A ; fully-qualified # 🍚 cooked rice +1F35B ; fully-qualified # 🍛 curry rice +1F35C ; fully-qualified # 🍜 steaming bowl +1F35D ; fully-qualified # 🍝 spaghetti +1F360 ; fully-qualified # 🍠 roasted sweet potato +1F362 ; fully-qualified # 🍢 oden +1F363 ; fully-qualified # 🍣 sushi +1F364 ; fully-qualified # 🍤 fried shrimp +1F365 ; fully-qualified # 🍥 fish cake with swirl +1F96E ; fully-qualified # 🥮 moon cake +1F361 ; fully-qualified # 🍡 dango +1F95F ; fully-qualified # 🥟 dumpling +1F960 ; fully-qualified # 🥠 fortune cookie +1F961 ; fully-qualified # 🥡 takeout box + +# subgroup: food-marine +1F980 ; fully-qualified # 🦀 crab +1F99E ; fully-qualified # 🦞 lobster +1F990 ; fully-qualified # 🦐 shrimp +1F991 ; fully-qualified # 🦑 squid +1F9AA ; fully-qualified # 🦪 oyster + +# subgroup: food-sweet +1F366 ; fully-qualified # 🍦 soft ice cream +1F367 ; fully-qualified # 🍧 shaved ice +1F368 ; fully-qualified # 🍨 ice cream +1F369 ; fully-qualified # 🍩 doughnut +1F36A ; fully-qualified # 🍪 cookie +1F382 ; fully-qualified # 🎂 birthday cake +1F370 ; fully-qualified # 🍰 shortcake +1F9C1 ; fully-qualified # 🧁 cupcake +1F967 ; fully-qualified # 🥧 pie +1F36B ; fully-qualified # 🍫 chocolate bar +1F36C ; fully-qualified # 🍬 candy +1F36D ; fully-qualified # 🍭 lollipop +1F36E ; fully-qualified # 🍮 custard +1F36F ; fully-qualified # 🍯 honey pot + +# subgroup: drink +1F37C ; fully-qualified # 🍼 baby bottle +1F95B ; fully-qualified # 🥛 glass of milk +2615 ; fully-qualified # ☕ hot beverage +1F375 ; fully-qualified # 🍵 teacup without handle +1F376 ; fully-qualified # 🍶 sake +1F37E ; fully-qualified # 🍾 bottle with popping cork +1F377 ; fully-qualified # 🍷 wine glass +1F378 ; fully-qualified # 🍸 cocktail glass +1F379 ; fully-qualified # 🍹 tropical drink +1F37A ; fully-qualified # 🍺 beer mug +1F37B ; fully-qualified # 🍻 clinking beer mugs +1F942 ; fully-qualified # 🥂 clinking glasses +1F943 ; fully-qualified # 🥃 tumbler glass +1F964 ; fully-qualified # 🥤 cup with straw +1F9C3 ; fully-qualified # 🧃 beverage box +1F9C9 ; fully-qualified # 🧉 mate +1F9CA ; fully-qualified # 🧊 ice cube + +# subgroup: dishware +1F962 ; fully-qualified # 🥢 chopsticks +1F37D FE0F ; fully-qualified # 🍽️ fork and knife with plate +1F37D ; unqualified # 🍽 fork and knife with plate +1F374 ; fully-qualified # 🍴 fork and knife +1F944 ; fully-qualified # 🥄 spoon +1F52A ; fully-qualified # 🔪 kitchen knife +1F3FA ; fully-qualified # 🏺 amphora + +# Food & Drink subtotal: 123 +# Food & Drink subtotal: 123 w/o modifiers + +# group: Travel & Places + +# subgroup: place-map +1F30D ; fully-qualified # 🌍 globe showing Europe-Africa +1F30E ; fully-qualified # 🌎 globe showing Americas +1F30F ; fully-qualified # 🌏 globe showing Asia-Australia +1F310 ; fully-qualified # 🌐 globe with meridians +1F5FA FE0F ; fully-qualified # 🗺️ world map +1F5FA ; unqualified # 🗺 world map +1F5FE ; fully-qualified # 🗾 map of Japan +1F9ED ; fully-qualified # 🧭 compass + +# subgroup: place-geographic +1F3D4 FE0F ; fully-qualified # 🏔️ snow-capped mountain +1F3D4 ; unqualified # 🏔 snow-capped mountain +26F0 FE0F ; fully-qualified # ⛰️ mountain +26F0 ; unqualified # ⛰ mountain +1F30B ; fully-qualified # 🌋 volcano +1F5FB ; fully-qualified # 🗻 mount fuji +1F3D5 FE0F ; fully-qualified # 🏕️ camping +1F3D5 ; unqualified # 🏕 camping +1F3D6 FE0F ; fully-qualified # 🏖️ beach with umbrella +1F3D6 ; unqualified # 🏖 beach with umbrella +1F3DC FE0F ; fully-qualified # 🏜️ desert +1F3DC ; unqualified # 🏜 desert +1F3DD FE0F ; fully-qualified # 🏝️ desert island +1F3DD ; unqualified # 🏝 desert island +1F3DE FE0F ; fully-qualified # 🏞️ national park +1F3DE ; unqualified # 🏞 national park + +# subgroup: place-building +1F3DF FE0F ; fully-qualified # 🏟️ stadium +1F3DF ; unqualified # 🏟 stadium +1F3DB FE0F ; fully-qualified # 🏛️ classical building +1F3DB ; unqualified # 🏛 classical building +1F3D7 FE0F ; fully-qualified # 🏗️ building construction +1F3D7 ; unqualified # 🏗 building construction +1F9F1 ; fully-qualified # 🧱 brick +1F3D8 FE0F ; fully-qualified # 🏘️ houses +1F3D8 ; unqualified # 🏘 houses +1F3DA FE0F ; fully-qualified # 🏚️ derelict house +1F3DA ; unqualified # 🏚 derelict house +1F3E0 ; fully-qualified # 🏠 house +1F3E1 ; fully-qualified # 🏡 house with garden +1F3E2 ; fully-qualified # 🏢 office building +1F3E3 ; fully-qualified # 🏣 Japanese post office +1F3E4 ; fully-qualified # 🏤 post office +1F3E5 ; fully-qualified # 🏥 hospital +1F3E6 ; fully-qualified # 🏦 bank +1F3E8 ; fully-qualified # 🏨 hotel +1F3E9 ; fully-qualified # 🏩 love hotel +1F3EA ; fully-qualified # 🏪 convenience store +1F3EB ; fully-qualified # 🏫 school +1F3EC ; fully-qualified # 🏬 department store +1F3ED ; fully-qualified # 🏭 factory +1F3EF ; fully-qualified # 🏯 Japanese castle +1F3F0 ; fully-qualified # 🏰 castle +1F492 ; fully-qualified # 💒 wedding +1F5FC ; fully-qualified # 🗼 Tokyo tower +1F5FD ; fully-qualified # 🗽 Statue of Liberty + +# subgroup: place-religious +26EA ; fully-qualified # ⛪ church +1F54C ; fully-qualified # 🕌 mosque +1F6D5 ; fully-qualified # 🛕 hindu temple +1F54D ; fully-qualified # 🕍 synagogue +26E9 FE0F ; fully-qualified # ⛩️ shinto shrine +26E9 ; unqualified # ⛩ shinto shrine +1F54B ; fully-qualified # 🕋 kaaba + +# subgroup: place-other +26F2 ; fully-qualified # ⛲ fountain +26FA ; fully-qualified # ⛺ tent +1F301 ; fully-qualified # 🌁 foggy +1F303 ; fully-qualified # 🌃 night with stars +1F3D9 FE0F ; fully-qualified # 🏙️ cityscape +1F3D9 ; unqualified # 🏙 cityscape +1F304 ; fully-qualified # 🌄 sunrise over mountains +1F305 ; fully-qualified # 🌅 sunrise +1F306 ; fully-qualified # 🌆 cityscape at dusk +1F307 ; fully-qualified # 🌇 sunset +1F309 ; fully-qualified # 🌉 bridge at night +2668 FE0F ; fully-qualified # ♨️ hot springs +2668 ; unqualified # ♨ hot springs +1F3A0 ; fully-qualified # 🎠 carousel horse +1F3A1 ; fully-qualified # 🎡 ferris wheel +1F3A2 ; fully-qualified # 🎢 roller coaster +1F488 ; fully-qualified # 💈 barber pole +1F3AA ; fully-qualified # 🎪 circus tent + +# subgroup: transport-ground +1F682 ; fully-qualified # 🚂 locomotive +1F683 ; fully-qualified # 🚃 railway car +1F684 ; fully-qualified # 🚄 high-speed train +1F685 ; fully-qualified # 🚅 bullet train +1F686 ; fully-qualified # 🚆 train +1F687 ; fully-qualified # 🚇 metro +1F688 ; fully-qualified # 🚈 light rail +1F689 ; fully-qualified # 🚉 station +1F68A ; fully-qualified # 🚊 tram +1F69D ; fully-qualified # 🚝 monorail +1F69E ; fully-qualified # 🚞 mountain railway +1F68B ; fully-qualified # 🚋 tram car +1F68C ; fully-qualified # 🚌 bus +1F68D ; fully-qualified # 🚍 oncoming bus +1F68E ; fully-qualified # 🚎 trolleybus +1F690 ; fully-qualified # 🚐 minibus +1F691 ; fully-qualified # 🚑 ambulance +1F692 ; fully-qualified # 🚒 fire engine +1F693 ; fully-qualified # 🚓 police car +1F694 ; fully-qualified # 🚔 oncoming police car +1F695 ; fully-qualified # 🚕 taxi +1F696 ; fully-qualified # 🚖 oncoming taxi +1F697 ; fully-qualified # 🚗 automobile +1F698 ; fully-qualified # 🚘 oncoming automobile +1F699 ; fully-qualified # 🚙 sport utility vehicle +1F69A ; fully-qualified # 🚚 delivery truck +1F69B ; fully-qualified # 🚛 articulated lorry +1F69C ; fully-qualified # 🚜 tractor +1F3CE FE0F ; fully-qualified # 🏎️ racing car +1F3CE ; unqualified # 🏎 racing car +1F3CD FE0F ; fully-qualified # 🏍️ motorcycle +1F3CD ; unqualified # 🏍 motorcycle +1F6F5 ; fully-qualified # 🛵 motor scooter +1F9BD ; fully-qualified # 🦽 manual wheelchair +1F9BC ; fully-qualified # 🦼 motorized wheelchair +1F6FA ; fully-qualified # 🛺 auto rickshaw +1F6B2 ; fully-qualified # 🚲 bicycle +1F6F4 ; fully-qualified # 🛴 kick scooter +1F6F9 ; fully-qualified # 🛹 skateboard +1F68F ; fully-qualified # 🚏 bus stop +1F6E3 FE0F ; fully-qualified # 🛣️ motorway +1F6E3 ; unqualified # 🛣 motorway +1F6E4 FE0F ; fully-qualified # 🛤️ railway track +1F6E4 ; unqualified # 🛤 railway track +1F6E2 FE0F ; fully-qualified # 🛢️ oil drum +1F6E2 ; unqualified # 🛢 oil drum +26FD ; fully-qualified # ⛽ fuel pump +1F6A8 ; fully-qualified # 🚨 police car light +1F6A5 ; fully-qualified # 🚥 horizontal traffic light +1F6A6 ; fully-qualified # 🚦 vertical traffic light +1F6D1 ; fully-qualified # 🛑 stop sign +1F6A7 ; fully-qualified # 🚧 construction + +# subgroup: transport-water +2693 ; fully-qualified # ⚓ anchor +26F5 ; fully-qualified # ⛵ sailboat +1F6F6 ; fully-qualified # 🛶 canoe +1F6A4 ; fully-qualified # 🚤 speedboat +1F6F3 FE0F ; fully-qualified # 🛳️ passenger ship +1F6F3 ; unqualified # 🛳 passenger ship +26F4 FE0F ; fully-qualified # ⛴️ ferry +26F4 ; unqualified # ⛴ ferry +1F6E5 FE0F ; fully-qualified # 🛥️ motor boat +1F6E5 ; unqualified # 🛥 motor boat +1F6A2 ; fully-qualified # 🚢 ship + +# subgroup: transport-air +2708 FE0F ; fully-qualified # ✈️ airplane +2708 ; unqualified # ✈ airplane +1F6E9 FE0F ; fully-qualified # 🛩️ small airplane +1F6E9 ; unqualified # 🛩 small airplane +1F6EB ; fully-qualified # 🛫 airplane departure +1F6EC ; fully-qualified # 🛬 airplane arrival +1FA82 ; fully-qualified # 🪂 parachute +1F4BA ; fully-qualified # 💺 seat +1F681 ; fully-qualified # 🚁 helicopter +1F69F ; fully-qualified # 🚟 suspension railway +1F6A0 ; fully-qualified # 🚠 mountain cableway +1F6A1 ; fully-qualified # 🚡 aerial tramway +1F6F0 FE0F ; fully-qualified # 🛰️ satellite +1F6F0 ; unqualified # 🛰 satellite +1F680 ; fully-qualified # 🚀 rocket +1F6F8 ; fully-qualified # 🛸 flying saucer + +# subgroup: hotel +1F6CE FE0F ; fully-qualified # 🛎️ bellhop bell +1F6CE ; unqualified # 🛎 bellhop bell +1F9F3 ; fully-qualified # 🧳 luggage + +# subgroup: time +231B ; fully-qualified # ⌛ hourglass done +23F3 ; fully-qualified # ⏳ hourglass not done +231A ; fully-qualified # ⌚ watch +23F0 ; fully-qualified # ⏰ alarm clock +23F1 FE0F ; fully-qualified # ⏱️ stopwatch +23F1 ; unqualified # ⏱ stopwatch +23F2 FE0F ; fully-qualified # ⏲️ timer clock +23F2 ; unqualified # ⏲ timer clock +1F570 FE0F ; fully-qualified # 🕰️ mantelpiece clock +1F570 ; unqualified # 🕰 mantelpiece clock +1F55B ; fully-qualified # 🕛 twelve o’clock +1F567 ; fully-qualified # 🕧 twelve-thirty +1F550 ; fully-qualified # 🕐 one o’clock +1F55C ; fully-qualified # 🕜 one-thirty +1F551 ; fully-qualified # 🕑 two o’clock +1F55D ; fully-qualified # 🕝 two-thirty +1F552 ; fully-qualified # 🕒 three o’clock +1F55E ; fully-qualified # 🕞 three-thirty +1F553 ; fully-qualified # 🕓 four o’clock +1F55F ; fully-qualified # 🕟 four-thirty +1F554 ; fully-qualified # 🕔 five o’clock +1F560 ; fully-qualified # 🕠 five-thirty +1F555 ; fully-qualified # 🕕 six o’clock +1F561 ; fully-qualified # 🕡 six-thirty +1F556 ; fully-qualified # 🕖 seven o’clock +1F562 ; fully-qualified # 🕢 seven-thirty +1F557 ; fully-qualified # 🕗 eight o’clock +1F563 ; fully-qualified # 🕣 eight-thirty +1F558 ; fully-qualified # 🕘 nine o’clock +1F564 ; fully-qualified # 🕤 nine-thirty +1F559 ; fully-qualified # 🕙 ten o’clock +1F565 ; fully-qualified # 🕥 ten-thirty +1F55A ; fully-qualified # 🕚 eleven o’clock +1F566 ; fully-qualified # 🕦 eleven-thirty + +# subgroup: sky & weather +1F311 ; fully-qualified # 🌑 new moon +1F312 ; fully-qualified # 🌒 waxing crescent moon +1F313 ; fully-qualified # 🌓 first quarter moon +1F314 ; fully-qualified # 🌔 waxing gibbous moon +1F315 ; fully-qualified # 🌕 full moon +1F316 ; fully-qualified # 🌖 waning gibbous moon +1F317 ; fully-qualified # 🌗 last quarter moon +1F318 ; fully-qualified # 🌘 waning crescent moon +1F319 ; fully-qualified # 🌙 crescent moon +1F31A ; fully-qualified # 🌚 new moon face +1F31B ; fully-qualified # 🌛 first quarter moon face +1F31C ; fully-qualified # 🌜 last quarter moon face +1F321 FE0F ; fully-qualified # 🌡️ thermometer +1F321 ; unqualified # 🌡 thermometer +2600 FE0F ; fully-qualified # ☀️ sun +2600 ; unqualified # ☀ sun +1F31D ; fully-qualified # 🌝 full moon face +1F31E ; fully-qualified # 🌞 sun with face +1FA90 ; fully-qualified # 🪐 ringed planet +2B50 ; fully-qualified # ⭐ star +1F31F ; fully-qualified # 🌟 glowing star +1F320 ; fully-qualified # 🌠 shooting star +1F30C ; fully-qualified # 🌌 milky way +2601 FE0F ; fully-qualified # ☁️ cloud +2601 ; unqualified # ☁ cloud +26C5 ; fully-qualified # ⛅ sun behind cloud +26C8 FE0F ; fully-qualified # ⛈️ cloud with lightning and rain +26C8 ; unqualified # ⛈ cloud with lightning and rain +1F324 FE0F ; fully-qualified # 🌤️ sun behind small cloud +1F324 ; unqualified # 🌤 sun behind small cloud +1F325 FE0F ; fully-qualified # 🌥️ sun behind large cloud +1F325 ; unqualified # 🌥 sun behind large cloud +1F326 FE0F ; fully-qualified # 🌦️ sun behind rain cloud +1F326 ; unqualified # 🌦 sun behind rain cloud +1F327 FE0F ; fully-qualified # 🌧️ cloud with rain +1F327 ; unqualified # 🌧 cloud with rain +1F328 FE0F ; fully-qualified # 🌨️ cloud with snow +1F328 ; unqualified # 🌨 cloud with snow +1F329 FE0F ; fully-qualified # 🌩️ cloud with lightning +1F329 ; unqualified # 🌩 cloud with lightning +1F32A FE0F ; fully-qualified # 🌪️ tornado +1F32A ; unqualified # 🌪 tornado +1F32B FE0F ; fully-qualified # 🌫️ fog +1F32B ; unqualified # 🌫 fog +1F32C FE0F ; fully-qualified # 🌬️ wind face +1F32C ; unqualified # 🌬 wind face +1F300 ; fully-qualified # 🌀 cyclone +1F308 ; fully-qualified # 🌈 rainbow +1F302 ; fully-qualified # 🌂 closed umbrella +2602 FE0F ; fully-qualified # ☂️ umbrella +2602 ; unqualified # ☂ umbrella +2614 ; fully-qualified # ☔ umbrella with rain drops +26F1 FE0F ; fully-qualified # ⛱️ umbrella on ground +26F1 ; unqualified # ⛱ umbrella on ground +26A1 ; fully-qualified # ⚡ high voltage +2744 FE0F ; fully-qualified # ❄️ snowflake +2744 ; unqualified # ❄ snowflake +2603 FE0F ; fully-qualified # ☃️ snowman +2603 ; unqualified # ☃ snowman +26C4 ; fully-qualified # ⛄ snowman without snow +2604 FE0F ; fully-qualified # ☄️ comet +2604 ; unqualified # ☄ comet +1F525 ; fully-qualified # 🔥 fire +1F4A7 ; fully-qualified # 💧 droplet +1F30A ; fully-qualified # 🌊 water wave + +# Travel & Places subtotal: 259 +# Travel & Places subtotal: 259 w/o modifiers + +# group: Activities + +# subgroup: event +1F383 ; fully-qualified # 🎃 jack-o-lantern +1F384 ; fully-qualified # 🎄 Christmas tree +1F386 ; fully-qualified # 🎆 fireworks +1F387 ; fully-qualified # 🎇 sparkler +1F9E8 ; fully-qualified # 🧨 firecracker +2728 ; fully-qualified # ✨ sparkles +1F388 ; fully-qualified # 🎈 balloon +1F389 ; fully-qualified # 🎉 party popper +1F38A ; fully-qualified # 🎊 confetti ball +1F38B ; fully-qualified # 🎋 tanabata tree +1F38D ; fully-qualified # 🎍 pine decoration +1F38E ; fully-qualified # 🎎 Japanese dolls +1F38F ; fully-qualified # 🎏 carp streamer +1F390 ; fully-qualified # 🎐 wind chime +1F391 ; fully-qualified # 🎑 moon viewing ceremony +1F9E7 ; fully-qualified # 🧧 red envelope +1F380 ; fully-qualified # 🎀 ribbon +1F381 ; fully-qualified # 🎁 wrapped gift +1F397 FE0F ; fully-qualified # 🎗️ reminder ribbon +1F397 ; unqualified # 🎗 reminder ribbon +1F39F FE0F ; fully-qualified # 🎟️ admission tickets +1F39F ; unqualified # 🎟 admission tickets +1F3AB ; fully-qualified # 🎫 ticket + +# subgroup: award-medal +1F396 FE0F ; fully-qualified # 🎖️ military medal +1F396 ; unqualified # 🎖 military medal +1F3C6 ; fully-qualified # 🏆 trophy +1F3C5 ; fully-qualified # 🏅 sports medal +1F947 ; fully-qualified # 🥇 1st place medal +1F948 ; fully-qualified # 🥈 2nd place medal +1F949 ; fully-qualified # 🥉 3rd place medal + +# subgroup: sport +26BD ; fully-qualified # ⚽ soccer ball +26BE ; fully-qualified # ⚾ baseball +1F94E ; fully-qualified # 🥎 softball +1F3C0 ; fully-qualified # 🏀 basketball +1F3D0 ; fully-qualified # 🏐 volleyball +1F3C8 ; fully-qualified # 🏈 american football +1F3C9 ; fully-qualified # 🏉 rugby football +1F3BE ; fully-qualified # 🎾 tennis +1F94F ; fully-qualified # 🥏 flying disc +1F3B3 ; fully-qualified # 🎳 bowling +1F3CF ; fully-qualified # 🏏 cricket game +1F3D1 ; fully-qualified # 🏑 field hockey +1F3D2 ; fully-qualified # 🏒 ice hockey +1F94D ; fully-qualified # 🥍 lacrosse +1F3D3 ; fully-qualified # 🏓 ping pong +1F3F8 ; fully-qualified # 🏸 badminton +1F94A ; fully-qualified # 🥊 boxing glove +1F94B ; fully-qualified # 🥋 martial arts uniform +1F945 ; fully-qualified # 🥅 goal net +26F3 ; fully-qualified # ⛳ flag in hole +26F8 FE0F ; fully-qualified # ⛸️ ice skate +26F8 ; unqualified # ⛸ ice skate +1F3A3 ; fully-qualified # 🎣 fishing pole +1F93F ; fully-qualified # 🤿 diving mask +1F3BD ; fully-qualified # 🎽 running shirt +1F3BF ; fully-qualified # 🎿 skis +1F6F7 ; fully-qualified # 🛷 sled +1F94C ; fully-qualified # 🥌 curling stone + +# subgroup: game +1F3AF ; fully-qualified # 🎯 direct hit +1FA80 ; fully-qualified # 🪀 yo-yo +1FA81 ; fully-qualified # 🪁 kite +1F3B1 ; fully-qualified # 🎱 pool 8 ball +1F52E ; fully-qualified # 🔮 crystal ball +1F9FF ; fully-qualified # 🧿 nazar amulet +1F3AE ; fully-qualified # 🎮 video game +1F579 FE0F ; fully-qualified # 🕹️ joystick +1F579 ; unqualified # 🕹 joystick +1F3B0 ; fully-qualified # 🎰 slot machine +1F3B2 ; fully-qualified # 🎲 game die +1F9E9 ; fully-qualified # 🧩 puzzle piece +1F9F8 ; fully-qualified # 🧸 teddy bear +2660 FE0F ; fully-qualified # ♠️ spade suit +2660 ; unqualified # ♠ spade suit +2665 FE0F ; fully-qualified # ♥️ heart suit +2665 ; unqualified # ♥ heart suit +2666 FE0F ; fully-qualified # ♦️ diamond suit +2666 ; unqualified # ♦ diamond suit +2663 FE0F ; fully-qualified # ♣️ club suit +2663 ; unqualified # ♣ club suit +265F FE0F ; fully-qualified # ♟️ chess pawn +265F ; unqualified # ♟ chess pawn +1F0CF ; fully-qualified # 🃏 joker +1F004 ; fully-qualified # 🀄 mahjong red dragon +1F3B4 ; fully-qualified # 🎴 flower playing cards + +# subgroup: arts & crafts +1F3AD ; fully-qualified # 🎭 performing arts +1F5BC FE0F ; fully-qualified # 🖼️ framed picture +1F5BC ; unqualified # 🖼 framed picture +1F3A8 ; fully-qualified # 🎨 artist palette +1F9F5 ; fully-qualified # 🧵 thread +1F9F6 ; fully-qualified # 🧶 yarn + +# Activities subtotal: 90 +# Activities subtotal: 90 w/o modifiers + +# group: Objects + +# subgroup: clothing +1F453 ; fully-qualified # 👓 glasses +1F576 FE0F ; fully-qualified # 🕶️ sunglasses +1F576 ; unqualified # 🕶 sunglasses +1F97D ; fully-qualified # 🥽 goggles +1F97C ; fully-qualified # 🥼 lab coat +1F9BA ; fully-qualified # 🦺 safety vest +1F454 ; fully-qualified # 👔 necktie +1F455 ; fully-qualified # 👕 t-shirt +1F456 ; fully-qualified # 👖 jeans +1F9E3 ; fully-qualified # 🧣 scarf +1F9E4 ; fully-qualified # 🧤 gloves +1F9E5 ; fully-qualified # 🧥 coat +1F9E6 ; fully-qualified # 🧦 socks +1F457 ; fully-qualified # 👗 dress +1F458 ; fully-qualified # 👘 kimono +1F97B ; fully-qualified # 🥻 sari +1FA71 ; fully-qualified # 🩱 one-piece swimsuit +1FA72 ; fully-qualified # 🩲 swim brief +1FA73 ; fully-qualified # 🩳 shorts +1F459 ; fully-qualified # 👙 bikini +1F45A ; fully-qualified # 👚 woman’s clothes +1F45B ; fully-qualified # 👛 purse +1F45C ; fully-qualified # 👜 handbag +1F45D ; fully-qualified # 👝 clutch bag +1F6CD FE0F ; fully-qualified # 🛍️ shopping bags +1F6CD ; unqualified # 🛍 shopping bags +1F392 ; fully-qualified # 🎒 backpack +1F45E ; fully-qualified # 👞 man’s shoe +1F45F ; fully-qualified # 👟 running shoe +1F97E ; fully-qualified # 🥾 hiking boot +1F97F ; fully-qualified # 🥿 flat shoe +1F460 ; fully-qualified # 👠 high-heeled shoe +1F461 ; fully-qualified # 👡 woman’s sandal +1FA70 ; fully-qualified # 🩰 ballet shoes +1F462 ; fully-qualified # 👢 woman’s boot +1F451 ; fully-qualified # 👑 crown +1F452 ; fully-qualified # 👒 woman’s hat +1F3A9 ; fully-qualified # 🎩 top hat +1F393 ; fully-qualified # 🎓 graduation cap +1F9E2 ; fully-qualified # 🧢 billed cap +26D1 FE0F ; fully-qualified # ⛑️ rescue worker’s helmet +26D1 ; unqualified # ⛑ rescue worker’s helmet +1F4FF ; fully-qualified # 📿 prayer beads +1F484 ; fully-qualified # 💄 lipstick +1F48D ; fully-qualified # 💍 ring +1F48E ; fully-qualified # 💎 gem stone + +# subgroup: sound +1F507 ; fully-qualified # 🔇 muted speaker +1F508 ; fully-qualified # 🔈 speaker low volume +1F509 ; fully-qualified # 🔉 speaker medium volume +1F50A ; fully-qualified # 🔊 speaker high volume +1F4E2 ; fully-qualified # 📢 loudspeaker +1F4E3 ; fully-qualified # 📣 megaphone +1F4EF ; fully-qualified # 📯 postal horn +1F514 ; fully-qualified # 🔔 bell +1F515 ; fully-qualified # 🔕 bell with slash + +# subgroup: music +1F3BC ; fully-qualified # 🎼 musical score +1F3B5 ; fully-qualified # 🎵 musical note +1F3B6 ; fully-qualified # 🎶 musical notes +1F399 FE0F ; fully-qualified # 🎙️ studio microphone +1F399 ; unqualified # 🎙 studio microphone +1F39A FE0F ; fully-qualified # 🎚️ level slider +1F39A ; unqualified # 🎚 level slider +1F39B FE0F ; fully-qualified # 🎛️ control knobs +1F39B ; unqualified # 🎛 control knobs +1F3A4 ; fully-qualified # 🎤 microphone +1F3A7 ; fully-qualified # 🎧 headphone +1F4FB ; fully-qualified # 📻 radio + +# subgroup: musical-instrument +1F3B7 ; fully-qualified # 🎷 saxophone +1F3B8 ; fully-qualified # 🎸 guitar +1F3B9 ; fully-qualified # 🎹 musical keyboard +1F3BA ; fully-qualified # 🎺 trumpet +1F3BB ; fully-qualified # 🎻 violin +1FA95 ; fully-qualified # 🪕 banjo +1F941 ; fully-qualified # 🥁 drum + +# subgroup: phone +1F4F1 ; fully-qualified # 📱 mobile phone +1F4F2 ; fully-qualified # 📲 mobile phone with arrow +260E FE0F ; fully-qualified # ☎️ telephone +260E ; unqualified # ☎ telephone +1F4DE ; fully-qualified # 📞 telephone receiver +1F4DF ; fully-qualified # 📟 pager +1F4E0 ; fully-qualified # 📠 fax machine + +# subgroup: computer +1F50B ; fully-qualified # 🔋 battery +1F50C ; fully-qualified # 🔌 electric plug +1F4BB ; fully-qualified # 💻 laptop computer +1F5A5 FE0F ; fully-qualified # 🖥️ desktop computer +1F5A5 ; unqualified # 🖥 desktop computer +1F5A8 FE0F ; fully-qualified # 🖨️ printer +1F5A8 ; unqualified # 🖨 printer +2328 FE0F ; fully-qualified # ⌨️ keyboard +2328 ; unqualified # ⌨ keyboard +1F5B1 FE0F ; fully-qualified # 🖱️ computer mouse +1F5B1 ; unqualified # 🖱 computer mouse +1F5B2 FE0F ; fully-qualified # 🖲️ trackball +1F5B2 ; unqualified # 🖲 trackball +1F4BD ; fully-qualified # 💽 computer disk +1F4BE ; fully-qualified # 💾 floppy disk +1F4BF ; fully-qualified # 💿 optical disk +1F4C0 ; fully-qualified # 📀 dvd +1F9EE ; fully-qualified # 🧮 abacus + +# subgroup: light & video +1F3A5 ; fully-qualified # 🎥 movie camera +1F39E FE0F ; fully-qualified # 🎞️ film frames +1F39E ; unqualified # 🎞 film frames +1F4FD FE0F ; fully-qualified # 📽️ film projector +1F4FD ; unqualified # 📽 film projector +1F3AC ; fully-qualified # 🎬 clapper board +1F4FA ; fully-qualified # 📺 television +1F4F7 ; fully-qualified # 📷 camera +1F4F8 ; fully-qualified # 📸 camera with flash +1F4F9 ; fully-qualified # 📹 video camera +1F4FC ; fully-qualified # 📼 videocassette +1F50D ; fully-qualified # 🔍 magnifying glass tilted left +1F50E ; fully-qualified # 🔎 magnifying glass tilted right +1F56F FE0F ; fully-qualified # 🕯️ candle +1F56F ; unqualified # 🕯 candle +1F4A1 ; fully-qualified # 💡 light bulb +1F526 ; fully-qualified # 🔦 flashlight +1F3EE ; fully-qualified # 🏮 red paper lantern +1FA94 ; fully-qualified # 🪔 diya lamp + +# subgroup: book-paper +1F4D4 ; fully-qualified # 📔 notebook with decorative cover +1F4D5 ; fully-qualified # 📕 closed book +1F4D6 ; fully-qualified # 📖 open book +1F4D7 ; fully-qualified # 📗 green book +1F4D8 ; fully-qualified # 📘 blue book +1F4D9 ; fully-qualified # 📙 orange book +1F4DA ; fully-qualified # 📚 books +1F4D3 ; fully-qualified # 📓 notebook +1F4D2 ; fully-qualified # 📒 ledger +1F4C3 ; fully-qualified # 📃 page with curl +1F4DC ; fully-qualified # 📜 scroll +1F4C4 ; fully-qualified # 📄 page facing up +1F4F0 ; fully-qualified # 📰 newspaper +1F5DE FE0F ; fully-qualified # 🗞️ rolled-up newspaper +1F5DE ; unqualified # 🗞 rolled-up newspaper +1F4D1 ; fully-qualified # 📑 bookmark tabs +1F516 ; fully-qualified # 🔖 bookmark +1F3F7 FE0F ; fully-qualified # 🏷️ label +1F3F7 ; unqualified # 🏷 label + +# subgroup: money +1F4B0 ; fully-qualified # 💰 money bag +1F4B4 ; fully-qualified # 💴 yen banknote +1F4B5 ; fully-qualified # 💵 dollar banknote +1F4B6 ; fully-qualified # 💶 euro banknote +1F4B7 ; fully-qualified # 💷 pound banknote +1F4B8 ; fully-qualified # 💸 money with wings +1F4B3 ; fully-qualified # 💳 credit card +1F9FE ; fully-qualified # 🧾 receipt +1F4B9 ; fully-qualified # 💹 chart increasing with yen +1F4B1 ; fully-qualified # 💱 currency exchange +1F4B2 ; fully-qualified # 💲 heavy dollar sign + +# subgroup: mail +2709 FE0F ; fully-qualified # ✉️ envelope +2709 ; unqualified # ✉ envelope +1F4E7 ; fully-qualified # 📧 e-mail +1F4E8 ; fully-qualified # 📨 incoming envelope +1F4E9 ; fully-qualified # 📩 envelope with arrow +1F4E4 ; fully-qualified # 📤 outbox tray +1F4E5 ; fully-qualified # 📥 inbox tray +1F4E6 ; fully-qualified # 📦 package +1F4EB ; fully-qualified # 📫 closed mailbox with raised flag +1F4EA ; fully-qualified # 📪 closed mailbox with lowered flag +1F4EC ; fully-qualified # 📬 open mailbox with raised flag +1F4ED ; fully-qualified # 📭 open mailbox with lowered flag +1F4EE ; fully-qualified # 📮 postbox +1F5F3 FE0F ; fully-qualified # 🗳️ ballot box with ballot +1F5F3 ; unqualified # 🗳 ballot box with ballot + +# subgroup: writing +270F FE0F ; fully-qualified # ✏️ pencil +270F ; unqualified # ✏ pencil +2712 FE0F ; fully-qualified # ✒️ black nib +2712 ; unqualified # ✒ black nib +1F58B FE0F ; fully-qualified # 🖋️ fountain pen +1F58B ; unqualified # 🖋 fountain pen +1F58A FE0F ; fully-qualified # 🖊️ pen +1F58A ; unqualified # 🖊 pen +1F58C FE0F ; fully-qualified # 🖌️ paintbrush +1F58C ; unqualified # 🖌 paintbrush +1F58D FE0F ; fully-qualified # 🖍️ crayon +1F58D ; unqualified # 🖍 crayon +1F4DD ; fully-qualified # 📝 memo + +# subgroup: office +1F4BC ; fully-qualified # 💼 briefcase +1F4C1 ; fully-qualified # 📁 file folder +1F4C2 ; fully-qualified # 📂 open file folder +1F5C2 FE0F ; fully-qualified # 🗂️ card index dividers +1F5C2 ; unqualified # 🗂 card index dividers +1F4C5 ; fully-qualified # 📅 calendar +1F4C6 ; fully-qualified # 📆 tear-off calendar +1F5D2 FE0F ; fully-qualified # 🗒️ spiral notepad +1F5D2 ; unqualified # 🗒 spiral notepad +1F5D3 FE0F ; fully-qualified # 🗓️ spiral calendar +1F5D3 ; unqualified # 🗓 spiral calendar +1F4C7 ; fully-qualified # 📇 card index +1F4C8 ; fully-qualified # 📈 chart increasing +1F4C9 ; fully-qualified # 📉 chart decreasing +1F4CA ; fully-qualified # 📊 bar chart +1F4CB ; fully-qualified # 📋 clipboard +1F4CC ; fully-qualified # 📌 pushpin +1F4CD ; fully-qualified # 📍 round pushpin +1F4CE ; fully-qualified # 📎 paperclip +1F587 FE0F ; fully-qualified # 🖇️ linked paperclips +1F587 ; unqualified # 🖇 linked paperclips +1F4CF ; fully-qualified # 📏 straight ruler +1F4D0 ; fully-qualified # 📐 triangular ruler +2702 FE0F ; fully-qualified # ✂️ scissors +2702 ; unqualified # ✂ scissors +1F5C3 FE0F ; fully-qualified # 🗃️ card file box +1F5C3 ; unqualified # 🗃 card file box +1F5C4 FE0F ; fully-qualified # 🗄️ file cabinet +1F5C4 ; unqualified # 🗄 file cabinet +1F5D1 FE0F ; fully-qualified # 🗑️ wastebasket +1F5D1 ; unqualified # 🗑 wastebasket + +# subgroup: lock +1F512 ; fully-qualified # 🔒 locked +1F513 ; fully-qualified # 🔓 unlocked +1F50F ; fully-qualified # 🔏 locked with pen +1F510 ; fully-qualified # 🔐 locked with key +1F511 ; fully-qualified # 🔑 key +1F5DD FE0F ; fully-qualified # 🗝️ old key +1F5DD ; unqualified # 🗝 old key + +# subgroup: tool +1F528 ; fully-qualified # 🔨 hammer +1FA93 ; fully-qualified # 🪓 axe +26CF FE0F ; fully-qualified # ⛏️ pick +26CF ; unqualified # ⛏ pick +2692 FE0F ; fully-qualified # ⚒️ hammer and pick +2692 ; unqualified # ⚒ hammer and pick +1F6E0 FE0F ; fully-qualified # 🛠️ hammer and wrench +1F6E0 ; unqualified # 🛠 hammer and wrench +1F5E1 FE0F ; fully-qualified # 🗡️ dagger +1F5E1 ; unqualified # 🗡 dagger +2694 FE0F ; fully-qualified # ⚔️ crossed swords +2694 ; unqualified # ⚔ crossed swords +1F52B ; fully-qualified # 🔫 pistol +1F3F9 ; fully-qualified # 🏹 bow and arrow +1F6E1 FE0F ; fully-qualified # 🛡️ shield +1F6E1 ; unqualified # 🛡 shield +1F527 ; fully-qualified # 🔧 wrench +1F529 ; fully-qualified # 🔩 nut and bolt +2699 FE0F ; fully-qualified # ⚙️ gear +2699 ; unqualified # ⚙ gear +1F5DC FE0F ; fully-qualified # 🗜️ clamp +1F5DC ; unqualified # 🗜 clamp +2696 FE0F ; fully-qualified # ⚖️ balance scale +2696 ; unqualified # ⚖ balance scale +1F9AF ; fully-qualified # 🦯 probing cane +1F517 ; fully-qualified # 🔗 link +26D3 FE0F ; fully-qualified # ⛓️ chains +26D3 ; unqualified # ⛓ chains +1F9F0 ; fully-qualified # 🧰 toolbox +1F9F2 ; fully-qualified # 🧲 magnet + +# subgroup: science +2697 FE0F ; fully-qualified # ⚗️ alembic +2697 ; unqualified # ⚗ alembic +1F9EA ; fully-qualified # 🧪 test tube +1F9EB ; fully-qualified # 🧫 petri dish +1F9EC ; fully-qualified # 🧬 dna +1F52C ; fully-qualified # 🔬 microscope +1F52D ; fully-qualified # 🔭 telescope +1F4E1 ; fully-qualified # 📡 satellite antenna + +# subgroup: medical +1F489 ; fully-qualified # 💉 syringe +1FA78 ; fully-qualified # 🩸 drop of blood +1F48A ; fully-qualified # 💊 pill +1FA79 ; fully-qualified # 🩹 adhesive bandage +1FA7A ; fully-qualified # 🩺 stethoscope + +# subgroup: household +1F6AA ; fully-qualified # 🚪 door +1F6CF FE0F ; fully-qualified # 🛏️ bed +1F6CF ; unqualified # 🛏 bed +1F6CB FE0F ; fully-qualified # 🛋️ couch and lamp +1F6CB ; unqualified # 🛋 couch and lamp +1FA91 ; fully-qualified # 🪑 chair +1F6BD ; fully-qualified # 🚽 toilet +1F6BF ; fully-qualified # 🚿 shower +1F6C1 ; fully-qualified # 🛁 bathtub +1FA92 ; fully-qualified # 🪒 razor +1F9F4 ; fully-qualified # 🧴 lotion bottle +1F9F7 ; fully-qualified # 🧷 safety pin +1F9F9 ; fully-qualified # 🧹 broom +1F9FA ; fully-qualified # 🧺 basket +1F9FB ; fully-qualified # 🧻 roll of paper +1F9FC ; fully-qualified # 🧼 soap +1F9FD ; fully-qualified # 🧽 sponge +1F9EF ; fully-qualified # 🧯 fire extinguisher +1F6D2 ; fully-qualified # 🛒 shopping cart + +# subgroup: other-object +1F6AC ; fully-qualified # 🚬 cigarette +26B0 FE0F ; fully-qualified # ⚰️ coffin +26B0 ; unqualified # ⚰ coffin +26B1 FE0F ; fully-qualified # ⚱️ funeral urn +26B1 ; unqualified # ⚱ funeral urn +1F5FF ; fully-qualified # 🗿 moai + +# Objects subtotal: 282 +# Objects subtotal: 282 w/o modifiers + +# group: Symbols + +# subgroup: transport-sign +1F3E7 ; fully-qualified # 🏧 ATM sign +1F6AE ; fully-qualified # 🚮 litter in bin sign +1F6B0 ; fully-qualified # 🚰 potable water +267F ; fully-qualified # ♿ wheelchair symbol +1F6B9 ; fully-qualified # 🚹 men’s room +1F6BA ; fully-qualified # 🚺 women’s room +1F6BB ; fully-qualified # 🚻 restroom +1F6BC ; fully-qualified # 🚼 baby symbol +1F6BE ; fully-qualified # 🚾 water closet +1F6C2 ; fully-qualified # 🛂 passport control +1F6C3 ; fully-qualified # 🛃 customs +1F6C4 ; fully-qualified # 🛄 baggage claim +1F6C5 ; fully-qualified # 🛅 left luggage + +# subgroup: warning +26A0 FE0F ; fully-qualified # ⚠️ warning +26A0 ; unqualified # ⚠ warning +1F6B8 ; fully-qualified # 🚸 children crossing +26D4 ; fully-qualified # ⛔ no entry +1F6AB ; fully-qualified # 🚫 prohibited +1F6B3 ; fully-qualified # 🚳 no bicycles +1F6AD ; fully-qualified # 🚭 no smoking +1F6AF ; fully-qualified # 🚯 no littering +1F6B1 ; fully-qualified # 🚱 non-potable water +1F6B7 ; fully-qualified # 🚷 no pedestrians +1F4F5 ; fully-qualified # 📵 no mobile phones +1F51E ; fully-qualified # 🔞 no one under eighteen +2622 FE0F ; fully-qualified # ☢️ radioactive +2622 ; unqualified # ☢ radioactive +2623 FE0F ; fully-qualified # ☣️ biohazard +2623 ; unqualified # ☣ biohazard + +# subgroup: arrow +2B06 FE0F ; fully-qualified # ⬆️ up arrow +2B06 ; unqualified # ⬆ up arrow +2197 FE0F ; fully-qualified # ↗️ up-right arrow +2197 ; unqualified # ↗ up-right arrow +27A1 FE0F ; fully-qualified # ➡️ right arrow +27A1 ; unqualified # ➡ right arrow +2198 FE0F ; fully-qualified # ↘️ down-right arrow +2198 ; unqualified # ↘ down-right arrow +2B07 FE0F ; fully-qualified # ⬇️ down arrow +2B07 ; unqualified # ⬇ down arrow +2199 FE0F ; fully-qualified # ↙️ down-left arrow +2199 ; unqualified # ↙ down-left arrow +2B05 FE0F ; fully-qualified # ⬅️ left arrow +2B05 ; unqualified # ⬅ left arrow +2196 FE0F ; fully-qualified # ↖️ up-left arrow +2196 ; unqualified # ↖ up-left arrow +2195 FE0F ; fully-qualified # ↕️ up-down arrow +2195 ; unqualified # ↕ up-down arrow +2194 FE0F ; fully-qualified # ↔️ left-right arrow +2194 ; unqualified # ↔ left-right arrow +21A9 FE0F ; fully-qualified # ↩️ right arrow curving left +21A9 ; unqualified # ↩ right arrow curving left +21AA FE0F ; fully-qualified # ↪️ left arrow curving right +21AA ; unqualified # ↪ left arrow curving right +2934 FE0F ; fully-qualified # ⤴️ right arrow curving up +2934 ; unqualified # ⤴ right arrow curving up +2935 FE0F ; fully-qualified # ⤵️ right arrow curving down +2935 ; unqualified # ⤵ right arrow curving down +1F503 ; fully-qualified # 🔃 clockwise vertical arrows +1F504 ; fully-qualified # 🔄 counterclockwise arrows button +1F519 ; fully-qualified # 🔙 BACK arrow +1F51A ; fully-qualified # 🔚 END arrow +1F51B ; fully-qualified # 🔛 ON! arrow +1F51C ; fully-qualified # 🔜 SOON arrow +1F51D ; fully-qualified # 🔝 TOP arrow + +# subgroup: religion +1F6D0 ; fully-qualified # 🛐 place of worship +269B FE0F ; fully-qualified # ⚛️ atom symbol +269B ; unqualified # ⚛ atom symbol +1F549 FE0F ; fully-qualified # 🕉️ om +1F549 ; unqualified # 🕉 om +2721 FE0F ; fully-qualified # ✡️ star of David +2721 ; unqualified # ✡ star of David +2638 FE0F ; fully-qualified # ☸️ wheel of dharma +2638 ; unqualified # ☸ wheel of dharma +262F FE0F ; fully-qualified # ☯️ yin yang +262F ; unqualified # ☯ yin yang +271D FE0F ; fully-qualified # ✝️ latin cross +271D ; unqualified # ✝ latin cross +2626 FE0F ; fully-qualified # ☦️ orthodox cross +2626 ; unqualified # ☦ orthodox cross +262A FE0F ; fully-qualified # ☪️ star and crescent +262A ; unqualified # ☪ star and crescent +262E FE0F ; fully-qualified # ☮️ peace symbol +262E ; unqualified # ☮ peace symbol +1F54E ; fully-qualified # 🕎 menorah +1F52F ; fully-qualified # 🔯 dotted six-pointed star + +# subgroup: zodiac +2648 ; fully-qualified # ♈ Aries +2649 ; fully-qualified # ♉ Taurus +264A ; fully-qualified # ♊ Gemini +264B ; fully-qualified # ♋ Cancer +264C ; fully-qualified # ♌ Leo +264D ; fully-qualified # ♍ Virgo +264E ; fully-qualified # ♎ Libra +264F ; fully-qualified # ♏ Scorpio +2650 ; fully-qualified # ♐ Sagittarius +2651 ; fully-qualified # ♑ Capricorn +2652 ; fully-qualified # ♒ Aquarius +2653 ; fully-qualified # ♓ Pisces +26CE ; fully-qualified # ⛎ Ophiuchus + +# subgroup: av-symbol +1F500 ; fully-qualified # 🔀 shuffle tracks button +1F501 ; fully-qualified # 🔁 repeat button +1F502 ; fully-qualified # 🔂 repeat single button +25B6 FE0F ; fully-qualified # ▶️ play button +25B6 ; unqualified # ▶ play button +23E9 ; fully-qualified # ⏩ fast-forward button +23ED FE0F ; fully-qualified # ⏭️ next track button +23ED ; unqualified # ⏭ next track button +23EF FE0F ; fully-qualified # ⏯️ play or pause button +23EF ; unqualified # ⏯ play or pause button +25C0 FE0F ; fully-qualified # ◀️ reverse button +25C0 ; unqualified # ◀ reverse button +23EA ; fully-qualified # ⏪ fast reverse button +23EE FE0F ; fully-qualified # ⏮️ last track button +23EE ; unqualified # ⏮ last track button +1F53C ; fully-qualified # 🔼 upwards button +23EB ; fully-qualified # ⏫ fast up button +1F53D ; fully-qualified # 🔽 downwards button +23EC ; fully-qualified # ⏬ fast down button +23F8 FE0F ; fully-qualified # ⏸️ pause button +23F8 ; unqualified # ⏸ pause button +23F9 FE0F ; fully-qualified # ⏹️ stop button +23F9 ; unqualified # ⏹ stop button +23FA FE0F ; fully-qualified # ⏺️ record button +23FA ; unqualified # ⏺ record button +23CF FE0F ; fully-qualified # ⏏️ eject button +23CF ; unqualified # ⏏ eject button +1F3A6 ; fully-qualified # 🎦 cinema +1F505 ; fully-qualified # 🔅 dim button +1F506 ; fully-qualified # 🔆 bright button +1F4F6 ; fully-qualified # 📶 antenna bars +1F4F3 ; fully-qualified # 📳 vibration mode +1F4F4 ; fully-qualified # 📴 mobile phone off + +# subgroup: gender +2640 FE0F ; fully-qualified # ♀️ female sign +2640 ; unqualified # ♀ female sign +2642 FE0F ; fully-qualified # ♂️ male sign +2642 ; unqualified # ♂ male sign + +# subgroup: other-symbol +2695 FE0F ; fully-qualified # ⚕️ medical symbol +2695 ; unqualified # ⚕ medical symbol +267E FE0F ; fully-qualified # ♾️ infinity +267E ; unqualified # ♾ infinity +267B FE0F ; fully-qualified # ♻️ recycling symbol +267B ; unqualified # ♻ recycling symbol +269C FE0F ; fully-qualified # ⚜️ fleur-de-lis +269C ; unqualified # ⚜ fleur-de-lis +1F531 ; fully-qualified # 🔱 trident emblem +1F4DB ; fully-qualified # 📛 name badge +1F530 ; fully-qualified # 🔰 Japanese symbol for beginner +2B55 ; fully-qualified # ⭕ hollow red circle +2705 ; fully-qualified # ✅ check mark button +2611 FE0F ; fully-qualified # ☑️ check box with check +2611 ; unqualified # ☑ check box with check +2714 FE0F ; fully-qualified # ✔️ check mark +2714 ; unqualified # ✔ check mark +2716 FE0F ; fully-qualified # ✖️ multiplication sign +2716 ; unqualified # ✖ multiplication sign +274C ; fully-qualified # ❌ cross mark +274E ; fully-qualified # ❎ cross mark button +2795 ; fully-qualified # ➕ plus sign +2796 ; fully-qualified # ➖ minus sign +2797 ; fully-qualified # ➗ division sign +27B0 ; fully-qualified # ➰ curly loop +27BF ; fully-qualified # ➿ double curly loop +303D FE0F ; fully-qualified # 〽️ part alternation mark +303D ; unqualified # 〽 part alternation mark +2733 FE0F ; fully-qualified # ✳️ eight-spoked asterisk +2733 ; unqualified # ✳ eight-spoked asterisk +2734 FE0F ; fully-qualified # ✴️ eight-pointed star +2734 ; unqualified # ✴ eight-pointed star +2747 FE0F ; fully-qualified # ❇️ sparkle +2747 ; unqualified # ❇ sparkle +203C FE0F ; fully-qualified # ‼️ double exclamation mark +203C ; unqualified # ‼ double exclamation mark +2049 FE0F ; fully-qualified # ⁉️ exclamation question mark +2049 ; unqualified # ⁉ exclamation question mark +2753 ; fully-qualified # ❓ question mark +2754 ; fully-qualified # ❔ white question mark +2755 ; fully-qualified # ❕ white exclamation mark +2757 ; fully-qualified # ❗ exclamation mark +3030 FE0F ; fully-qualified # 〰️ wavy dash +3030 ; unqualified # 〰 wavy dash +00A9 FE0F ; fully-qualified # ©️ copyright +00A9 ; unqualified # © copyright +00AE FE0F ; fully-qualified # ®️ registered +00AE ; unqualified # ® registered +2122 FE0F ; fully-qualified # ™️ trade mark +2122 ; unqualified # ™ trade mark + +# subgroup: keycap +0023 FE0F 20E3 ; fully-qualified # #️⃣ keycap: # +0023 20E3 ; unqualified # #⃣ keycap: # +002A FE0F 20E3 ; fully-qualified # *️⃣ keycap: * +002A 20E3 ; unqualified # *⃣ keycap: * +0030 FE0F 20E3 ; fully-qualified # 0️⃣ keycap: 0 +0030 20E3 ; unqualified # 0⃣ keycap: 0 +0031 FE0F 20E3 ; fully-qualified # 1️⃣ keycap: 1 +0031 20E3 ; unqualified # 1⃣ keycap: 1 +0032 FE0F 20E3 ; fully-qualified # 2️⃣ keycap: 2 +0032 20E3 ; unqualified # 2⃣ keycap: 2 +0033 FE0F 20E3 ; fully-qualified # 3️⃣ keycap: 3 +0033 20E3 ; unqualified # 3⃣ keycap: 3 +0034 FE0F 20E3 ; fully-qualified # 4️⃣ keycap: 4 +0034 20E3 ; unqualified # 4⃣ keycap: 4 +0035 FE0F 20E3 ; fully-qualified # 5️⃣ keycap: 5 +0035 20E3 ; unqualified # 5⃣ keycap: 5 +0036 FE0F 20E3 ; fully-qualified # 6️⃣ keycap: 6 +0036 20E3 ; unqualified # 6⃣ keycap: 6 +0037 FE0F 20E3 ; fully-qualified # 7️⃣ keycap: 7 +0037 20E3 ; unqualified # 7⃣ keycap: 7 +0038 FE0F 20E3 ; fully-qualified # 8️⃣ keycap: 8 +0038 20E3 ; unqualified # 8⃣ keycap: 8 +0039 FE0F 20E3 ; fully-qualified # 9️⃣ keycap: 9 +0039 20E3 ; unqualified # 9⃣ keycap: 9 +1F51F ; fully-qualified # 🔟 keycap: 10 + +# subgroup: alphanum +1F520 ; fully-qualified # 🔠 input latin uppercase +1F521 ; fully-qualified # 🔡 input latin lowercase +1F522 ; fully-qualified # 🔢 input numbers +1F523 ; fully-qualified # 🔣 input symbols +1F524 ; fully-qualified # 🔤 input latin letters +1F170 FE0F ; fully-qualified # 🅰️ A button (blood type) +1F170 ; unqualified # 🅰 A button (blood type) +1F18E ; fully-qualified # 🆎 AB button (blood type) +1F171 FE0F ; fully-qualified # 🅱️ B button (blood type) +1F171 ; unqualified # 🅱 B button (blood type) +1F191 ; fully-qualified # 🆑 CL button +1F192 ; fully-qualified # 🆒 COOL button +1F193 ; fully-qualified # 🆓 FREE button +2139 FE0F ; fully-qualified # ℹ️ information +2139 ; unqualified # ℹ information +1F194 ; fully-qualified # 🆔 ID button +24C2 FE0F ; fully-qualified # Ⓜ️ circled M +24C2 ; unqualified # Ⓜ circled M +1F195 ; fully-qualified # 🆕 NEW button +1F196 ; fully-qualified # 🆖 NG button +1F17E FE0F ; fully-qualified # 🅾️ O button (blood type) +1F17E ; unqualified # 🅾 O button (blood type) +1F197 ; fully-qualified # 🆗 OK button +1F17F FE0F ; fully-qualified # 🅿️ P button +1F17F ; unqualified # 🅿 P button +1F198 ; fully-qualified # 🆘 SOS button +1F199 ; fully-qualified # 🆙 UP! button +1F19A ; fully-qualified # 🆚 VS button +1F201 ; fully-qualified # 🈁 Japanese “here” button +1F202 FE0F ; fully-qualified # 🈂️ Japanese “service charge” button +1F202 ; unqualified # 🈂 Japanese “service charge” button +1F237 FE0F ; fully-qualified # 🈷️ Japanese “monthly amount” button +1F237 ; unqualified # 🈷 Japanese “monthly amount” button +1F236 ; fully-qualified # 🈶 Japanese “not free of charge” button +1F22F ; fully-qualified # 🈯 Japanese “reserved” button +1F250 ; fully-qualified # 🉐 Japanese “bargain” button +1F239 ; fully-qualified # 🈹 Japanese “discount” button +1F21A ; fully-qualified # 🈚 Japanese “free of charge” button +1F232 ; fully-qualified # 🈲 Japanese “prohibited” button +1F251 ; fully-qualified # 🉑 Japanese “acceptable” button +1F238 ; fully-qualified # 🈸 Japanese “application” button +1F234 ; fully-qualified # 🈴 Japanese “passing grade” button +1F233 ; fully-qualified # 🈳 Japanese “vacancy” button +3297 FE0F ; fully-qualified # ㊗️ Japanese “congratulations” button +3297 ; unqualified # ㊗ Japanese “congratulations” button +3299 FE0F ; fully-qualified # ㊙️ Japanese “secret” button +3299 ; unqualified # ㊙ Japanese “secret” button +1F23A ; fully-qualified # 🈺 Japanese “open for business” button +1F235 ; fully-qualified # 🈵 Japanese “no vacancy” button + +# subgroup: geometric +1F534 ; fully-qualified # 🔴 red circle +1F7E0 ; fully-qualified # 🟠 orange circle +1F7E1 ; fully-qualified # 🟡 yellow circle +1F7E2 ; fully-qualified # 🟢 green circle +1F535 ; fully-qualified # 🔵 blue circle +1F7E3 ; fully-qualified # 🟣 purple circle +1F7E4 ; fully-qualified # 🟤 brown circle +26AB ; fully-qualified # ⚫ black circle +26AA ; fully-qualified # ⚪ white circle +1F7E5 ; fully-qualified # 🟥 red square +1F7E7 ; fully-qualified # 🟧 orange square +1F7E8 ; fully-qualified # 🟨 yellow square +1F7E9 ; fully-qualified # 🟩 green square +1F7E6 ; fully-qualified # 🟦 blue square +1F7EA ; fully-qualified # 🟪 purple square +1F7EB ; fully-qualified # 🟫 brown square +2B1B ; fully-qualified # ⬛ black large square +2B1C ; fully-qualified # ⬜ white large square +25FC FE0F ; fully-qualified # ◼️ black medium square +25FC ; unqualified # ◼ black medium square +25FB FE0F ; fully-qualified # ◻️ white medium square +25FB ; unqualified # ◻ white medium square +25FE ; fully-qualified # ◾ black medium-small square +25FD ; fully-qualified # ◽ white medium-small square +25AA FE0F ; fully-qualified # ▪️ black small square +25AA ; unqualified # ▪ black small square +25AB FE0F ; fully-qualified # ▫️ white small square +25AB ; unqualified # ▫ white small square +1F536 ; fully-qualified # 🔶 large orange diamond +1F537 ; fully-qualified # 🔷 large blue diamond +1F538 ; fully-qualified # 🔸 small orange diamond +1F539 ; fully-qualified # 🔹 small blue diamond +1F53A ; fully-qualified # 🔺 red triangle pointed up +1F53B ; fully-qualified # 🔻 red triangle pointed down +1F4A0 ; fully-qualified # 💠 diamond with a dot +1F518 ; fully-qualified # 🔘 radio button +1F533 ; fully-qualified # 🔳 white square button +1F532 ; fully-qualified # 🔲 black square button + +# Symbols subtotal: 297 +# Symbols subtotal: 297 w/o modifiers + +# group: Flags + +# subgroup: flag +1F3C1 ; fully-qualified # 🏁 chequered flag +1F6A9 ; fully-qualified # 🚩 triangular flag +1F38C ; fully-qualified # 🎌 crossed flags +1F3F4 ; fully-qualified # 🏴 black flag +1F3F3 FE0F ; fully-qualified # 🏳️ white flag +1F3F3 ; unqualified # 🏳 white flag +1F3F3 FE0F 200D 1F308 ; fully-qualified # 🏳️‍🌈 rainbow flag +1F3F3 200D 1F308 ; unqualified # 🏳‍🌈 rainbow flag +1F3F4 200D 2620 FE0F ; fully-qualified # 🏴‍☠️ pirate flag +1F3F4 200D 2620 ; minimally-qualified # 🏴‍☠ pirate flag + +# subgroup: country-flag +1F1E6 1F1E8 ; fully-qualified # 🇦🇨 flag: Ascension Island +1F1E6 1F1E9 ; fully-qualified # 🇦🇩 flag: Andorra +1F1E6 1F1EA ; fully-qualified # 🇦🇪 flag: United Arab Emirates +1F1E6 1F1EB ; fully-qualified # 🇦🇫 flag: Afghanistan +1F1E6 1F1EC ; fully-qualified # 🇦🇬 flag: Antigua & Barbuda +1F1E6 1F1EE ; fully-qualified # 🇦🇮 flag: Anguilla +1F1E6 1F1F1 ; fully-qualified # 🇦🇱 flag: Albania +1F1E6 1F1F2 ; fully-qualified # 🇦🇲 flag: Armenia +1F1E6 1F1F4 ; fully-qualified # 🇦🇴 flag: Angola +1F1E6 1F1F6 ; fully-qualified # 🇦🇶 flag: Antarctica +1F1E6 1F1F7 ; fully-qualified # 🇦🇷 flag: Argentina +1F1E6 1F1F8 ; fully-qualified # 🇦🇸 flag: American Samoa +1F1E6 1F1F9 ; fully-qualified # 🇦🇹 flag: Austria +1F1E6 1F1FA ; fully-qualified # 🇦🇺 flag: Australia +1F1E6 1F1FC ; fully-qualified # 🇦🇼 flag: Aruba +1F1E6 1F1FD ; fully-qualified # 🇦🇽 flag: Åland Islands +1F1E6 1F1FF ; fully-qualified # 🇦🇿 flag: Azerbaijan +1F1E7 1F1E6 ; fully-qualified # 🇧🇦 flag: Bosnia & Herzegovina +1F1E7 1F1E7 ; fully-qualified # 🇧🇧 flag: Barbados +1F1E7 1F1E9 ; fully-qualified # 🇧🇩 flag: Bangladesh +1F1E7 1F1EA ; fully-qualified # 🇧🇪 flag: Belgium +1F1E7 1F1EB ; fully-qualified # 🇧🇫 flag: Burkina Faso +1F1E7 1F1EC ; fully-qualified # 🇧🇬 flag: Bulgaria +1F1E7 1F1ED ; fully-qualified # 🇧🇭 flag: Bahrain +1F1E7 1F1EE ; fully-qualified # 🇧🇮 flag: Burundi +1F1E7 1F1EF ; fully-qualified # 🇧🇯 flag: Benin +1F1E7 1F1F1 ; fully-qualified # 🇧🇱 flag: St. Barthélemy +1F1E7 1F1F2 ; fully-qualified # 🇧🇲 flag: Bermuda +1F1E7 1F1F3 ; fully-qualified # 🇧🇳 flag: Brunei +1F1E7 1F1F4 ; fully-qualified # 🇧🇴 flag: Bolivia +1F1E7 1F1F6 ; fully-qualified # 🇧🇶 flag: Caribbean Netherlands +1F1E7 1F1F7 ; fully-qualified # 🇧🇷 flag: Brazil +1F1E7 1F1F8 ; fully-qualified # 🇧🇸 flag: Bahamas +1F1E7 1F1F9 ; fully-qualified # 🇧🇹 flag: Bhutan +1F1E7 1F1FB ; fully-qualified # 🇧🇻 flag: Bouvet Island +1F1E7 1F1FC ; fully-qualified # 🇧🇼 flag: Botswana +1F1E7 1F1FE ; fully-qualified # 🇧🇾 flag: Belarus +1F1E7 1F1FF ; fully-qualified # 🇧🇿 flag: Belize +1F1E8 1F1E6 ; fully-qualified # 🇨🇦 flag: Canada +1F1E8 1F1E8 ; fully-qualified # 🇨🇨 flag: Cocos (Keeling) Islands +1F1E8 1F1E9 ; fully-qualified # 🇨🇩 flag: Congo - Kinshasa +1F1E8 1F1EB ; fully-qualified # 🇨🇫 flag: Central African Republic +1F1E8 1F1EC ; fully-qualified # 🇨🇬 flag: Congo - Brazzaville +1F1E8 1F1ED ; fully-qualified # 🇨🇭 flag: Switzerland +1F1E8 1F1EE ; fully-qualified # 🇨🇮 flag: Côte d’Ivoire +1F1E8 1F1F0 ; fully-qualified # 🇨🇰 flag: Cook Islands +1F1E8 1F1F1 ; fully-qualified # 🇨🇱 flag: Chile +1F1E8 1F1F2 ; fully-qualified # 🇨🇲 flag: Cameroon +1F1E8 1F1F3 ; fully-qualified # 🇨🇳 flag: China +1F1E8 1F1F4 ; fully-qualified # 🇨🇴 flag: Colombia +1F1E8 1F1F5 ; fully-qualified # 🇨🇵 flag: Clipperton Island +1F1E8 1F1F7 ; fully-qualified # 🇨🇷 flag: Costa Rica +1F1E8 1F1FA ; fully-qualified # 🇨🇺 flag: Cuba +1F1E8 1F1FB ; fully-qualified # 🇨🇻 flag: Cape Verde +1F1E8 1F1FC ; fully-qualified # 🇨🇼 flag: Curaçao +1F1E8 1F1FD ; fully-qualified # 🇨🇽 flag: Christmas Island +1F1E8 1F1FE ; fully-qualified # 🇨🇾 flag: Cyprus +1F1E8 1F1FF ; fully-qualified # 🇨🇿 flag: Czechia +1F1E9 1F1EA ; fully-qualified # 🇩🇪 flag: Germany +1F1E9 1F1EC ; fully-qualified # 🇩🇬 flag: Diego Garcia +1F1E9 1F1EF ; fully-qualified # 🇩🇯 flag: Djibouti +1F1E9 1F1F0 ; fully-qualified # 🇩🇰 flag: Denmark +1F1E9 1F1F2 ; fully-qualified # 🇩🇲 flag: Dominica +1F1E9 1F1F4 ; fully-qualified # 🇩🇴 flag: Dominican Republic +1F1E9 1F1FF ; fully-qualified # 🇩🇿 flag: Algeria +1F1EA 1F1E6 ; fully-qualified # 🇪🇦 flag: Ceuta & Melilla +1F1EA 1F1E8 ; fully-qualified # 🇪🇨 flag: Ecuador +1F1EA 1F1EA ; fully-qualified # 🇪🇪 flag: Estonia +1F1EA 1F1EC ; fully-qualified # 🇪🇬 flag: Egypt +1F1EA 1F1ED ; fully-qualified # 🇪🇭 flag: Western Sahara +1F1EA 1F1F7 ; fully-qualified # 🇪🇷 flag: Eritrea +1F1EA 1F1F8 ; fully-qualified # 🇪🇸 flag: Spain +1F1EA 1F1F9 ; fully-qualified # 🇪🇹 flag: Ethiopia +1F1EA 1F1FA ; fully-qualified # 🇪🇺 flag: European Union +1F1EB 1F1EE ; fully-qualified # 🇫🇮 flag: Finland +1F1EB 1F1EF ; fully-qualified # 🇫🇯 flag: Fiji +1F1EB 1F1F0 ; fully-qualified # 🇫🇰 flag: Falkland Islands +1F1EB 1F1F2 ; fully-qualified # 🇫🇲 flag: Micronesia +1F1EB 1F1F4 ; fully-qualified # 🇫🇴 flag: Faroe Islands +1F1EB 1F1F7 ; fully-qualified # 🇫🇷 flag: France +1F1EC 1F1E6 ; fully-qualified # 🇬🇦 flag: Gabon +1F1EC 1F1E7 ; fully-qualified # 🇬🇧 flag: United Kingdom +1F1EC 1F1E9 ; fully-qualified # 🇬🇩 flag: Grenada +1F1EC 1F1EA ; fully-qualified # 🇬🇪 flag: Georgia +1F1EC 1F1EB ; fully-qualified # 🇬🇫 flag: French Guiana +1F1EC 1F1EC ; fully-qualified # 🇬🇬 flag: Guernsey +1F1EC 1F1ED ; fully-qualified # 🇬🇭 flag: Ghana +1F1EC 1F1EE ; fully-qualified # 🇬🇮 flag: Gibraltar +1F1EC 1F1F1 ; fully-qualified # 🇬🇱 flag: Greenland +1F1EC 1F1F2 ; fully-qualified # 🇬🇲 flag: Gambia +1F1EC 1F1F3 ; fully-qualified # 🇬🇳 flag: Guinea +1F1EC 1F1F5 ; fully-qualified # 🇬🇵 flag: Guadeloupe +1F1EC 1F1F6 ; fully-qualified # 🇬🇶 flag: Equatorial Guinea +1F1EC 1F1F7 ; fully-qualified # 🇬🇷 flag: Greece +1F1EC 1F1F8 ; fully-qualified # 🇬🇸 flag: South Georgia & South Sandwich Islands +1F1EC 1F1F9 ; fully-qualified # 🇬🇹 flag: Guatemala +1F1EC 1F1FA ; fully-qualified # 🇬🇺 flag: Guam +1F1EC 1F1FC ; fully-qualified # 🇬🇼 flag: Guinea-Bissau +1F1EC 1F1FE ; fully-qualified # 🇬🇾 flag: Guyana +1F1ED 1F1F0 ; fully-qualified # 🇭🇰 flag: Hong Kong SAR China +1F1ED 1F1F2 ; fully-qualified # 🇭🇲 flag: Heard & McDonald Islands +1F1ED 1F1F3 ; fully-qualified # 🇭🇳 flag: Honduras +1F1ED 1F1F7 ; fully-qualified # 🇭🇷 flag: Croatia +1F1ED 1F1F9 ; fully-qualified # 🇭🇹 flag: Haiti +1F1ED 1F1FA ; fully-qualified # 🇭🇺 flag: Hungary +1F1EE 1F1E8 ; fully-qualified # 🇮🇨 flag: Canary Islands +1F1EE 1F1E9 ; fully-qualified # 🇮🇩 flag: Indonesia +1F1EE 1F1EA ; fully-qualified # 🇮🇪 flag: Ireland +1F1EE 1F1F1 ; fully-qualified # 🇮🇱 flag: Israel +1F1EE 1F1F2 ; fully-qualified # 🇮🇲 flag: Isle of Man +1F1EE 1F1F3 ; fully-qualified # 🇮🇳 flag: India +1F1EE 1F1F4 ; fully-qualified # 🇮🇴 flag: British Indian Ocean Territory +1F1EE 1F1F6 ; fully-qualified # 🇮🇶 flag: Iraq +1F1EE 1F1F7 ; fully-qualified # 🇮🇷 flag: Iran +1F1EE 1F1F8 ; fully-qualified # 🇮🇸 flag: Iceland +1F1EE 1F1F9 ; fully-qualified # 🇮🇹 flag: Italy +1F1EF 1F1EA ; fully-qualified # 🇯🇪 flag: Jersey +1F1EF 1F1F2 ; fully-qualified # 🇯🇲 flag: Jamaica +1F1EF 1F1F4 ; fully-qualified # 🇯🇴 flag: Jordan +1F1EF 1F1F5 ; fully-qualified # 🇯🇵 flag: Japan +1F1F0 1F1EA ; fully-qualified # 🇰🇪 flag: Kenya +1F1F0 1F1EC ; fully-qualified # 🇰🇬 flag: Kyrgyzstan +1F1F0 1F1ED ; fully-qualified # 🇰🇭 flag: Cambodia +1F1F0 1F1EE ; fully-qualified # 🇰🇮 flag: Kiribati +1F1F0 1F1F2 ; fully-qualified # 🇰🇲 flag: Comoros +1F1F0 1F1F3 ; fully-qualified # 🇰🇳 flag: St. Kitts & Nevis +1F1F0 1F1F5 ; fully-qualified # 🇰🇵 flag: North Korea +1F1F0 1F1F7 ; fully-qualified # 🇰🇷 flag: South Korea +1F1F0 1F1FC ; fully-qualified # 🇰🇼 flag: Kuwait +1F1F0 1F1FE ; fully-qualified # 🇰🇾 flag: Cayman Islands +1F1F0 1F1FF ; fully-qualified # 🇰🇿 flag: Kazakhstan +1F1F1 1F1E6 ; fully-qualified # 🇱🇦 flag: Laos +1F1F1 1F1E7 ; fully-qualified # 🇱🇧 flag: Lebanon +1F1F1 1F1E8 ; fully-qualified # 🇱🇨 flag: St. Lucia +1F1F1 1F1EE ; fully-qualified # 🇱🇮 flag: Liechtenstein +1F1F1 1F1F0 ; fully-qualified # 🇱🇰 flag: Sri Lanka +1F1F1 1F1F7 ; fully-qualified # 🇱🇷 flag: Liberia +1F1F1 1F1F8 ; fully-qualified # 🇱🇸 flag: Lesotho +1F1F1 1F1F9 ; fully-qualified # 🇱🇹 flag: Lithuania +1F1F1 1F1FA ; fully-qualified # 🇱🇺 flag: Luxembourg +1F1F1 1F1FB ; fully-qualified # 🇱🇻 flag: Latvia +1F1F1 1F1FE ; fully-qualified # 🇱🇾 flag: Libya +1F1F2 1F1E6 ; fully-qualified # 🇲🇦 flag: Morocco +1F1F2 1F1E8 ; fully-qualified # 🇲🇨 flag: Monaco +1F1F2 1F1E9 ; fully-qualified # 🇲🇩 flag: Moldova +1F1F2 1F1EA ; fully-qualified # 🇲🇪 flag: Montenegro +1F1F2 1F1EB ; fully-qualified # 🇲🇫 flag: St. Martin +1F1F2 1F1EC ; fully-qualified # 🇲🇬 flag: Madagascar +1F1F2 1F1ED ; fully-qualified # 🇲🇭 flag: Marshall Islands +1F1F2 1F1F0 ; fully-qualified # 🇲🇰 flag: Macedonia +1F1F2 1F1F1 ; fully-qualified # 🇲🇱 flag: Mali +1F1F2 1F1F2 ; fully-qualified # 🇲🇲 flag: Myanmar (Burma) +1F1F2 1F1F3 ; fully-qualified # 🇲🇳 flag: Mongolia +1F1F2 1F1F4 ; fully-qualified # 🇲🇴 flag: Macao SAR China +1F1F2 1F1F5 ; fully-qualified # 🇲🇵 flag: Northern Mariana Islands +1F1F2 1F1F6 ; fully-qualified # 🇲🇶 flag: Martinique +1F1F2 1F1F7 ; fully-qualified # 🇲🇷 flag: Mauritania +1F1F2 1F1F8 ; fully-qualified # 🇲🇸 flag: Montserrat +1F1F2 1F1F9 ; fully-qualified # 🇲🇹 flag: Malta +1F1F2 1F1FA ; fully-qualified # 🇲🇺 flag: Mauritius +1F1F2 1F1FB ; fully-qualified # 🇲🇻 flag: Maldives +1F1F2 1F1FC ; fully-qualified # 🇲🇼 flag: Malawi +1F1F2 1F1FD ; fully-qualified # 🇲🇽 flag: Mexico +1F1F2 1F1FE ; fully-qualified # 🇲🇾 flag: Malaysia +1F1F2 1F1FF ; fully-qualified # 🇲🇿 flag: Mozambique +1F1F3 1F1E6 ; fully-qualified # 🇳🇦 flag: Namibia +1F1F3 1F1E8 ; fully-qualified # 🇳🇨 flag: New Caledonia +1F1F3 1F1EA ; fully-qualified # 🇳🇪 flag: Niger +1F1F3 1F1EB ; fully-qualified # 🇳🇫 flag: Norfolk Island +1F1F3 1F1EC ; fully-qualified # 🇳🇬 flag: Nigeria +1F1F3 1F1EE ; fully-qualified # 🇳🇮 flag: Nicaragua +1F1F3 1F1F1 ; fully-qualified # 🇳🇱 flag: Netherlands +1F1F3 1F1F4 ; fully-qualified # 🇳🇴 flag: Norway +1F1F3 1F1F5 ; fully-qualified # 🇳🇵 flag: Nepal +1F1F3 1F1F7 ; fully-qualified # 🇳🇷 flag: Nauru +1F1F3 1F1FA ; fully-qualified # 🇳🇺 flag: Niue +1F1F3 1F1FF ; fully-qualified # 🇳🇿 flag: New Zealand +1F1F4 1F1F2 ; fully-qualified # 🇴🇲 flag: Oman +1F1F5 1F1E6 ; fully-qualified # 🇵🇦 flag: Panama +1F1F5 1F1EA ; fully-qualified # 🇵🇪 flag: Peru +1F1F5 1F1EB ; fully-qualified # 🇵🇫 flag: French Polynesia +1F1F5 1F1EC ; fully-qualified # 🇵🇬 flag: Papua New Guinea +1F1F5 1F1ED ; fully-qualified # 🇵🇭 flag: Philippines +1F1F5 1F1F0 ; fully-qualified # 🇵🇰 flag: Pakistan +1F1F5 1F1F1 ; fully-qualified # 🇵🇱 flag: Poland +1F1F5 1F1F2 ; fully-qualified # 🇵🇲 flag: St. Pierre & Miquelon +1F1F5 1F1F3 ; fully-qualified # 🇵🇳 flag: Pitcairn Islands +1F1F5 1F1F7 ; fully-qualified # 🇵🇷 flag: Puerto Rico +1F1F5 1F1F8 ; fully-qualified # 🇵🇸 flag: Palestinian Territories +1F1F5 1F1F9 ; fully-qualified # 🇵🇹 flag: Portugal +1F1F5 1F1FC ; fully-qualified # 🇵🇼 flag: Palau +1F1F5 1F1FE ; fully-qualified # 🇵🇾 flag: Paraguay +1F1F6 1F1E6 ; fully-qualified # 🇶🇦 flag: Qatar +1F1F7 1F1EA ; fully-qualified # 🇷🇪 flag: Réunion +1F1F7 1F1F4 ; fully-qualified # 🇷🇴 flag: Romania +1F1F7 1F1F8 ; fully-qualified # 🇷🇸 flag: Serbia +1F1F7 1F1FA ; fully-qualified # 🇷🇺 flag: Russia +1F1F7 1F1FC ; fully-qualified # 🇷🇼 flag: Rwanda +1F1F8 1F1E6 ; fully-qualified # 🇸🇦 flag: Saudi Arabia +1F1F8 1F1E7 ; fully-qualified # 🇸🇧 flag: Solomon Islands +1F1F8 1F1E8 ; fully-qualified # 🇸🇨 flag: Seychelles +1F1F8 1F1E9 ; fully-qualified # 🇸🇩 flag: Sudan +1F1F8 1F1EA ; fully-qualified # 🇸🇪 flag: Sweden +1F1F8 1F1EC ; fully-qualified # 🇸🇬 flag: Singapore +1F1F8 1F1ED ; fully-qualified # 🇸🇭 flag: St. Helena +1F1F8 1F1EE ; fully-qualified # 🇸🇮 flag: Slovenia +1F1F8 1F1EF ; fully-qualified # 🇸🇯 flag: Svalbard & Jan Mayen +1F1F8 1F1F0 ; fully-qualified # 🇸🇰 flag: Slovakia +1F1F8 1F1F1 ; fully-qualified # 🇸🇱 flag: Sierra Leone +1F1F8 1F1F2 ; fully-qualified # 🇸🇲 flag: San Marino +1F1F8 1F1F3 ; fully-qualified # 🇸🇳 flag: Senegal +1F1F8 1F1F4 ; fully-qualified # 🇸🇴 flag: Somalia +1F1F8 1F1F7 ; fully-qualified # 🇸🇷 flag: Suriname +1F1F8 1F1F8 ; fully-qualified # 🇸🇸 flag: South Sudan +1F1F8 1F1F9 ; fully-qualified # 🇸🇹 flag: São Tomé & Príncipe +1F1F8 1F1FB ; fully-qualified # 🇸🇻 flag: El Salvador +1F1F8 1F1FD ; fully-qualified # 🇸🇽 flag: Sint Maarten +1F1F8 1F1FE ; fully-qualified # 🇸🇾 flag: Syria +1F1F8 1F1FF ; fully-qualified # 🇸🇿 flag: Eswatini +1F1F9 1F1E6 ; fully-qualified # 🇹🇦 flag: Tristan da Cunha +1F1F9 1F1E8 ; fully-qualified # 🇹🇨 flag: Turks & Caicos Islands +1F1F9 1F1E9 ; fully-qualified # 🇹🇩 flag: Chad +1F1F9 1F1EB ; fully-qualified # 🇹🇫 flag: French Southern Territories +1F1F9 1F1EC ; fully-qualified # 🇹🇬 flag: Togo +1F1F9 1F1ED ; fully-qualified # 🇹🇭 flag: Thailand +1F1F9 1F1EF ; fully-qualified # 🇹🇯 flag: Tajikistan +1F1F9 1F1F0 ; fully-qualified # 🇹🇰 flag: Tokelau +1F1F9 1F1F1 ; fully-qualified # 🇹🇱 flag: Timor-Leste +1F1F9 1F1F2 ; fully-qualified # 🇹🇲 flag: Turkmenistan +1F1F9 1F1F3 ; fully-qualified # 🇹🇳 flag: Tunisia +1F1F9 1F1F4 ; fully-qualified # 🇹🇴 flag: Tonga +1F1F9 1F1F7 ; fully-qualified # 🇹🇷 flag: Turkey +1F1F9 1F1F9 ; fully-qualified # 🇹🇹 flag: Trinidad & Tobago +1F1F9 1F1FB ; fully-qualified # 🇹🇻 flag: Tuvalu +1F1F9 1F1FC ; fully-qualified # 🇹🇼 flag: Taiwan +1F1F9 1F1FF ; fully-qualified # 🇹🇿 flag: Tanzania +1F1FA 1F1E6 ; fully-qualified # 🇺🇦 flag: Ukraine +1F1FA 1F1EC ; fully-qualified # 🇺🇬 flag: Uganda +1F1FA 1F1F2 ; fully-qualified # 🇺🇲 flag: U.S. Outlying Islands +1F1FA 1F1F3 ; fully-qualified # 🇺🇳 flag: United Nations +1F1FA 1F1F8 ; fully-qualified # 🇺🇸 flag: United States +1F1FA 1F1FE ; fully-qualified # 🇺🇾 flag: Uruguay +1F1FA 1F1FF ; fully-qualified # 🇺🇿 flag: Uzbekistan +1F1FB 1F1E6 ; fully-qualified # 🇻🇦 flag: Vatican City +1F1FB 1F1E8 ; fully-qualified # 🇻🇨 flag: St. Vincent & Grenadines +1F1FB 1F1EA ; fully-qualified # 🇻🇪 flag: Venezuela +1F1FB 1F1EC ; fully-qualified # 🇻🇬 flag: British Virgin Islands +1F1FB 1F1EE ; fully-qualified # 🇻🇮 flag: U.S. Virgin Islands +1F1FB 1F1F3 ; fully-qualified # 🇻🇳 flag: Vietnam +1F1FB 1F1FA ; fully-qualified # 🇻🇺 flag: Vanuatu +1F1FC 1F1EB ; fully-qualified # 🇼🇫 flag: Wallis & Futuna +1F1FC 1F1F8 ; fully-qualified # 🇼🇸 flag: Samoa +1F1FD 1F1F0 ; fully-qualified # 🇽🇰 flag: Kosovo +1F1FE 1F1EA ; fully-qualified # 🇾🇪 flag: Yemen +1F1FE 1F1F9 ; fully-qualified # 🇾🇹 flag: Mayotte +1F1FF 1F1E6 ; fully-qualified # 🇿🇦 flag: South Africa +1F1FF 1F1F2 ; fully-qualified # 🇿🇲 flag: Zambia +1F1FF 1F1FC ; fully-qualified # 🇿🇼 flag: Zimbabwe + +# subgroup: subdivision-flag +1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; fully-qualified # 🏴󠁧󠁢󠁥󠁮󠁧󠁿 flag: England +1F3F4 E0067 E0062 E0073 E0063 E0074 E007F ; fully-qualified # 🏴󠁧󠁢󠁳󠁣󠁴󠁿 flag: Scotland +1F3F4 E0067 E0062 E0077 E006C E0073 E007F ; fully-qualified # 🏴󠁧󠁢󠁷󠁬󠁳󠁿 flag: Wales + +# Flags subtotal: 271 +# Flags subtotal: 271 w/o modifiers + +# Status Counts +# fully-qualified : 3010 +# minimally-qualified : 571 +# unqualified : 246 +# component : 9 + +#EOF diff --git a/datastructures-validation/src/test/resources/test/Address.java b/datastructures-validation/src/test/resources/test/Address.java new file mode 100644 index 0000000..06b42db --- /dev/null +++ b/datastructures-validation/src/test/resources/test/Address.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import org.xbib.datastructures.validation.meta.ConstraintTarget; + +public class Address { + + private final Country country; + + private final PhoneNumber phoneNumber; + + private final String street; + + public Address(@ConstraintTarget(getter = false) Country country, + @ConstraintTarget(getter = false) String street, + @ConstraintTarget(getter = false) PhoneNumber phoneNumber) { + this.country = country; + this.street = street; + this.phoneNumber = phoneNumber; + } + + public Country country() { + return this.country; + } + + public PhoneNumber phoneNumber() { + return this.phoneNumber; + } + + public String street() { + return this.street; + } + + public static class Country { + + private final String name; + + public Country(@ConstraintTarget(getter = false) String name) { + this.name = name; + } + + public String name() { + return this.name; + } + } + + public static class PhoneNumber { + + private final String value; + + public PhoneNumber(@ConstraintTarget(getter = false) String value) { + this.value = value; + } + + public String value() { + return this.value; + } + } +} \ No newline at end of file diff --git a/datastructures-validation/src/test/resources/test/AllTypesBean.java b/datastructures-validation/src/test/resources/test/AllTypesBean.java new file mode 100644 index 0000000..c57db0b --- /dev/null +++ b/datastructures-validation/src/test/resources/test/AllTypesBean.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; + +import org.xbib.datastructures.validation.meta.ConstraintTarget; + +public class AllTypesBean { + + private String stringValue; + + private Boolean booleanValue; + + private boolean booleanPrimitiveValue; + + private Character characterValue; + + private char characterPrimitiveValue; + + private Byte byteValue; + + private byte bytePrimitiveValue; + + private Short shortValue; + + private short shortPrimitiveValue; + + private Integer integerValue; + + private int integerPrimitiveValue; + + private Long longValue; + + private long longPrimitiveValue; + + private Float floatValue; + + private float floatPrimitiveValue; + + private Double doubleValue; + + private double doublePrimitiveValue; + + private BigInteger bigIntegerValue; + + private BigDecimal bigDecimalValue; + + private LocalDate localDateValue; + + @ConstraintTarget + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + @ConstraintTarget + public Boolean getBooleanValue() { + return booleanValue; + } + + public void setBooleanValue(Boolean booleanValue) { + this.booleanValue = booleanValue; + } + + @ConstraintTarget + public boolean isBooleanPrimitiveValue() { + return booleanPrimitiveValue; + } + + public void setBooleanPrimitiveValue(boolean booleanPrimitiveValue) { + this.booleanPrimitiveValue = booleanPrimitiveValue; + } + + @ConstraintTarget + public Character getCharacterValue() { + return characterValue; + } + + public void setCharacterValue(Character characterValue) { + this.characterValue = characterValue; + } + + @ConstraintTarget + public char getCharacterPrimitiveValue() { + return characterPrimitiveValue; + } + + public void setCharacterPrimitiveValue(char characterPrimitiveValue) { + this.characterPrimitiveValue = characterPrimitiveValue; + } + + @ConstraintTarget + public Byte getByteValue() { + return byteValue; + } + + public void setByteValue(Byte byteValue) { + this.byteValue = byteValue; + } + + @ConstraintTarget + public byte getBytePrimitiveValue() { + return bytePrimitiveValue; + } + + public void setBytePrimitiveValue(byte bytePrimitiveValue) { + this.bytePrimitiveValue = bytePrimitiveValue; + } + + @ConstraintTarget + public Short getShortValue() { + return shortValue; + } + + public void setShortValue(Short shortValue) { + this.shortValue = shortValue; + } + + @ConstraintTarget + public short getShortPrimitiveValue() { + return shortPrimitiveValue; + } + + public void setShortPrimitiveValue(short shortPrimitiveValue) { + this.shortPrimitiveValue = shortPrimitiveValue; + } + + @ConstraintTarget + public Integer getIntegerValue() { + return integerValue; + } + + public void setIntegerValue(Integer integerValue) { + this.integerValue = integerValue; + } + + @ConstraintTarget + public int getIntegerPrimitiveValue() { + return integerPrimitiveValue; + } + + public void setIntegerPrimitiveValue(int integerPrimitiveValue) { + this.integerPrimitiveValue = integerPrimitiveValue; + } + + @ConstraintTarget + public Long getLongValue() { + return longValue; + } + + public void setLongValue(Long longValue) { + this.longValue = longValue; + } + + @ConstraintTarget + public long getLongPrimitiveValue() { + return longPrimitiveValue; + } + + public void setLongPrimitiveValue(long longPrimitiveValue) { + this.longPrimitiveValue = longPrimitiveValue; + } + + @ConstraintTarget + public Float getFloatValue() { + return floatValue; + } + + public void setFloatValue(Float floatValue) { + this.floatValue = floatValue; + } + + @ConstraintTarget + public float getFloatPrimitiveValue() { + return floatPrimitiveValue; + } + + public void setFloatPrimitiveValue(float floatPrimitiveValue) { + this.floatPrimitiveValue = floatPrimitiveValue; + } + + @ConstraintTarget + public Double getDoubleValue() { + return doubleValue; + } + + public void setDoubleValue(Double doubleValue) { + this.doubleValue = doubleValue; + } + + @ConstraintTarget + public double getDoublePrimitiveValue() { + return doublePrimitiveValue; + } + + public void setDoublePrimitiveValue(double doublePrimitiveValue) { + this.doublePrimitiveValue = doublePrimitiveValue; + } + + @ConstraintTarget + public BigInteger getBigIntegerValue() { + return bigIntegerValue; + } + + public void setBigIntegerValue(BigInteger bigIntegerValue) { + this.bigIntegerValue = bigIntegerValue; + } + + @ConstraintTarget + public BigDecimal getBigDecimalValue() { + return bigDecimalValue; + } + + public void setBigDecimalValue(BigDecimal bigDecimalValue) { + this.bigDecimalValue = bigDecimalValue; + } + + @ConstraintTarget + public LocalDate getLocalDateValue() { + return localDateValue; + } + + public void setLocalDateValue(LocalDate localDateValue) { + this.localDateValue = localDateValue; + } +} diff --git a/datastructures-validation/src/test/resources/test/AllTypesField.java b/datastructures-validation/src/test/resources/test/AllTypesField.java new file mode 100644 index 0000000..30f62b3 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/AllTypesField.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; + +import org.xbib.datastructures.validation.meta.ConstraintTarget; + +public class AllTypesField { + + final String stringValue; + + final Boolean booleanValue; + + final boolean booleanPrimitiveValue; + + final Character characterValue; + + final char characterPrimitiveValue; + + final Byte byteValue; + + final byte bytePrimitiveValue; + + final Short shortValue; + + final short shortPrimitiveValue; + + final Integer integerValue; + + final int integerPrimitiveValue; + + final Long longValue; + + final long longPrimitiveValue; + + final Float floatValue; + + final float floatPrimitiveValue; + + final Double doubleValue; + + final double doublePrimitiveValue; + + final BigInteger bigIntegerValue; + + final BigDecimal bigDecimalValue; + + final LocalDate localDateValue; + + public AllTypesField(@ConstraintTarget(field = true) String stringValue, + @ConstraintTarget(field = true) Boolean booleanValue, + @ConstraintTarget(field = true) boolean booleanPrimitiveValue, + @ConstraintTarget(field = true) Character characterValue, + @ConstraintTarget(field = true) char characterPrimitiveValue, + @ConstraintTarget(field = true) Byte byteValue, + @ConstraintTarget(field = true) byte bytePrimitiveValue, + @ConstraintTarget(field = true) Short shortValue, + @ConstraintTarget(field = true) short shortPrimitiveValue, + @ConstraintTarget(field = true) Integer integerValue, + @ConstraintTarget(field = true) int integerPrimitiveValue, + @ConstraintTarget(field = true) Long longValue, + @ConstraintTarget(field = true) long longPrimitiveValue, + @ConstraintTarget(field = true) Float floatValue, + @ConstraintTarget(field = true) float floatPrimitiveValue, + @ConstraintTarget(field = true) Double doubleValue, + @ConstraintTarget(field = true) double doublePrimitiveValue, + @ConstraintTarget(field = true) BigInteger bigIntegerValue, + @ConstraintTarget(field = true) BigDecimal bigDecimalValue, + @ConstraintTarget(field = true) LocalDate localDateValue) { + this.stringValue = stringValue; + this.booleanValue = booleanValue; + this.booleanPrimitiveValue = booleanPrimitiveValue; + this.characterValue = characterValue; + this.characterPrimitiveValue = characterPrimitiveValue; + this.byteValue = byteValue; + this.bytePrimitiveValue = bytePrimitiveValue; + this.shortValue = shortValue; + this.shortPrimitiveValue = shortPrimitiveValue; + this.integerValue = integerValue; + this.integerPrimitiveValue = integerPrimitiveValue; + this.longValue = longValue; + this.longPrimitiveValue = longPrimitiveValue; + this.floatValue = floatValue; + this.floatPrimitiveValue = floatPrimitiveValue; + this.doubleValue = doubleValue; + this.doublePrimitiveValue = doublePrimitiveValue; + this.bigIntegerValue = bigIntegerValue; + this.bigDecimalValue = bigDecimalValue; + this.localDateValue = localDateValue; + } +} diff --git a/datastructures-validation/src/test/resources/test/AllTypesImmutable.java b/datastructures-validation/src/test/resources/test/AllTypesImmutable.java new file mode 100644 index 0000000..b44d755 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/AllTypesImmutable.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; + +import org.xbib.datastructures.validation.meta.ConstraintTarget; + +public class AllTypesImmutable { + + private final String stringValue; + + private final Boolean booleanValue; + + private final boolean booleanPrimitiveValue; + + private final Character characterValue; + + private final char characterPrimitiveValue; + + private final Byte byteValue; + + private final byte bytePrimitiveValue; + + private final Short shortValue; + + private final short shortPrimitiveValue; + + private final Integer integerValue; + + private final int integerPrimitiveValue; + + private final Long longValue; + + private final long longPrimitiveValue; + + private final Float floatValue; + + private final float floatPrimitiveValue; + + private final Double doubleValue; + + private final double doublePrimitiveValue; + + private final BigInteger bigIntegerValue; + + private final BigDecimal bigDecimalValue; + + private final LocalDate localDateValue; + + public AllTypesImmutable(String stringValue, Boolean booleanValue, + boolean booleanPrimitiveValue, Character characterValue, + char characterPrimitiveValue, Byte byteValue, byte bytePrimitiveValue, + Short shortValue, short shortPrimitiveValue, Integer integerValue, + int integerPrimitiveValue, Long longValue, long longPrimitiveValue, + Float floatValue, float floatPrimitiveValue, Double doubleValue, + double doublePrimitiveValue, BigInteger bigIntegerValue, + BigDecimal bigDecimalValue, LocalDate localDateValue) { + this.stringValue = stringValue; + this.booleanValue = booleanValue; + this.booleanPrimitiveValue = booleanPrimitiveValue; + this.characterValue = characterValue; + this.characterPrimitiveValue = characterPrimitiveValue; + this.byteValue = byteValue; + this.bytePrimitiveValue = bytePrimitiveValue; + this.shortValue = shortValue; + this.shortPrimitiveValue = shortPrimitiveValue; + this.integerValue = integerValue; + this.integerPrimitiveValue = integerPrimitiveValue; + this.longValue = longValue; + this.longPrimitiveValue = longPrimitiveValue; + this.floatValue = floatValue; + this.floatPrimitiveValue = floatPrimitiveValue; + this.doubleValue = doubleValue; + this.doublePrimitiveValue = doublePrimitiveValue; + this.bigIntegerValue = bigIntegerValue; + this.bigDecimalValue = bigDecimalValue; + this.localDateValue = localDateValue; + } + + @ConstraintTarget(getter = false) + public String stringValue() { + return stringValue; + } + + @ConstraintTarget(getter = false) + public Boolean booleanValue() { + return booleanValue; + } + + @ConstraintTarget(getter = false) + public boolean booleanPrimitiveValue() { + return booleanPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Character characterValue() { + return characterValue; + } + + @ConstraintTarget(getter = false) + public char characterPrimitiveValue() { + return characterPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Byte byteValue() { + return byteValue; + } + + @ConstraintTarget(getter = false) + public byte bytePrimitiveValue() { + return bytePrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Short shortValue() { + return shortValue; + } + + @ConstraintTarget(getter = false) + public short shortPrimitiveValue() { + return shortPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Integer integerValue() { + return integerValue; + } + + @ConstraintTarget(getter = false) + public int integerPrimitiveValue() { + return integerPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Long longValue() { + return longValue; + } + + @ConstraintTarget(getter = false) + public long longPrimitiveValue() { + return longPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Float floatValue() { + return floatValue; + } + + @ConstraintTarget(getter = false) + public float floatPrimitiveValue() { + return floatPrimitiveValue; + } + + @ConstraintTarget(getter = false) + public Double doubleValue() { + return doubleValue; + } + + @ConstraintTarget(getter = false) + public double doublePrimitiveValue() { + return doublePrimitiveValue; + } + + @ConstraintTarget(getter = false) + public BigInteger bigIntegerValue() { + return bigIntegerValue; + } + + @ConstraintTarget(getter = false) + public BigDecimal bigDecimalValue() { + return bigDecimalValue; + } + + @ConstraintTarget(getter = false) + public LocalDate localDateValue() { + return localDateValue; + } +} diff --git a/datastructures-validation/src/test/resources/test/Car.java b/datastructures-validation/src/test/resources/test/Car.java new file mode 100644 index 0000000..0364c29 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/Car.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import org.xbib.datastructures.validation.meta.ConstraintTarget; + +public class Car { + private final String name; + private final int gas; + + public Car(String name, int gas) { + this.name = name; + this.gas = gas; + } + + @ConstraintTarget(getter = false) + public String name() { + return this.name; + } + + @ConstraintTarget(getter = false) + public int gas() { + return this.gas; + } +} diff --git a/datastructures-validation/src/test/resources/test/Car2.java b/datastructures-validation/src/test/resources/test/Car2.java new file mode 100644 index 0000000..7ee0bb6 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/Car2.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import org.xbib.datastructures.validation.meta.ConstraintArguments; + +public class Car2 { + private final String name; + private final int gas; + + @ConstraintArguments + public Car2(String name, int gas) { + this.name = name; + this.gas = gas; + } + + public String name() { + return this.name; + } + + public int gas() { + return this.gas; + } +} diff --git a/datastructures-validation/src/test/resources/test/CarBean.java b/datastructures-validation/src/test/resources/test/CarBean.java new file mode 100644 index 0000000..fb6bab6 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/CarBean.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import org.xbib.datastructures.validation.meta.ConstraintTarget; + +public class CarBean { + private String name; + private int gas; + + public CarBean(String name, int gas) { + this.name = name; + this.gas = gas; + } + + @ConstraintTarget + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + @ConstraintTarget + public int getGas() { + return this.gas; + } + + public void setGas(int gas) { + this.gas = gas; + } +} diff --git a/datastructures-validation/src/test/resources/test/CarField.java b/datastructures-validation/src/test/resources/test/CarField.java new file mode 100644 index 0000000..d4dd349 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/CarField.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import org.xbib.datastructures.validation.meta.ConstraintTarget; + +public class CarField { + final String name; + final int gas; + + public CarField(@ConstraintTarget(field = true) String name, @ConstraintTarget(field = true) int gas) { + this.name = name; + this.gas = gas; + } +} diff --git a/datastructures-validation/src/test/resources/test/User.java b/datastructures-validation/src/test/resources/test/User.java new file mode 100644 index 0000000..e26fa31 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/User.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class User { + private final String email; + private final String name; + + public User(String email, String name) { + this.email = email; + this.name = name; + } + + @Override + public String toString() { + return "User{" + "email='" + email + '\'' + ", name='" + name + '\'' + '}'; + } +} diff --git a/datastructures-validation/src/test/resources/test/UserService.java b/datastructures-validation/src/test/resources/test/UserService.java new file mode 100644 index 0000000..c82bf46 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/UserService.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +import org.xbib.datastructures.validation.meta.ConstraintArguments; + +public class UserService { + @ConstraintArguments + public User createUser(String email, String name) { + return new User(email, name); + } +} diff --git a/datastructures-validation/src/test/resources/test/_AddressMeta.java b/datastructures-validation/src/test/resources/test/_AddressMeta.java new file mode 100644 index 0000000..d572091 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_AddressMeta.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _AddressMeta { + + public static final org.xbib.datastructures.validation.meta.ObjectConstraintMeta COUNTRY = new org.xbib.datastructures.validation.meta.ObjectConstraintMeta() { + + @Override + public String name() { + return "country"; + } + + @Override + public java.util.function.Function toValue() { + return test.Address::country; + } + }; + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta STREET = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "street"; + } + + @Override + public java.util.function.Function toValue() { + return test.Address::street; + } + }; + + public static final org.xbib.datastructures.validation.meta.ObjectConstraintMeta PHONENUMBER = new org.xbib.datastructures.validation.meta.ObjectConstraintMeta() { + + @Override + public String name() { + return "phoneNumber"; + } + + @Override + public java.util.function.Function toValue() { + return test.Address::phoneNumber; + } + }; +} diff --git a/datastructures-validation/src/test/resources/test/_Address_CountryMeta.java b/datastructures-validation/src/test/resources/test/_Address_CountryMeta.java new file mode 100644 index 0000000..bc7dc4d --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_Address_CountryMeta.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _Address_CountryMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta NAME = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "name"; + } + + @Override + public java.util.function.Function toValue() { + return test.Address.Country::name; + } + }; +} diff --git a/datastructures-validation/src/test/resources/test/_Address_PhoneNumberMeta.java b/datastructures-validation/src/test/resources/test/_Address_PhoneNumberMeta.java new file mode 100644 index 0000000..e311ecf --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_Address_PhoneNumberMeta.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _Address_PhoneNumberMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta VALUE = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "value"; + } + + @Override + public java.util.function.Function toValue() { + return test.Address.PhoneNumber::value; + } + }; +} diff --git a/datastructures-validation/src/test/resources/test/_AllTypesBeanMeta.java b/datastructures-validation/src/test/resources/test/_AllTypesBeanMeta.java new file mode 100644 index 0000000..145f847 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_AllTypesBeanMeta.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _AllTypesBeanMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta STRINGVALUE = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "stringValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getStringValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getBooleanValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::isBooleanPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getCharacterValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getCharacterPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "byteValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getByteValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "bytePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getBytePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getShortValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getShortPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getIntegerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getIntegerPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getLongValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getLongPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getFloatValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getFloatPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doubleValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getDoubleValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doublePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getDoublePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta BIGINTEGERVALUE = new org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta() { + + @Override + public String name() { + return "bigIntegerValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getBigIntegerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta BIGDECIMALVALUE = new org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta() { + + @Override + public String name() { + return "bigDecimalValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getBigDecimalValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LocalDateConstraintMeta LOCALDATEVALUE = new org.xbib.datastructures.validation.meta.LocalDateConstraintMeta() { + + @Override + public String name() { + return "localDateValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesBean::getLocalDateValue; + } + }; +} diff --git a/datastructures-validation/src/test/resources/test/_AllTypesFieldMeta.java b/datastructures-validation/src/test/resources/test/_AllTypesFieldMeta.java new file mode 100644 index 0000000..beffd38 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_AllTypesFieldMeta.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _AllTypesFieldMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta STRINGVALUE = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "stringValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.stringValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.booleanValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.booleanPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.characterValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.characterPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "byteValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.byteValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "bytePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.bytePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.shortValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.shortPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.integerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.integerPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.longValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.longPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.floatValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.floatPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doubleValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.doubleValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doublePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.doublePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta BIGINTEGERVALUE = new org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta() { + + @Override + public String name() { + return "bigIntegerValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.bigIntegerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta BIGDECIMALVALUE = new org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta() { + + @Override + public String name() { + return "bigDecimalValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.bigDecimalValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LocalDateConstraintMeta LOCALDATEVALUE = new org.xbib.datastructures.validation.meta.LocalDateConstraintMeta() { + + @Override + public String name() { + return "localDateValue"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.localDateValue; + } + }; +} \ No newline at end of file diff --git a/datastructures-validation/src/test/resources/test/_AllTypesImmutableMeta.java b/datastructures-validation/src/test/resources/test/_AllTypesImmutableMeta.java new file mode 100644 index 0000000..49efe5a --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_AllTypesImmutableMeta.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _AllTypesImmutableMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta STRINGVALUE = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "stringValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::stringValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::booleanValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BooleanConstraintMeta BOOLEANPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.BooleanConstraintMeta() { + + @Override + public String name() { + return "booleanPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::booleanPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::characterValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.CharacterConstraintMeta CHARACTERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.CharacterConstraintMeta() { + + @Override + public String name() { + return "characterPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::characterPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "byteValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::byteValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ByteConstraintMeta BYTEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ByteConstraintMeta() { + + @Override + public String name() { + return "bytePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::bytePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::shortValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.ShortConstraintMeta SHORTPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.ShortConstraintMeta() { + + @Override + public String name() { + return "shortPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::shortPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::integerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta INTEGERPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "integerPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::integerPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::longValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LongConstraintMeta LONGPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.LongConstraintMeta() { + + @Override + public String name() { + return "longPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::longPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::floatValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.FloatConstraintMeta FLOATPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.FloatConstraintMeta() { + + @Override + public String name() { + return "floatPrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::floatPrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doubleValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::doubleValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.DoubleConstraintMeta DOUBLEPRIMITIVEVALUE = new org.xbib.datastructures.validation.meta.DoubleConstraintMeta() { + + @Override + public String name() { + return "doublePrimitiveValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::doublePrimitiveValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta BIGINTEGERVALUE = new org.xbib.datastructures.validation.meta.BigIntegerConstraintMeta() { + + @Override + public String name() { + return "bigIntegerValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::bigIntegerValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta BIGDECIMALVALUE = new org.xbib.datastructures.validation.meta.BigDecimalConstraintMeta() { + + @Override + public String name() { + return "bigDecimalValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::bigDecimalValue; + } + }; + + public static final org.xbib.datastructures.validation.meta.LocalDateConstraintMeta LOCALDATEVALUE = new org.xbib.datastructures.validation.meta.LocalDateConstraintMeta() { + + @Override + public String name() { + return "localDateValue"; + } + + @Override + public java.util.function.Function toValue() { + return test.AllTypesImmutable::localDateValue; + } + }; +} diff --git a/datastructures-validation/src/test/resources/test/_Car2ArgumentsMeta.java b/datastructures-validation/src/test/resources/test/_Car2ArgumentsMeta.java new file mode 100644 index 0000000..d717c79 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_Car2ArgumentsMeta.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _Car2ArgumentsMeta { + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta> NAME = new org.xbib.datastructures.validation.meta.StringConstraintMeta>() { + + @Override + public String name() { + return "name"; + } + + @Override + public java.util.function.Function, java.lang.String> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments1::arg1; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta> GAS = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta>() { + + @Override + public String name() { + return "gas"; + } + + @Override + public java.util.function.Function, java.lang.Integer> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments2::arg2; + } + }; +} \ No newline at end of file diff --git a/datastructures-validation/src/test/resources/test/_CarBeanMeta.java b/datastructures-validation/src/test/resources/test/_CarBeanMeta.java new file mode 100644 index 0000000..558c23e --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_CarBeanMeta.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _CarBeanMeta { + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta NAME = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "name"; + } + + @Override + public java.util.function.Function toValue() { + return test.CarBean::getName; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta GAS = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "gas"; + } + + @Override + public java.util.function.Function toValue() { + return test.CarBean::getGas; + } + }; +} diff --git a/datastructures-validation/src/test/resources/test/_CarFieldMeta.java b/datastructures-validation/src/test/resources/test/_CarFieldMeta.java new file mode 100644 index 0000000..002dbe1 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_CarFieldMeta.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _CarFieldMeta { + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta NAME = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "name"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.name; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta GAS = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "gas"; + } + + @Override + public java.util.function.Function toValue() { + return x -> x.gas; + } + }; +} diff --git a/datastructures-validation/src/test/resources/test/_CarMeta.java b/datastructures-validation/src/test/resources/test/_CarMeta.java new file mode 100644 index 0000000..d4f381a --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_CarMeta.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _CarMeta { + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta NAME = new org.xbib.datastructures.validation.meta.StringConstraintMeta() { + + @Override + public String name() { + return "name"; + } + + @Override + public java.util.function.Function toValue() { + return test.Car::name; + } + }; + + public static final org.xbib.datastructures.validation.meta.IntegerConstraintMeta GAS = new org.xbib.datastructures.validation.meta.IntegerConstraintMeta() { + + @Override + public String name() { + return "gas"; + } + + @Override + public java.util.function.Function toValue() { + return test.Car::gas; + } + }; +} diff --git a/datastructures-validation/src/test/resources/test/_UserServiceCreateUserArgumentsMeta.java b/datastructures-validation/src/test/resources/test/_UserServiceCreateUserArgumentsMeta.java new file mode 100644 index 0000000..0985fe3 --- /dev/null +++ b/datastructures-validation/src/test/resources/test/_UserServiceCreateUserArgumentsMeta.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018-2022 Toshiaki Maki + * + * 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 test; + +public class _UserServiceCreateUserArgumentsMeta { + + public static final org.xbib.datastructures.validation.meta.ObjectConstraintMeta, test.UserService> USERSERVICE = new org.xbib.datastructures.validation.meta.ObjectConstraintMeta, test.UserService>() { + + @Override + public String name() { + return "userService"; + } + + @Override + public java.util.function.Function, test.UserService> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments1::arg1; + } + }; + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta> EMAIL = new org.xbib.datastructures.validation.meta.StringConstraintMeta>() { + + @Override + public String name() { + return "email"; + } + + @Override + public java.util.function.Function, java.lang.String> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments2::arg2; + } + }; + + public static final org.xbib.datastructures.validation.meta.StringConstraintMeta> NAME = new org.xbib.datastructures.validation.meta.StringConstraintMeta>() { + + @Override + public String name() { + return "name"; + } + + @Override + public java.util.function.Function, java.lang.String> toValue() { + return org.xbib.datastructures.validation.arguments.Arguments3::arg3; + } + }; +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 59d1105..fb12683 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,3 +11,6 @@ jackson.version = 2.12.4 orgjson.version = 20210307 jsoniter.version = 0.9.23 javassist.version = 3.28.0-GA +assertj.version = 3.22.0 +jsr305.version = 2.0.0 +google-testing.version = 0.19 diff --git a/settings.gradle b/settings.gradle index a7a2a39..cb4f9d8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,4 +18,6 @@ include 'datastructures-queue-tape' include 'datastructures-tiny' include 'datastructures-json-tiny' include 'datastructures-yaml-tiny' +include 'datastructures-validation' +include 'datastructures-trie' include 'benchmark'