blob: a1f1afd98c2d8c76b7201138b4e03248cdb524c8 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.v23.vdl;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import io.v.v23.security.BlessingPattern;
import io.v.v23.security.BlessingPatternNativeConverter;
import io.v.v23.security.Blessings;
import io.v.v23.security.BlessingsNativeConverter;
import io.v.v23.security.Discharge;
import io.v.v23.security.DischargeNativeConverter;
import io.v.v23.security.access.AccessList;
import io.v.v23.security.access.AccessListNativeConverter;
import io.v.v23.vdl.NativeTime.DateTimeConverter;
import io.v.v23.vdl.NativeTime.DurationConverter;
import io.v.v23.vdl.NativeTypes.Converter;
import io.v.v23.vdl.VdlType.Builder;
import io.v.v23.vdl.VdlType.PendingType;
import io.v.v23.verror.VException;
import io.v.v23.verror.VExceptionVdlConverter;
/**
* Types provides helpers to create VDL types.
*/
public final class Types {
/**
* The {@code VdlType} object representing the VDL type any, it is unnamed.
*/
public static final VdlType ANY = createPrimitiveType(Kind.ANY);
/**
* The {@code VdlType} object representing the VDL type bool, it is unnamed.
*/
public static final VdlType BOOL = createPrimitiveType(Kind.BOOL);
/**
* The {@code VdlType} object representing the VDL type byte, it is unnamed.
*/
public static final VdlType BYTE = createPrimitiveType(Kind.BYTE);
/**
* The {@code VdlType} object representing the VDL type uint16, it is unnamed.
*/
public static final VdlType UINT16 = createPrimitiveType(Kind.UINT16);
/**
* The {@code VdlType} object representing the VDL type uint32, it is unnamed.
*/
public static final VdlType UINT32 = createPrimitiveType(Kind.UINT32);
/**
* The {@code VdlType} object representing the VDL type uint64, it is unnamed.
*/
public static final VdlType UINT64 = createPrimitiveType(Kind.UINT64);
/**
* The {@code VdlType} object representing the VDL type int16, it is unnamed.
*/
public static final VdlType INT16 = createPrimitiveType(Kind.INT16);
/**
* The {@code VdlType} object representing the VDL type int32, it is unnamed.
*/
public static final VdlType INT32 = createPrimitiveType(Kind.INT32);
/**
* The {@code VdlType} object representing the VDL type int64, it is unnamed.
*/
public static final VdlType INT64 = createPrimitiveType(Kind.INT64);
/**
* The {@code VdlType} object representing the VDL type float32, it is unnamed.
*/
public static final VdlType FLOAT32 = createPrimitiveType(Kind.FLOAT32);
/**
* The {@code VdlType} object representing the VDL type float64, it is unnamed.
*/
public static final VdlType FLOAT64 = createPrimitiveType(Kind.FLOAT64);
/**
* The {@code VdlType} object representing the VDL type complex64, it is unnamed.
*/
public static final VdlType COMPLEX64 = createPrimitiveType(Kind.COMPLEX64);
/**
* The {@code VdlType} object representing the VDL type complex128, it is unnamed.
*/
public static final VdlType COMPLEX128 = createPrimitiveType(Kind.COMPLEX128);
/**
* The {@code VdlType} object representing the VDL type string, it is unnamed.
*/
public static final VdlType STRING = createPrimitiveType(Kind.STRING);
/**
* The {@code VdlType} object representing the VDL type typeObject, it is unnamed.
*/
public static final VdlType TYPEOBJECT = createPrimitiveType(Kind.TYPEOBJECT);
private static final Map<Type, VdlType> typeCache = new ConcurrentHashMap<Type, VdlType>();
private static final Map<VdlType, Type> typeRegistry = new ConcurrentHashMap<VdlType, Type>();
private static final Map<Type, Converter> nativeTypeRegistry =
new ConcurrentHashMap<Type, Converter>();
static {
typeCache.put(VdlAny.class, ANY);
typeCache.put(VdlBool.class, BOOL);
typeCache.put(VdlByte.class, BYTE);
typeCache.put(VdlUint16.class, UINT16);
typeCache.put(VdlUint32.class, UINT32);
typeCache.put(VdlUint64.class, UINT64);
typeCache.put(VdlInt16.class, INT16);
typeCache.put(VdlInt32.class, INT32);
typeCache.put(VdlInt64.class, INT64);
typeCache.put(VdlFloat32.class, FLOAT32);
typeCache.put(VdlFloat64.class, FLOAT64);
typeCache.put(VdlComplex64.class, COMPLEX64);
typeCache.put(VdlComplex128.class, COMPLEX128);
typeCache.put(VdlString.class, STRING);
typeCache.put(VdlTypeObject.class, TYPEOBJECT);
typeCache.put(Boolean.TYPE, BOOL);
typeCache.put(Boolean.class, BOOL);
typeCache.put(Byte.TYPE, BYTE);
typeCache.put(Byte.class, BYTE);
typeCache.put(Short.TYPE, INT16);
typeCache.put(Short.class, INT16);
typeCache.put(Integer.TYPE, INT32);
typeCache.put(Integer.class, INT32);
typeCache.put(Long.TYPE, INT64);
typeCache.put(Long.class, INT64);
typeCache.put(Float.TYPE, FLOAT32);
typeCache.put(Float.class, FLOAT32);
typeCache.put(Double.TYPE, FLOAT64);
typeCache.put(Double.class, FLOAT64);
typeCache.put(String.class, STRING);
// When registering native types, make sure to register "child" types first. For example,
// if VDL type A contains VDL type B and both have native types that you want to register
// here, you must register A before B.
registerNativeType(VException.class, VExceptionVdlConverter.INSTANCE);
registerNativeType(org.joda.time.DateTime.class, DateTimeConverter.INSTANCE);
registerNativeType(org.joda.time.Duration.class, DurationConverter.INSTANCE);
registerNativeType(Discharge.class, DischargeNativeConverter.INSTANCE);
registerNativeType(Blessings.class, BlessingsNativeConverter.INSTANCE);
registerNativeType(BlessingPattern.class, BlessingPatternNativeConverter.INSTANCE);
registerNativeType(AccessList.class, AccessListNativeConverter.INSTANCE);
}
private static void registerNativeType(Type nativeType, Converter converter) {
VdlType vdlType = getVdlTypeFromReflect(converter.getWireType());
typeCache.put(nativeType, vdlType);
typeRegistry.put(vdlType, nativeType);
nativeTypeRegistry.put(nativeType, converter);
}
private static VdlType createPrimitiveType(Kind kind) {
Builder builder = new Builder();
PendingType pending = builder.newPending(kind);
builder.build();
return pending.built();
}
/**
* Returns a {@code VdlType} object representing a VDL type of specified kind.
*/
public static VdlType primitiveTypeFromKind(Kind kind) {
switch (kind) {
case ANY:
return ANY;
case BOOL:
return BOOL;
case BYTE:
return BYTE;
case UINT16:
return UINT16;
case UINT32:
return UINT32;
case UINT64:
return UINT64;
case INT16:
return INT16;
case INT32:
return INT32;
case INT64:
return INT64;
case FLOAT32:
return FLOAT32;
case FLOAT64:
return FLOAT64;
case COMPLEX64:
return COMPLEX64;
case COMPLEX128:
return COMPLEX128;
case STRING:
return STRING;
case TYPEOBJECT:
return TYPEOBJECT;
default:
throw new RuntimeException("Unknown primitive kind " + kind);
}
}
/**
* A helper used to create a single VDL enum type.
*/
public static VdlType enumOf(String... labels) {
Builder builder = new Builder();
PendingType pending = builder.newPending(Kind.ENUM);
for (String label : labels) {
pending.addLabel(label);
}
builder.build();
return pending.built();
}
/**
* A helper used to create a single VDL fixed length array type.
*/
public static VdlType arrayOf(int len, VdlType elem) {
Builder builder = new Builder();
PendingType pending = builder.newPending(Kind.ARRAY).setLength(len).setElem(elem);
builder.build();
return pending.built();
}
/**
* A helper used to create a single VDL list type.
*/
public static VdlType listOf(VdlType elem) {
Builder builder = new Builder();
PendingType pending = builder.newPending(Kind.LIST).setElem(elem);
builder.build();
return pending.built();
}
/**
* A helper used to create a single VDL set type.
*/
public static VdlType setOf(VdlType key) {
Builder builder = new Builder();
PendingType pending = builder.newPending(Kind.SET).setKey(key);
builder.build();
return pending.built();
}
/**
* A helper used to create a single VDL map type.
*/
public static VdlType mapOf(VdlType key, VdlType elem) {
Builder builder = new Builder();
PendingType pending = builder.newPending(Kind.MAP).setKey(key).setElem(elem);
builder.build();
return pending.built();
}
/**
* A helper used to create a single VDL struct type.
*/
public static VdlType structOf(VdlField... fields) {
Builder builder = new Builder();
PendingType pending = builder.newPending(Kind.STRUCT);
for (VdlField field : fields) {
pending.addField(field.getName(), field.getType());
}
builder.build();
return pending.built();
}
/**
* A helper used to create a single VDL union type.
*/
public static VdlType unionOf(VdlField... fields) {
Builder builder = new Builder();
PendingType pending = builder.newPending(Kind.UNION);
for (VdlField field : fields) {
pending.addField(field.getName(), field.getType());
}
builder.build();
return pending.built();
}
/**
* A helper used to create a single VDL optional type.
*/
public static VdlType optionalOf(VdlType elem) {
Builder builder = new Builder();
PendingType pending = builder.newPending(Kind.OPTIONAL).setElem(elem);
builder.build();
return pending.built();
}
/**
* A helper used to create a single named VDL type based on another VDL type.
*/
public static VdlType named(String name, VdlType base) {
Builder builder = new Builder();
PendingType pending = builder.newPending().assignBase(base).setName(name);
builder.build();
return pending.built();
}
/**
* Returns a {@code NativeTypes.Converter} object for a provided java native type or null
* if there is no converter from provided java type to its VDL wire representation.
*/
public static NativeTypes.Converter getNativeTypeConverter(Type type) {
return nativeTypeRegistry.get(type);
}
/**
* Creates a {@code VdlType} object corresponding to a {@code java.lang.reflect.Type} object.
* Resolves maps, sets, lists, arrays, primitives and classes generated from *.vdl files.
* All results are statically cached. All named VDL types are also registered so that the
* corresponding {@code Type} object can be retrieved by calling {@code getReflectTypeForVdl}.
*
* @throws IllegalArgumentException if the VDL type can't be constructed
*/
public static VdlType getVdlTypeFromReflect(Type type) {
if (typeCache.containsKey(type)) {
return typeCache.get(type);
}
return synchronizedLookupOrBuildType(type);
}
/**
* Returns a {@code Type} object corresponding to VDL type.
* We look up named types that were built by calling {@code getVdlTypeFromReflect}, and build
* the unnamed types that have java native equivalent (all except array, enum, struct, union).
*
* @param vdlType the VDL type
* @return the {@code Type} object
* @throws IllegalArgumentException if the type can't be constructed
*/
public static Type getReflectTypeForVdl(VdlType vdlType) {
Type type = typeRegistry.get(vdlType);
if (type != null) {
return type;
}
if (!Strings.isNullOrEmpty(vdlType.getName())) { // named type
throw new IllegalArgumentException("Can't build java type for VDL type " + vdlType + " - named type is unregistered");
}
Type key, elem;
switch (vdlType.getKind()) {
case ARRAY:
case ENUM:
case STRUCT:
case UNION:
throw new IllegalArgumentException("Can't build java type for VDL type " + vdlType + " - illegal unnamed union");
case ANY:
return VdlAny.class;
case BOOL:
return Boolean.class;
case BYTE:
return Byte.class;
case COMPLEX128:
return VdlComplex128.class;
case COMPLEX64:
return VdlComplex64.class;
case FLOAT32:
return Float.class;
case FLOAT64:
return Double.class;
case INT16:
return Short.class;
case INT32:
return Integer.class;
case INT64:
return Long.class;
case LIST:
if (vdlType.getElem().getKind() == Kind.BYTE) {
return byte[].class;
}
elem = getReflectTypeForVdl(vdlType.getElem());
if (elem != null) {
return new ParameterizedTypeImpl(VdlList.class, elem);
}
throw new IllegalArgumentException("Can't build java type for VDL type " + vdlType + " - unknown list elem type");
case MAP:
key = getReflectTypeForVdl(vdlType.getKey());
elem = getReflectTypeForVdl(vdlType.getElem());
if (key != null && elem != null) {
return new ParameterizedTypeImpl(VdlMap.class, key, elem);
}
throw new IllegalArgumentException("Can't build java type for VDL type " + vdlType + " - unknown map key or elem type");
case OPTIONAL:
elem = getReflectTypeForVdl(vdlType.getElem());
if (elem != null) {
return new ParameterizedTypeImpl(VdlOptional.class, elem);
}
throw new IllegalArgumentException("Can't build java type for VDL type " + vdlType + " - unkown optional elem type");
case SET:
key = getReflectTypeForVdl(vdlType.getKey());
if (key != null) {
return new ParameterizedTypeImpl(VdlSet.class, key);
}
throw new IllegalArgumentException("Can't build java type for VDL type " + vdlType + " - unknown set key type");
case STRING:
return String.class;
case TYPEOBJECT:
return VdlTypeObject.class;
case UINT16:
return VdlUint16.class;
case UINT32:
return VdlUint32.class;
case UINT64:
return VdlUint64.class;
default:
throw new IllegalArgumentException("Unsupported VDL type: " + vdlType);
}
}
/**
* Tries to load a Java class that was generated from named VDL type.
*
* @param name the name of VDL type
* @return true iff the class was found
*/
public static boolean loadClassForVdlName(String name) {
String[] parts = name.split("/");
for (int i = 0; i < parts.length - 1; i++) {
List<String> subparts = Arrays.asList(parts[i].split("\\."));
Collections.reverse(subparts);
parts[i] = Joiner.on(".").join(subparts);
}
String className = Joiner.on(".").join(parts);
try {
// lookup and load class
Class.forName(className);
return true;
} catch (ClassNotFoundException | NoClassDefFoundError e) {
return false;
}
}
private static synchronized VdlType synchronizedLookupOrBuildType(Type type) {
if (typeCache.containsKey(type)) {
return typeCache.get(type);
}
ReflectToVdlTypeBuilder builder = new ReflectToVdlTypeBuilder();
PendingType pendingType = builder.lookupOrBuildPending(type);
builder.buildAndCache();
return pendingType.built();
}
/**
* Builds VdlType from {@code java.lang.reflect.Type}. All results are cached in typeCahce.
*/
private static final class ReflectToVdlTypeBuilder {
private final Builder builder;
private final Map<Type, PendingType> pendingTypes;
public ReflectToVdlTypeBuilder() {
builder = new Builder();
pendingTypes = new HashMap<Type, PendingType>();
}
public void buildAndCache() {
builder.build();
for (Map.Entry<Type, PendingType> entry : pendingTypes.entrySet()) {
Type reflectType = entry.getKey();
VdlType vdlType = entry.getValue().built();
typeCache.put(reflectType, vdlType);
if (!Strings.isNullOrEmpty(vdlType.getName())) {
typeRegistry.put(vdlType, reflectType);
}
}
}
public PendingType lookupOrBuildPending(Type type) {
PendingType vdlType = lookupType(type);
if (vdlType != null) {
return vdlType;
}
return buildPendingFromType(type);
}
private PendingType lookupType(Type type) {
if (typeCache.containsKey(type)) {
return builder.builtPendingFromType(typeCache.get(type));
}
if (pendingTypes.containsKey(type)) {
return pendingTypes.get(type);
}
return null;
}
private PendingType buildPendingFromType(Type type) {
Class<?> klass;
Type[] elementTypes;
if (type instanceof Class) {
klass = (Class<?>) type;
return buildPendingFromClass(klass);
} else if (type instanceof ParameterizedType) {
klass = (Class<?>) ((ParameterizedType) type).getRawType();
elementTypes = ((ParameterizedType) type).getActualTypeArguments();
} else if (type instanceof GenericArrayType) {
klass = List.class;
elementTypes = new Type[1];
elementTypes[0] = (((GenericArrayType) type).getGenericComponentType());
} else {
throw new IllegalArgumentException("Unable to create VDL Type for type " + type);
}
PendingType pending;
if (List.class.isAssignableFrom(klass)) {
pending = builder.listOf(lookupOrBuildPending(elementTypes[0]));
} else if (Set.class.isAssignableFrom(klass)) {
pending = builder.setOf(lookupOrBuildPending(elementTypes[0]));
} else if (Map.class.isAssignableFrom(klass)) {
pending = builder.mapOf(lookupOrBuildPending(elementTypes[0]),
lookupOrBuildPending(elementTypes[1]));
} else if (VdlOptional.class.isAssignableFrom(klass)) {
pending = builder.optionalOf(lookupOrBuildPending(elementTypes[0]));
} else {
throw new IllegalArgumentException("Unable to create VDL Type for type " + type);
}
pendingTypes.put(type, pending);
return pending;
}
private PendingType buildPendingFromClass(Class<?> klass) {
PendingType pending;
if (klass.isArray()) {
pending = builder.listOf(lookupOrBuildPending(klass.getComponentType()));
pendingTypes.put(klass, pending);
return pending;
}
if (klass.isAssignableFrom(List.class)) {
throw new IllegalArgumentException("Unable to create a VDL type from List.class." +
" Consider creating a type using a TypeToken.");
} else if (klass.isAssignableFrom(Set.class)) {
throw new IllegalArgumentException("Unable to create a VDL type from Set.class." +
" Consider creating a type using a TypeToken.");
} else if (klass.isAssignableFrom(Map.class)) {
throw new IllegalArgumentException("Unable to create a VDL type from Map.class." +
" Consider creating a type using a TypeToken.");
}
pending = builder.newPending();
pendingTypes.put(klass, pending);
Class<?> superClass = klass.getSuperclass();
if (superClass == VdlEnum.class) {
populateEnum(pending, klass);
} else if (superClass == AbstractVdlStruct.class) {
if (klass == VdlStruct.class) {
throw new IllegalArgumentException("Unable to create VDL Type for " + klass);
}
populateStruct(pending, klass);
} else if (superClass == VdlUnion.class) {
populateUnion(pending, klass);
} else if (superClass == VdlArray.class) {
populateArray(pending, klass);
} else if (superClass != null && superClass != Object.class) {
pending.assignBase(lookupOrBuildPending(klass.getGenericSuperclass()));
} else {
// Attempt to decode as a struct.
populateStruct(pending, klass);
}
GeneratedFromVdl annotation = klass.getAnnotation(GeneratedFromVdl.class);
if (annotation != null) {
pending.setName(annotation.name());
} else if (klass.getCanonicalName() != null){
pending.setName(klass.getCanonicalName());
}
return pending;
}
private void populateEnum(PendingType pending, Class<?> klass) {
pending.setKind(Kind.ENUM);
TreeMap<Integer, String> labels = new TreeMap<Integer, String>();
for (Field field : klass.getDeclaredFields()) {
GeneratedFromVdl annotation = field.getAnnotation(GeneratedFromVdl.class);
if (annotation != null) {
labels.put(annotation.index(), annotation.name());
}
}
for (Map.Entry<Integer, String> entry : labels.entrySet()) {
pending.addLabel(entry.getValue());
}
}
private void populateStruct(PendingType pending, Class<?> klass) {
pending.setKind(Kind.STRUCT);
TreeMap<Integer, PendingVdlField> fields = new TreeMap<Integer, PendingVdlField>();
// See if the struct has any annotations. If not, we assume user has provided
// a raw class and we try to guess what the annotations would be.
boolean hasFieldAnnotations = false;
for (Field field : klass.getDeclaredFields()) {
GeneratedFromVdl annotation = field.getAnnotation(GeneratedFromVdl.class);
if (annotation != null) {
hasFieldAnnotations = true;
break;
}
}
int fieldIndex = 0;
for (Field field : klass.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) { // skip static fields
continue;
}
if (Character.isUpperCase(field.getName().charAt(0))) {
throw new IllegalArgumentException("Java field names must be lower-cased");
}
GeneratedFromVdl annotation = field.getAnnotation(GeneratedFromVdl.class);
if (annotation != null) {
fields.put(annotation.index(), new PendingVdlField(annotation.name(),
lookupOrBuildPending(field.getGenericType())));
} else if (!hasFieldAnnotations) {
fields.put(++fieldIndex, new PendingVdlField(firstCharToUpper(field.getName()),
lookupOrBuildPending(field.getGenericType())));
}
}
for (Map.Entry<Integer, PendingVdlField> entry : fields.entrySet()) {
pending.addField(entry.getValue().name, entry.getValue().type);
}
}
private void populateUnion(PendingType pending, Class<?> klass) {
pending.setKind(Kind.UNION);
TreeMap<Integer, PendingVdlField> fields = new TreeMap<Integer, PendingVdlField>();
for (Class<?> unionClass : klass.getDeclaredClasses()) {
GeneratedFromVdl annotation = unionClass.getAnnotation(GeneratedFromVdl.class);
if (annotation == null) {
continue;
}
Type type;
try {
type = unionClass.getDeclaredField("elem").getGenericType();
} catch (Exception e) {
throw new IllegalArgumentException(
"Unable to create VDL Type for type " + klass, e);
}
String name = annotation.name().substring(annotation.name().lastIndexOf('$') + 1);
fields.put(annotation.index(),
new PendingVdlField(name, lookupOrBuildPending(type)));
}
for (Map.Entry<Integer, PendingVdlField> entry : fields.entrySet()) {
pending.addField(entry.getValue().name, entry.getValue().type);
}
}
private void populateArray(PendingType pending, Class<?> klass) {
pending.setKind(Kind.ARRAY);
Type elementType = ((ParameterizedType) klass.getGenericSuperclass())
.getActualTypeArguments()[0];
pending.setElem(lookupOrBuildPending(elementType));
try {
ArrayLength length = klass.getAnnotation(ArrayLength.class);
pending.setLength(length.value());
} catch (Exception e) {
throw new IllegalArgumentException(
"Unable to create VDL Type for type " + klass, e);
}
}
private static String firstCharToUpper(String str) {
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
}
private static final class PendingVdlField {
final String name;
final PendingType type;
public PendingVdlField(String name, PendingType type) {
this.name = name;
this.type = type;
}
}
}
/**
* A helper class used to create {@code Type} instances for VDL types.
*/
private static class ParameterizedTypeImpl implements ParameterizedType {
private final Type rawType;
private final Type[] arguments;
public ParameterizedTypeImpl(Type rawType, Type ... arguments) {
this.rawType = rawType;
this.arguments = arguments;
}
@Override
public Type[] getActualTypeArguments() {
return arguments;
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type getOwnerType() {
return null;
}
}
}