TypeDefinitionRegistry.java
package graphql.schema.idl;
import graphql.Assert;
import graphql.GraphQLError;
import graphql.PublicApi;
import graphql.collect.ImmutableKit;
import graphql.language.DirectiveDefinition;
import graphql.language.EnumTypeExtensionDefinition;
import graphql.language.ImplementingTypeDefinition;
import graphql.language.InputObjectTypeExtensionDefinition;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.InterfaceTypeExtensionDefinition;
import graphql.language.ObjectTypeDefinition;
import graphql.language.ObjectTypeExtensionDefinition;
import graphql.language.OperationTypeDefinition;
import graphql.language.SDLDefinition;
import graphql.language.ScalarTypeDefinition;
import graphql.language.ScalarTypeExtensionDefinition;
import graphql.language.SchemaDefinition;
import graphql.language.SchemaExtensionDefinition;
import graphql.language.Type;
import graphql.language.TypeDefinition;
import graphql.language.TypeName;
import graphql.language.UnionTypeDefinition;
import graphql.language.UnionTypeExtensionDefinition;
import graphql.schema.idl.errors.DirectiveRedefinitionError;
import graphql.schema.idl.errors.SchemaProblem;
import graphql.schema.idl.errors.SchemaRedefinitionError;
import graphql.schema.idl.errors.TypeRedefinitionError;
import graphql.util.FpKit;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import static graphql.Assert.assertNotNull;
import static graphql.schema.idl.SchemaExtensionsChecker.defineOperationDefs;
import static graphql.schema.idl.SchemaExtensionsChecker.gatherOperationDefs;
import static graphql.schema.idl.TypeInfo.typeName;
import static java.util.Optional.ofNullable;
/**
* A {@link TypeDefinitionRegistry} contains the set of type definitions that come from compiling
* a graphql schema definition file via {@link SchemaParser#parse(String)}
*/
@SuppressWarnings("rawtypes")
@PublicApi
@NullMarked
public class TypeDefinitionRegistry implements Serializable {
protected final Map<String, List<ObjectTypeExtensionDefinition>> objectTypeExtensions;
protected final Map<String, List<InterfaceTypeExtensionDefinition>> interfaceTypeExtensions;
protected final Map<String, List<UnionTypeExtensionDefinition>> unionTypeExtensions;
protected final Map<String, List<EnumTypeExtensionDefinition>> enumTypeExtensions;
protected final Map<String, List<ScalarTypeExtensionDefinition>> scalarTypeExtensions;
protected final Map<String, List<InputObjectTypeExtensionDefinition>> inputObjectTypeExtensions;
protected final Map<String, TypeDefinition> types;
protected final Map<String, ScalarTypeDefinition> scalarTypes;
protected final Map<String, DirectiveDefinition> directiveDefinitions;
protected @Nullable SchemaDefinition schema;
protected final List<SchemaExtensionDefinition> schemaExtensionDefinitions;
protected final SchemaParseOrder schemaParseOrder;
public TypeDefinitionRegistry() {
objectTypeExtensions = new LinkedHashMap<>();
interfaceTypeExtensions = new LinkedHashMap<>();
unionTypeExtensions = new LinkedHashMap<>();
enumTypeExtensions = new LinkedHashMap<>();
scalarTypeExtensions = new LinkedHashMap<>();
inputObjectTypeExtensions = new LinkedHashMap<>();
types = new LinkedHashMap<>();
scalarTypes = new LinkedHashMap<>();
directiveDefinitions = new LinkedHashMap<>();
schemaExtensionDefinitions = new ArrayList<>();
schemaParseOrder = new SchemaParseOrder();
}
protected TypeDefinitionRegistry(Map<String, List<ObjectTypeExtensionDefinition>> objectTypeExtensions,
Map<String, List<InterfaceTypeExtensionDefinition>> interfaceTypeExtensions,
Map<String, List<UnionTypeExtensionDefinition>> unionTypeExtensions,
Map<String, List<EnumTypeExtensionDefinition>> enumTypeExtensions,
Map<String, List<ScalarTypeExtensionDefinition>> scalarTypeExtensions,
Map<String, List<InputObjectTypeExtensionDefinition>> inputObjectTypeExtensions,
Map<String, TypeDefinition> types,
Map<String, ScalarTypeDefinition> scalarTypes,
Map<String, DirectiveDefinition> directiveDefinitions,
List<SchemaExtensionDefinition> schemaExtensionDefinitions,
@Nullable SchemaDefinition schema,
SchemaParseOrder schemaParseOrder) {
this.objectTypeExtensions = objectTypeExtensions;
this.interfaceTypeExtensions = interfaceTypeExtensions;
this.unionTypeExtensions = unionTypeExtensions;
this.enumTypeExtensions = enumTypeExtensions;
this.scalarTypeExtensions = scalarTypeExtensions;
this.inputObjectTypeExtensions = inputObjectTypeExtensions;
this.types = types;
this.scalarTypes = scalarTypes;
this.directiveDefinitions = directiveDefinitions;
this.schemaExtensionDefinitions = schemaExtensionDefinitions;
this.schema = schema;
this.schemaParseOrder = schemaParseOrder;
}
/**
* @return an immutable view of this {@link TypeDefinitionRegistry} that is more performant
* when in read only mode.
*/
public ImmutableTypeDefinitionRegistry readOnly() {
if (this instanceof ImmutableTypeDefinitionRegistry) {
return (ImmutableTypeDefinitionRegistry) this;
}
return new ImmutableTypeDefinitionRegistry(this);
}
/**
* @return the order in which {@link SDLDefinition}s were parsed
*/
public SchemaParseOrder getParseOrder() {
return schemaParseOrder;
}
/**
* This will merge these type registries together and return this one
*
* @param typeRegistry the registry to be merged into this one
*
* @return this registry
*
* @throws SchemaProblem if there are problems merging the types such as redefinitions
*/
public TypeDefinitionRegistry merge(TypeDefinitionRegistry typeRegistry) throws SchemaProblem {
List<GraphQLError> errors = new ArrayList<>();
Map<String, TypeDefinition> tempTypes = new LinkedHashMap<>();
typeRegistry.types.values().forEach(newEntry -> {
Optional<GraphQLError> defined = define(this.types, tempTypes, newEntry);
defined.ifPresent(errors::add);
});
Map<String, DirectiveDefinition> tempDirectiveDefs = new LinkedHashMap<>();
typeRegistry.directiveDefinitions.values().forEach(newEntry -> {
Optional<GraphQLError> defined = define(this.directiveDefinitions, tempDirectiveDefs, newEntry);
defined.ifPresent(errors::add);
});
Map<String, ScalarTypeDefinition> tempScalarTypes = new LinkedHashMap<>();
typeRegistry.scalarTypes.values().forEach(newEntry -> define(this.scalarTypes, tempScalarTypes, newEntry).ifPresent(errors::add));
checkMergeSchemaDefs(typeRegistry, errors);
if (!errors.isEmpty()) {
throw new SchemaProblem(errors);
}
if (this.schema == null) {
// ensure schema is not overwritten by merge
this.schema = typeRegistry.schema;
schemaParseOrder.addDefinition(typeRegistry.schema);
}
this.schemaExtensionDefinitions.addAll(typeRegistry.schemaExtensionDefinitions);
typeRegistry.schemaExtensionDefinitions.forEach(schemaParseOrder::addDefinition);
// ok commit to the merge
this.types.putAll(tempTypes);
this.scalarTypes.putAll(tempScalarTypes);
this.directiveDefinitions.putAll(tempDirectiveDefs);
//
// merge type extensions since they can be redefined by design
typeRegistry.objectTypeExtensions.forEach((key, value) -> {
List<ObjectTypeExtensionDefinition> currentList = this.objectTypeExtensions
.computeIfAbsent(key, k -> new ArrayList<>());
currentList.addAll(value);
});
typeRegistry.interfaceTypeExtensions.forEach((key, value) -> {
List<InterfaceTypeExtensionDefinition> currentList = this.interfaceTypeExtensions
.computeIfAbsent(key, k -> new ArrayList<>());
currentList.addAll(value);
});
typeRegistry.unionTypeExtensions.forEach((key, value) -> {
List<UnionTypeExtensionDefinition> currentList = this.unionTypeExtensions
.computeIfAbsent(key, k -> new ArrayList<>());
currentList.addAll(value);
});
typeRegistry.enumTypeExtensions.forEach((key, value) -> {
List<EnumTypeExtensionDefinition> currentList = this.enumTypeExtensions
.computeIfAbsent(key, k -> new ArrayList<>());
currentList.addAll(value);
});
typeRegistry.scalarTypeExtensions.forEach((key, value) -> {
List<ScalarTypeExtensionDefinition> currentList = this.scalarTypeExtensions
.computeIfAbsent(key, k -> new ArrayList<>());
currentList.addAll(value);
});
typeRegistry.inputObjectTypeExtensions.forEach((key, value) -> {
List<InputObjectTypeExtensionDefinition> currentList = this.inputObjectTypeExtensions
.computeIfAbsent(key, k -> new ArrayList<>());
currentList.addAll(value);
});
return this;
}
private Map<String, OperationTypeDefinition> checkMergeSchemaDefs(TypeDefinitionRegistry toBeMergedTypeRegistry, List<GraphQLError> errors) {
if (toBeMergedTypeRegistry.schema != null && this.schema != null) {
errors.add(new SchemaRedefinitionError(this.schema, toBeMergedTypeRegistry.schema));
}
Map<String, OperationTypeDefinition> tempOperationDefs = gatherOperationDefs(errors, this.schema, this.schemaExtensionDefinitions);
Map<String, OperationTypeDefinition> mergedOperationDefs = gatherOperationDefs(errors, toBeMergedTypeRegistry.schema, toBeMergedTypeRegistry.schemaExtensionDefinitions);
defineOperationDefs(errors, mergedOperationDefs.values(), tempOperationDefs);
return tempOperationDefs;
}
private Optional<GraphQLError> checkAddOperationDefs() {
List<GraphQLError> errors = new ArrayList<>();
gatherOperationDefs(errors, this.schema, this.schemaExtensionDefinitions);
if (!errors.isEmpty()) {
return Optional.of(errors.get(0));
}
return Optional.empty();
}
/**
* Adds a a collections of definitions to the registry
*
* @param definitions the definitions to add
*
* @return an optional error for the first problem, typically type redefinition
*/
public Optional<GraphQLError> addAll(Collection<SDLDefinition> definitions) {
for (SDLDefinition definition : definitions) {
Optional<GraphQLError> error = add(definition);
if (error.isPresent()) {
return error;
}
}
return Optional.empty();
}
/**
* Adds a definition to the registry
*
* @param definition the definition to add
*
* @return an optional error
*/
public Optional<GraphQLError> add(SDLDefinition definition) {
// extensions
if (definition instanceof ObjectTypeExtensionDefinition) {
ObjectTypeExtensionDefinition newEntry = (ObjectTypeExtensionDefinition) definition;
return defineExt(objectTypeExtensions, newEntry, ObjectTypeExtensionDefinition::getName);
} else if (definition instanceof InterfaceTypeExtensionDefinition) {
InterfaceTypeExtensionDefinition newEntry = (InterfaceTypeExtensionDefinition) definition;
return defineExt(interfaceTypeExtensions, newEntry, InterfaceTypeExtensionDefinition::getName);
} else if (definition instanceof UnionTypeExtensionDefinition) {
UnionTypeExtensionDefinition newEntry = (UnionTypeExtensionDefinition) definition;
return defineExt(unionTypeExtensions, newEntry, UnionTypeExtensionDefinition::getName);
} else if (definition instanceof EnumTypeExtensionDefinition) {
EnumTypeExtensionDefinition newEntry = (EnumTypeExtensionDefinition) definition;
return defineExt(enumTypeExtensions, newEntry, EnumTypeExtensionDefinition::getName);
} else if (definition instanceof ScalarTypeExtensionDefinition) {
ScalarTypeExtensionDefinition newEntry = (ScalarTypeExtensionDefinition) definition;
return defineExt(scalarTypeExtensions, newEntry, ScalarTypeExtensionDefinition::getName);
} else if (definition instanceof InputObjectTypeExtensionDefinition) {
InputObjectTypeExtensionDefinition newEntry = (InputObjectTypeExtensionDefinition) definition;
return defineExt(inputObjectTypeExtensions, newEntry, InputObjectTypeExtensionDefinition::getName);
} else if (definition instanceof SchemaExtensionDefinition) {
schemaExtensionDefinitions.add((SchemaExtensionDefinition) definition);
schemaParseOrder.addDefinition(definition);
Optional<GraphQLError> error = checkAddOperationDefs();
if (error.isPresent()) {
return error;
}
} else if (definition instanceof ScalarTypeDefinition) {
ScalarTypeDefinition newEntry = (ScalarTypeDefinition) definition;
return define(scalarTypes, scalarTypes, newEntry);
} else if (definition instanceof TypeDefinition) {
TypeDefinition newEntry = (TypeDefinition) definition;
return define(types, types, newEntry);
} else if (definition instanceof DirectiveDefinition) {
DirectiveDefinition newEntry = (DirectiveDefinition) definition;
return define(directiveDefinitions, directiveDefinitions, newEntry);
} else if (definition instanceof SchemaDefinition) {
SchemaDefinition newSchema = (SchemaDefinition) definition;
if (schema != null) {
return Optional.of(new SchemaRedefinitionError(this.schema, newSchema));
} else {
schema = newSchema;
schemaParseOrder.addDefinition(newSchema);
}
Optional<GraphQLError> error = checkAddOperationDefs();
if (error.isPresent()) {
return error;
}
} else {
return Assert.assertShouldNeverHappen();
}
return Optional.empty();
}
/**
* Removes a {@code SDLDefinition} from the definition list.
*
* @param definition the definition to remove
*/
public void remove(SDLDefinition definition) {
assertNotNull(definition, () -> "definition to remove can't be null");
schemaParseOrder.removeDefinition(definition);
if (definition instanceof ObjectTypeExtensionDefinition) {
removeFromList(objectTypeExtensions, (TypeDefinition) definition);
} else if (definition instanceof InterfaceTypeExtensionDefinition) {
removeFromList(interfaceTypeExtensions, (TypeDefinition) definition);
} else if (definition instanceof UnionTypeExtensionDefinition) {
removeFromList(unionTypeExtensions, (TypeDefinition) definition);
} else if (definition instanceof EnumTypeExtensionDefinition) {
removeFromList(enumTypeExtensions, (TypeDefinition) definition);
} else if (definition instanceof ScalarTypeExtensionDefinition) {
removeFromList(scalarTypeExtensions, (TypeDefinition) definition);
} else if (definition instanceof InputObjectTypeExtensionDefinition) {
removeFromList(inputObjectTypeExtensions, (TypeDefinition) definition);
} else if (definition instanceof ScalarTypeDefinition) {
scalarTypes.remove(((ScalarTypeDefinition) definition).getName());
} else if (definition instanceof TypeDefinition) {
types.remove(((TypeDefinition) definition).getName());
} else if (definition instanceof DirectiveDefinition) {
directiveDefinitions.remove(((DirectiveDefinition) definition).getName());
} else if (definition instanceof SchemaExtensionDefinition) {
schemaExtensionDefinitions.remove(definition);
} else if (definition instanceof SchemaDefinition) {
schema = null;
} else {
Assert.assertShouldNeverHappen();
}
}
private void removeFromList(Map source, TypeDefinition value) {
//noinspection unchecked
List<TypeDefinition> list = (List<TypeDefinition>) source.get(value.getName());
if (list == null) {
return;
}
list.remove(value);
if (list.isEmpty()) {
source.remove(value.getName());
}
}
/**
* Removes a {@code SDLDefinition} from a map.
*
* @param key the key to remove
* @param definition the definition to remove
*/
public void remove(String key, SDLDefinition definition) {
assertNotNull(definition, () -> "definition to remove can't be null");
assertNotNull(key, () -> "key to remove can't be null");
schemaParseOrder.removeDefinition(definition);
if (definition instanceof ObjectTypeExtensionDefinition) {
removeFromMap(objectTypeExtensions, key);
} else if (definition instanceof InterfaceTypeExtensionDefinition) {
removeFromMap(interfaceTypeExtensions, key);
} else if (definition instanceof UnionTypeExtensionDefinition) {
removeFromMap(unionTypeExtensions, key);
} else if (definition instanceof EnumTypeExtensionDefinition) {
removeFromMap(enumTypeExtensions, key);
} else if (definition instanceof ScalarTypeExtensionDefinition) {
removeFromMap(scalarTypeExtensions, key);
} else if (definition instanceof InputObjectTypeExtensionDefinition) {
removeFromMap(inputObjectTypeExtensions, key);
} else if (definition instanceof ScalarTypeDefinition) {
removeFromMap(scalarTypes, key);
} else if (definition instanceof TypeDefinition) {
removeFromMap(types, key);
} else if (definition instanceof DirectiveDefinition) {
removeFromMap(directiveDefinitions, key);
} else if (definition instanceof SchemaExtensionDefinition) {
schemaExtensionDefinitions.remove(definition);
} else if (definition instanceof SchemaDefinition) {
schema = null;
} else {
Assert.assertShouldNeverHappen();
}
}
private void removeFromMap(Map source, String key) {
if (source == null) {
return;
}
source.remove(key);
}
private <T extends TypeDefinition> Optional<GraphQLError> define(Map<String, T> source, Map<String, T> target, T newEntry) {
String name = newEntry.getName();
T olderEntry = source.get(name);
if (olderEntry != null) {
return Optional.of(handleReDefinition(olderEntry, newEntry));
} else {
target.put(name, newEntry);
schemaParseOrder.addDefinition(newEntry);
}
return Optional.empty();
}
private <T extends DirectiveDefinition> Optional<GraphQLError> define(Map<String, T> source, Map<String, T> target, T newEntry) {
String name = newEntry.getName();
T olderEntry = source.get(name);
if (olderEntry != null) {
return Optional.of(handleReDefinition(olderEntry, newEntry));
} else {
target.put(name, newEntry);
schemaParseOrder.addDefinition(newEntry);
}
return Optional.empty();
}
private <T extends TypeDefinition> Optional<GraphQLError> defineExt(Map<String, List<T>> typeExtensions, T newEntry, Function<T, String> namerFunc) {
List<T> currentList = typeExtensions.computeIfAbsent(namerFunc.apply(newEntry), k -> new ArrayList<>());
currentList.add(newEntry);
schemaParseOrder.addDefinition(newEntry);
return Optional.empty();
}
public Map<String, TypeDefinition> types() {
return new LinkedHashMap<>(types);
}
public Map<String, ScalarTypeDefinition> scalars() {
LinkedHashMap<String, ScalarTypeDefinition> scalars = new LinkedHashMap<>(ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS);
scalars.putAll(scalarTypes);
return scalars;
}
public Map<String, List<ObjectTypeExtensionDefinition>> objectTypeExtensions() {
return new LinkedHashMap<>(objectTypeExtensions);
}
public Map<String, List<InterfaceTypeExtensionDefinition>> interfaceTypeExtensions() {
return new LinkedHashMap<>(interfaceTypeExtensions);
}
public Map<String, List<UnionTypeExtensionDefinition>> unionTypeExtensions() {
return new LinkedHashMap<>(unionTypeExtensions);
}
public Map<String, List<EnumTypeExtensionDefinition>> enumTypeExtensions() {
return new LinkedHashMap<>(enumTypeExtensions);
}
public Map<String, List<ScalarTypeExtensionDefinition>> scalarTypeExtensions() {
return new LinkedHashMap<>(scalarTypeExtensions);
}
public Map<String, List<InputObjectTypeExtensionDefinition>> inputObjectTypeExtensions() {
return new LinkedHashMap<>(inputObjectTypeExtensions);
}
public Optional<SchemaDefinition> schemaDefinition() {
return ofNullable(schema);
}
public List<SchemaExtensionDefinition> getSchemaExtensionDefinitions() {
return new ArrayList<>(schemaExtensionDefinitions);
}
private GraphQLError handleReDefinition(TypeDefinition oldEntry, TypeDefinition newEntry) {
return new TypeRedefinitionError(newEntry, oldEntry);
}
private GraphQLError handleReDefinition(DirectiveDefinition oldEntry, DirectiveDefinition newEntry) {
return new DirectiveRedefinitionError(newEntry, oldEntry);
}
public Optional<DirectiveDefinition> getDirectiveDefinition(String directiveName) {
return Optional.ofNullable(directiveDefinitions.get(directiveName));
}
public Map<String, DirectiveDefinition> getDirectiveDefinitions() {
return new LinkedHashMap<>(directiveDefinitions);
}
/**
* Returns true if the registry has a type of the specified {@link TypeName}
*
* @param typeName the type name to check
*
* @return true if the registry has a type by that type name
*/
public boolean hasType(TypeName typeName) {
String name = typeName.getName();
return hasType(name);
}
/**
* Returns true if the registry has a type of the specified name
*
* @param name the name to check
*
* @return true if the registry has a type by that name
*/
public boolean hasType(String name) {
return types.containsKey(name) || ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.containsKey(name) || scalarTypes.containsKey(name) || objectTypeExtensions.containsKey(name);
}
/**
* Returns an optional {@link TypeDefinition} of the specified type or {@link Optional#empty()}
*
* @param type the type to check
*
* @return an optional {@link TypeDefinition} or empty if it's not found
*
* @deprecated use the {@link #getTypeOrNull(Type)} variants instead since they avoid the allocation of an
* optional object
*/
@Deprecated(since = "2025-07-7")
public Optional<TypeDefinition> getType(Type type) {
return Optional.ofNullable(getTypeOrNull(type));
}
/**
* Returns an optional {@link TypeDefinition} of the specified type with the specified class or {@link Optional#empty()}
*
* @param type the type to check
* @param ofType the class of {@link TypeDefinition}
*
* @return an optional {@link TypeDefinition} or empty if it's not found
*
* @deprecated use the {@link #getTypeOrNull(Type)} variants instead since they avoid the allocation of an
* optional object
*/
@Deprecated(since = "2025-07-7")
public <T extends TypeDefinition> Optional<T> getType(Type type, Class<T> ofType) {
return Optional.ofNullable(getTypeOrNull(typeName(type), ofType));
}
/**
* Returns an optional {@link TypeDefinition} of the specified type name or {@link Optional#empty()}
*
* @param typeName the type to check
*
* @return an optional {@link TypeDefinition} or empty if it's not found
*
* @deprecated use the {@link #getTypeOrNull(Type)} variants instead since they avoid the allocation of an
* optional object
*/
@Deprecated(since = "2025-07-7")
public Optional<TypeDefinition> getType(String typeName) {
return Optional.ofNullable(getTypeOrNull(typeName));
}
/**
* Returns an optional {@link TypeDefinition} of the specified type name with the specified class or {@link Optional#empty()}
*
* @param typeName the type to check
* @param ofType the class of {@link TypeDefinition}
*
* @deprecated use the {@link #getTypeOrNull(Type)} variants instead since they avoid the allocation of an
* optional object
*/
@Deprecated(since = "2025-07-7")
public <T extends TypeDefinition> Optional<T> getType(String typeName, Class<T> ofType) {
return Optional.ofNullable(getTypeOrNull(typeName, ofType));
}
/**
* Returns a {@link TypeDefinition} of the specified type or null
*
* @param type the type to check
*
* @return a {@link TypeDefinition} or null if it's not found
*/
@Nullable
public TypeDefinition getTypeOrNull(Type type) {
return getTypeOrNull(typeName(type));
}
/**
* Returns a {@link TypeDefinition} of the specified type with the specified class or null
*
* @param type the type to check
* @param ofType the class of {@link TypeDefinition}
*
* @return a {@link TypeDefinition} or null if it's not found
*/
@Nullable
public <T extends TypeDefinition> T getTypeOrNull(Type type, Class<T> ofType) {
return getTypeOrNull(typeName(type), ofType);
}
/**
* Returns a {@link TypeDefinition} of the specified name or null
*
* @param typeName the type name to check
*
* @return a {@link TypeDefinition} or null if it's not found
*/
@Nullable
public TypeDefinition getTypeOrNull(String typeName) {
TypeDefinition<?> typeDefinition = types.get(typeName);
if (typeDefinition != null) {
return typeDefinition;
}
typeDefinition = scalars().get(typeName);
return typeDefinition;
}
/**
* Returns a {@link TypeDefinition} of the specified name and class or null
*
* @param typeName the type name to check
* @param ofType the class of {@link TypeDefinition}
*
* @return a {@link TypeDefinition} or null if it's not found
*/
@Nullable
public <T extends TypeDefinition> T getTypeOrNull(String typeName, Class<T> ofType) {
TypeDefinition type = getTypeOrNull(typeName);
if (type != null) {
if (type.getClass().equals(ofType)) {
//noinspection unchecked
return (T) type;
}
}
return null;
}
/**
* Returns true if the specified type exists in the registry and is an abstract (Interface or Union) type
*
* @param type the type to check
*
* @return true if its abstract
*/
public boolean isInterfaceOrUnion(Type type) {
TypeDefinition typeDefinition = getTypeOrNull(type);
if (typeDefinition != null) {
return typeDefinition instanceof UnionTypeDefinition || typeDefinition instanceof InterfaceTypeDefinition;
}
return false;
}
/**
* Returns true if the specified type exists in the registry and is an object type or interface
*
* @param type the type to check
*
* @return true if its an object type or interface
*/
public boolean isObjectTypeOrInterface(Type type) {
TypeDefinition typeDefinition = getTypeOrNull(type);
if (typeDefinition != null) {
return typeDefinition instanceof ObjectTypeDefinition || typeDefinition instanceof InterfaceTypeDefinition;
}
return false;
}
/**
* Returns true if the specified type exists in the registry and is an object type
*
* @param type the type to check
*
* @return true if its an object type
*/
public boolean isObjectType(Type type) {
return getTypeOrNull(type, ObjectTypeDefinition.class) != null;
}
/**
* Returns a list of types in the registry of that specified class
*
* @param targetClass the class to search for
* @param <T> must extend TypeDefinition
*
* @return a list of types of the target class
*/
public <T extends TypeDefinition> List<T> getTypes(Class<T> targetClass) {
return ImmutableKit.filterAndMap(types.values(),
targetClass::isInstance,
targetClass::cast
);
}
/**
* Returns a map of types in the registry of that specified class keyed by name
*
* @param targetClass the class to search for
* @param <T> must extend TypeDefinition
*
* @return a map of types
*/
public <T extends TypeDefinition> Map<String, T> getTypesMap(Class<T> targetClass) {
List<T> list = getTypes(targetClass);
return FpKit.getByName(list, TypeDefinition::getName, FpKit.mergeFirst());
}
/**
* Returns the list of object and interface types that implement the given interface type
*
* @param targetInterface the target to search for
*
* @return the list of object types that implement the given interface type
*
* @see TypeDefinitionRegistry#getImplementationsOf(InterfaceTypeDefinition)
*/
public List<ImplementingTypeDefinition> getAllImplementationsOf(InterfaceTypeDefinition targetInterface) {
return ImmutableKit.filter(
getTypes(ImplementingTypeDefinition.class),
implementingTypeDefinition -> {
List<Type<?>> implementsList = implementingTypeDefinition.getImplements();
for (Type iFace : implementsList) {
InterfaceTypeDefinition interfaceTypeDef = getTypeOrNull(iFace, InterfaceTypeDefinition.class);
if (interfaceTypeDef != null) {
if (interfaceTypeDef.getName().equals(targetInterface.getName())) {
return true;
}
}
}
return false;
});
}
/**
* Returns the list of object interface types that implement the given interface type
*
* @param targetInterface the target to search for
*
* @return the list of object types that implement the given interface type
*
* @see TypeDefinitionRegistry#getAllImplementationsOf(InterfaceTypeDefinition)
*/
public List<ObjectTypeDefinition> getImplementationsOf(InterfaceTypeDefinition targetInterface) {
return ImmutableKit.filterAndMap(
getAllImplementationsOf(targetInterface),
typeDefinition -> typeDefinition instanceof ObjectTypeDefinition,
typeDefinition -> (ObjectTypeDefinition) typeDefinition
);
}
/**
* Returns true of the abstract type is in implemented by the object type or interface
*
* @param abstractType the abstract type to check (interface or union)
* @param possibleType the object type or interface to check
*
* @return true if the object type or interface implements the abstract type
*/
public boolean isPossibleType(Type abstractType, Type possibleType) {
if (!isInterfaceOrUnion(abstractType)) {
return false;
}
if (!isObjectTypeOrInterface(possibleType)) {
return false;
}
TypeDefinition targetObjectTypeDef = Objects.requireNonNull(getTypeOrNull(possibleType));
TypeDefinition abstractTypeDef = Objects.requireNonNull(getTypeOrNull(abstractType));
if (abstractTypeDef instanceof UnionTypeDefinition) {
List<Type> memberTypes = ((UnionTypeDefinition) abstractTypeDef).getMemberTypes();
for (Type memberType : memberTypes) {
ObjectTypeDefinition checkType = getTypeOrNull(memberType, ObjectTypeDefinition.class);
if (checkType != null) {
if (checkType.getName().equals(targetObjectTypeDef.getName())) {
return true;
}
}
}
return false;
} else {
InterfaceTypeDefinition iFace = (InterfaceTypeDefinition) abstractTypeDef;
for (TypeDefinition<?> t : types.values()) {
if (t instanceof ImplementingTypeDefinition) {
if (t.getName().equals(targetObjectTypeDef.getName())) {
ImplementingTypeDefinition<?> itd = (ImplementingTypeDefinition<?>) t;
for (Type implementsType : itd.getImplements()) {
TypeDefinition<?> matchingInterface = types.get(typeName(implementsType));
if (matchingInterface != null && matchingInterface.getName().equals(iFace.getName())) {
return true;
}
}
}
}
}
return false;
}
}
/**
* Returns true if the maybe type is either equal or a subset of the second super type (covariant).
*
* @param maybeSubType the type to check
* @param superType the equality checked type
*
* @return true if maybeSubType is covariant or equal to superType
*/
@SuppressWarnings("SimplifiableIfStatement")
public boolean isSubTypeOf(Type maybeSubType, Type superType) {
TypeInfo maybeSubTypeInfo = TypeInfo.typeInfo(maybeSubType);
TypeInfo superTypeInfo = TypeInfo.typeInfo(superType);
// Equivalent type is a valid subtype
if (maybeSubTypeInfo.equals(superTypeInfo)) {
return true;
}
// If superType is non-null, maybeSubType must also be non-null.
if (superTypeInfo.isNonNull()) {
if (maybeSubTypeInfo.isNonNull()) {
return isSubTypeOf(maybeSubTypeInfo.unwrapOneType(), superTypeInfo.unwrapOneType());
}
return false;
}
if (maybeSubTypeInfo.isNonNull()) {
// If superType is nullable, maybeSubType may be non-null or nullable.
return isSubTypeOf(maybeSubTypeInfo.unwrapOneType(), superType);
}
// If superType type is a list, maybeSubType type must also be a list.
if (superTypeInfo.isList()) {
if (maybeSubTypeInfo.isList()) {
return isSubTypeOf(maybeSubTypeInfo.unwrapOneType(), superTypeInfo.unwrapOneType());
}
return false;
}
if (maybeSubTypeInfo.isList()) {
// If superType is not a list, maybeSubType must also be not a list.
return false;
}
// If superType type is an abstract type, maybeSubType type may be a currently
// possible object type.
if (isInterfaceOrUnion(superType) &&
isObjectTypeOrInterface(maybeSubType) &&
isPossibleType(superType, maybeSubType)) {
return true;
}
// Otherwise, the child type is not a valid subtype of the parent type.
return false;
}
}