TypeRegistry.java
/*
* Copyright 2015-2016 Providence Authors
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.morimekta.providence.types;
import net.morimekta.providence.PEnumValue;
import net.morimekta.providence.PMessage;
import net.morimekta.providence.descriptor.PAnnotation;
import net.morimekta.providence.descriptor.PContainer;
import net.morimekta.providence.descriptor.PDeclaredDescriptor;
import net.morimekta.providence.descriptor.PDescriptor;
import net.morimekta.providence.descriptor.PDescriptorProvider;
import net.morimekta.providence.descriptor.PEnumDescriptor;
import net.morimekta.providence.descriptor.PList;
import net.morimekta.providence.descriptor.PMap;
import net.morimekta.providence.descriptor.PMessageDescriptor;
import net.morimekta.providence.descriptor.PPrimitive;
import net.morimekta.providence.descriptor.PService;
import net.morimekta.providence.descriptor.PServiceProvider;
import net.morimekta.providence.descriptor.PSet;
import net.morimekta.util.collect.UnmodifiableMap;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static java.util.Objects.requireNonNull;
/**
* Base class for type registry. The type registry holds type information, possibly
* with some context, and has methods to get type information out from the registry.
*/
public abstract class TypeRegistry {
protected TypeRegistry() {}
/**
* Get the declared type with the given name and program context.
*
* @param reference The reference to the type.
* @return Optional type descriptor.
*/
@Nonnull
public abstract Optional<PDeclaredDescriptor<?>> getDeclaredType(@Nonnull TypeReference reference);
/**
* Get a service definition from the name and program context.
*
* @param reference The service reference.
* @return Optional service descriptor.
*/
@Nonnull
public abstract Optional<PService> getService(@Nonnull TypeReference reference);
/**
* This is to check to find a constant given the const name and program context. This will
* return empty if the constant does not exist.
*
* @param reference The constant reference.
* @return The optional const value.
* @param <T> The returned value type.
*/
@Nonnull
public abstract <T> Optional<T> getConstantValue(@Nonnull TypeReference reference);
/**
* Get a type-definition for the given reference.
*
* @param reference The typedef reference.
* @return Optional typedef definition.
*/
@Nonnull
public abstract Optional<TypeReference> getTypedef(@Nonnull TypeReference reference);
public abstract List<PDeclaredDescriptor<?>> getDeclaredTypes();
/**
* @param program The program name.
* @return True if the program is known in this registry.
*/
public abstract boolean isKnownProgram(@Nonnull String program);
// ---- Default Methods ---
/**
* Get the declared type with the given name and program context.
*
* @param reference The reference to the type.
* @return The declared type descriptor.
*/
@Nonnull
public PDeclaredDescriptor requireDeclaredType(@Nonnull TypeReference reference) {
if (reference.isNativeType()) {
throw new IllegalArgumentException("Not a valid type name '" + reference + "'");
}
final TypeReference finalReference = finalTypeReference(reference);
if (finalReference.isNativeType()) {
throw new IllegalArgumentException("Declared name '" + reference.typeName + "' is defined to native type in program '" + reference + "'");
}
if (getService(finalReference).isPresent() ||
getConstantValue(finalReference).isPresent()) {
if (reference.equals(finalReference)) {
throw new IllegalArgumentException(
"Declared name '" + finalReference.typeName + "' is not a type in program '" +
finalReference.programName + "'");
} else {
throw new IllegalArgumentException(
"Declared name '" + finalReference.typeName + "' is not a type in program '" +
finalReference.programName + "' from typedef '" + reference + "'");
}
}
return getDeclaredType(finalReference).orElseThrow(
() -> {
if (isKnownProgram(finalReference.programName)) {
return new IllegalArgumentException("No type '" + finalReference.typeName + "' in program '" + finalReference.programName + "'");
}
return new IllegalArgumentException("No program '" + finalReference.programName + "' for type '" + finalReference + "'");
});
}
/**
* Get the declared message type.
*
* @param reference The type reference.
* @param <M> The message type.
* @return The message type descriptor.
*/
@Nonnull
@SuppressWarnings("unchecked")
public <M extends PMessage<M>>
PMessageDescriptor<M> requireMessageType(@Nonnull TypeReference reference) {
PDeclaredDescriptor descriptor = requireDeclaredType(reference);
if (descriptor instanceof PMessageDescriptor) {
return (PMessageDescriptor) descriptor;
}
throw new IllegalStateException("Not a message type " + reference + ", was " + descriptor.getType());
}
/**
* Get the declared enum type.
*
* @param reference The type reference.
* @param <E> The enum value type.
* @return The enum type descriptor.
*/
@Nonnull
@SuppressWarnings("unchecked")
public <E extends PEnumValue<E>>
PEnumDescriptor<E> requireEnumType(@Nonnull TypeReference reference) {
PDeclaredDescriptor descriptor = requireDeclaredType(reference);
if (descriptor instanceof PEnumDescriptor) {
return (PEnumDescriptor) descriptor;
}
throw new IllegalStateException("Not an enum type " + reference + ", was " + descriptor.getType());
}
/**
* Get the declared type with the given name and program context.
*
* @param reference The reference to the type.
* @return The service descriptor.
*/
@Nonnull
public PService requireService(@Nonnull TypeReference reference) {
if (reference.isNativeType()) {
throw new IllegalArgumentException("Not a valid service name '" + reference + "'");
}
if (getDeclaredType(reference).isPresent() ||
getTypedef(reference).isPresent()) {
throw new IllegalArgumentException("Declared name '" + reference.typeName + "' is not a service in program '" + reference.programName + "'");
}
return getService(reference).orElseThrow(
() -> {
if (isKnownProgram(reference.programName)) {
return new IllegalArgumentException("No service '" + reference.typeName + "' in program '" + reference.programName + "'");
}
return new IllegalArgumentException("No program '" + reference.programName + "' for service '" + reference + "'");
});
}
// --- Providers ---
/**
* Get a type provider for reference.
* @param reference Type reference to get provider for.
* @return The type provider.
*/
@Nonnull
public PDescriptorProvider getTypeProvider(@Nonnull TypeReference reference) {
return getTypeProvider(reference, UnmodifiableMap.mapOf());
}
@Nonnull
public PDescriptorProvider getTypeProvider(@Nonnull TypeReference reference,
@Nonnull Map<String, String> annotations) {
return () -> resolveType(reference, annotations);
}
@Nonnull
public PServiceProvider getServiceProvider(@Nonnull TypeReference reference) {
return () -> requireService(reference);
}
// --- Internal ---
/**
* Get the final typename of the given identifier within the context.
*
* @param reference The type name.
* @return The final type reference.
*/
@Nonnull
protected TypeReference finalTypeReference(@Nonnull TypeReference reference) {
if (reference.isMap()) {
return new TypeReference(reference.programName,
reference.typeName,
finalTypeReference(requireNonNull(reference.keyType)),
finalTypeReference(requireNonNull(reference.valueType)));
} else if (reference.isContainer()) {
return new TypeReference(reference.programName,
reference.typeName,
null,
finalTypeReference(requireNonNull(reference.valueType)));
}
Optional<TypeReference> def = getTypedef(reference);
return def.map(this::finalTypeReference).orElse(reference);
}
// --- Private ---
/**
* Resolve a type reference to the actual type.
*
* @param reference The type reference.
* @param annotations Type annotations.
* @return The resolved type descriptor.
*/
private PDescriptor resolveType(TypeReference reference, Map<String, String> annotations) {
reference = finalTypeReference(reference);
// Prepend package context to name
PPrimitive primitive = PPrimitive.findByName(reference.typeName);
if (primitive != null) {
return primitive;
} else if (reference.isMap()) {
PDescriptorProvider keyType = getTypeProvider(requireNonNull(reference.keyType), annotations);
PDescriptorProvider valueType = getTypeProvider(requireNonNull(reference.valueType), annotations);
switch (PContainer.typeForName(annotations.get(PAnnotation.CONTAINER.tag))) {
case SORTED:
return PMap.sortedProvider(keyType, valueType).descriptor();
case ORDERED:
return PMap.orderedProvider(keyType, valueType).descriptor();
case DEFAULT:
return PMap.provider(keyType, valueType).descriptor();
}
} else if (reference.isSet()) {
PDescriptorProvider valueType = getTypeProvider(requireNonNull(reference.valueType), annotations);
switch (PContainer.typeForName(annotations.get(PAnnotation.CONTAINER.tag))) {
case SORTED:
return PSet.sortedProvider(valueType).descriptor();
case ORDERED:
return PSet.orderedProvider(valueType).descriptor();
case DEFAULT:
return PSet.provider(valueType).descriptor();
}
} else if (reference.isList()) {
PDescriptorProvider valueType = getTypeProvider(requireNonNull(reference.valueType), annotations);
return PList.provider(valueType).descriptor();
}
// Otherwise it's a declared type.
return requireDeclaredType(reference);
}
}