TypeSet.java
package org.klojang.collections;
import org.klojang.check.Check;
import org.klojang.util.CollectionMethods;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import static org.klojang.check.CommonChecks.deepNotNull;
/**
* <p>An extension of the {@link Set} interface with behaviour analogous to the
* {@link TypeMap} interface. That is, if the type passed to the
* {@link #contains(Object) contains()} method is not present in the {@code Set}, but
* one of its supertypes is, then {@code contains()} will return {@code true}. a
* {@code TypeSet} is unmodifiable and null-repellent. You obtain an instance of a
* {@code TypeSet} via the various static factory methods on the {@code TypeSet}
* interface. For more information about features like <a
* href="TypeMap.html#autoboxing">autoboxing</a> and
* <a href="TypeMap.html#auto-expansion">auto-expansion</a>, please read the
* documentation for the {@link TypeMap} interface.
*/
public sealed interface TypeSet extends Set<Class<?>> permits
AbstractTypeSet {
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#fixedTypeMap(Map) fixedTypeMap()},
*
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#fixedTypeMap(Map) fixedTypeMap()}
*/
static TypeSet fixedTypeSet(Collection<Class<?>> types) {
return fixedTypeSet(true, types);
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#fixedTypeMap(Map) fixedTypeMap()},
*
* @param autobox whether to enable "<a
* href="TypeMap.html#autoboxing">autoboxing</a>"
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#fixedTypeMap(Map) fixedTypeMap()}
*/
static TypeSet fixedTypeSet(boolean autobox, Collection<Class<?>> types) {
return fixedTypeSet(autobox, types.toArray(Class[]::new));
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#fixedTypeMap(Map) fixedTypeMap()},
*
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#fixedTypeMap(Map) fixedTypeMap()}
*/
static TypeSet fixedTypeSet(Class<?>... types) {
return fixedTypeSet(true, types);
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#fixedTypeMap(Map) fixedTypeMap()},
*
* @param autobox whether to enable "<a
* href="TypeMap.html#autoboxing">autoboxing</a>"
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#fixedTypeMap(Map) fixedTypeMap()}
*/
static TypeSet fixedTypeSet(boolean autobox, Class<?>... types) {
Check.notNull(types);
return new AbstractTypeSet() {
@Override
TypeMap<Object> createBackend() {
// NB we associate each type with Boolean.TRUE, but that's completely
// arbitrary. It could have been anything.
return new FixedTypeMapBuilder<>()
.autobox(autobox)
.addMultiple(Boolean.TRUE, types)
.freeze();
}
};
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#nativeTypeMap(Map) nativeTypeMap()}.
*
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#nativeTypeMap(Map) nativeTypeMap()}
*/
static TypeSet nativeTypeSet(Collection<Class<?>> types) {
return nativeTypeSet(true, types);
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#nativeTypeMap(Map) nativeTypeMap()}.
*
* @param autobox whether to enable "<a
* href="TypeMap.html#autoboxing">autoboxing</a>"
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#nativeTypeMap(Map) nativeTypeMap()}
*/
static TypeSet nativeTypeSet(boolean autobox, Collection<Class<?>> types) {
return nativeTypeSet(autobox, types.toArray(Class[]::new));
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#nativeTypeMap(Map) nativeTypeMap()}.
*
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#nativeTypeMap(Map) nativeTypeMap()}
*/
static TypeSet nativeTypeSet(Class<?>... types) {
return nativeTypeSet(true, types);
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#nativeTypeMap(Map) nativeTypeMap()}.
*
* @param autobox whether to enable "<a
* href="TypeMap.html#autoboxing">autoboxing</a>"
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#nativeTypeMap(Map) nativeTypeMap()}
*/
static TypeSet nativeTypeSet(boolean autobox, Class<?>... types) {
Check.notNull(types);
return new AbstractTypeSet() {
@Override
TypeMap<Object> createBackend() {
return new NativeTypeMapBuilder<>()
.autobox(autobox)
.addMultiple(Boolean.TRUE, types)
.freeze();
}
};
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#greedyTypeMap(Map) greedyTypeMap()}.
*
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#greedyTypeMap(Map) greedyTypeMap()}
*/
static TypeSet greedyTypeSet(Collection<Class<?>> types) {
return greedyTypeSet(true, types);
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#greedyTypeMap(Map) greedyTypeMap()}.
*
* @param autobox whether to enable "<a
* href="TypeMap.html#autoboxing">autoboxing</a>"
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#greedyTypeMap(Map) greedyTypeMap()}
*/
static TypeSet greedyTypeSet(boolean autobox,
Collection<Class<?>> types) {
return greedyTypeSet(autobox, types.toArray(Class[]::new));
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#greedyTypeMap(Map) greedyTypeMap()}.
*
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#greedyTypeMap(Map) greedyTypeMap()}
*/
static TypeSet greedyTypeSet(Class<?>... types) {
return greedyTypeSet(true, types);
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#greedyTypeMap(Map) greedyTypeMap()}.
*
* @param autobox whether to enable "<a
* href="TypeMap.html#autoboxing">autoboxing</a>"
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#greedyTypeMap(Map) greedyTypeMap()}
*/
static TypeSet greedyTypeSet(boolean autobox, Class<?>... types) {
Check.notNull(types);
return new AbstractTypeSet() {
@Override
TypeMap<Object> createBackend() {
return new GreedyTypeMapBuilder<>()
.autobox(autobox)
.addMultiple(Boolean.TRUE, types)
.freeze();
}
};
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#treeTypeMap(Map) treeTypeMap()}.
*
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#treeTypeMap(Map) treeTypeMap()}.
*/
static TypeSet treeTypeSet(Collection<Class<?>> types) {
return treeTypeSet(true, types);
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#treeTypeMap(Map) treeTypeMap()}.
*
* @param autobox whether to enable "<a
* href="TypeMap.html#autoboxing">autoboxing</a>"
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#treeTypeMap(Map) treeTypeMap()}.
*/
static TypeSet treeTypeSet(boolean autobox,
Collection<Class<?>> types) {
return treeTypeSet(autobox, types.toArray(Class[]::new));
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#treeTypeMap(Map) treeTypeMap()}.
*
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#treeTypeMap(Map) treeTypeMap()}.
*/
static TypeSet treeTypeSet(Class<?>... types) {
return treeTypeSet(true, types);
}
/**
* Returns a {@code TypeSet} that is internally backed by a
* {@link TypeMap#treeTypeMap(Map) treeTypeMap()}.
*
* @param autobox whether to enable "<a
* href="TypeMap.html#autoboxing">autoboxing</a>"
* @param types the types to initialize the {@code TypeSet} with
* @return a {@code TypeSet} that is internally backed by a
* {@link TypeMap#treeTypeMap(Map) treeTypeMap()}.
*/
static TypeSet treeTypeSet(boolean autobox, Class<?>... types) {
Check.notNull(types);
return new AbstractTypeSet() {
@Override
TypeMap<Object> createBackend() {
return new TreeTypeMapBuilder<>()
.autobox(autobox)
.addMultiple(Boolean.TRUE, types)
.freeze();
}
};
}
/**
* <p>Returns an unmodifiable {@code Set} in which the types in the provided
* collection are sorted according to their distance from {@code Object.class}.
* Note that this is a utility method, mainly meant for printing purposes. <b>The
* returned set is not an instance of {@code TypeSet}</b>. Its {@code contains}
* method performs poorly, but it can be iterated over quickly. The
* {@link java.util.Comparator Comparator} used to sort the types is similar to the
* one used for {@link #treeTypeSet(Class[]) treeTypeSet}, but much more
* heavy-handed, applying a fully-deterministic ordering of the types in the
* provided collection.
* <p>
* This is how the types in the returned set will be sorted:
* <ul>
* <li>primitive types
* <li>primitive wrapper types
* <li>enums (excluding {@code Enum.class} itself)
* <li>other non-array types, according to their distance from {@code Object .class}
* <li>array types (recursively according to component type)
* <li>interfaces according to the number of other interfaces they extend
* <li>{@code Object.class}
* <li>by inverse fully-qualified class name (e.g. OutputStream.io.java)
* </ul>
*
* @param src the collection to sort
* @return an unmodifiable {@code Set} in which the types are sorted according to
* their distance from {@code Object.class}.
*/
static Set<Class<?>> prettySort(Collection<Class<?>> src) {
Check.notNull(src);
if (!CollectionMethods.isNullRepellent(src)) {
Check.that(src).is(deepNotNull());
}
Class<?>[] types = src.toArray(Class[]::new);
Arrays.sort(types, new PrettyTypeComparator());
return ArraySet.of(types);
}
}