package org.elasticsearch.plan.a;
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.
*/
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
class Definition {
enum TypeMetadata {
VOID( void.class , 0 , false , false , false ),
BOOL( boolean.class , 1 , false , true , false ),
BYTE( byte.class , 1 , true , true , false ),
SHORT( short.class , 1 , true , true , false ),
CHAR( char.class , 1 , true , true , false ),
INT( int.class , 1 , true , true , false ),
LONG( long.class , 2 , true , true , false ),
FLOAT( float.class , 1 , true , true , false ),
DOUBLE( double.class , 2 , true , true , false ),
OBJECT( null , 1 , false , false , true ),
STRING( String.class , 1 , false , true , true ),
ARRAY( null , 1 , false , false , true );
final Class<?> clazz;
final int size;
final boolean numeric;
final boolean constant;
final boolean object;
TypeMetadata(final Class<?> clazz, final int size, final boolean numeric,
final boolean constant, final boolean object) {
this.clazz = clazz;
this.size = size;
this.numeric = numeric;
this.constant = constant;
this.object = object;
}
}
static class Type {
final String name;
final Struct struct;
final Class<?> clazz;
final String internal;
final String descriptor;
final int dimensions;
final TypeMetadata metadata;
Type(final String name, final Struct struct, final Class<?> clazz, final String internal,
final String descriptor, final int dimensions, final TypeMetadata metadata) {
this.name = name;
this.struct = struct;
this.clazz = clazz;
this.internal = internal;
this.descriptor = descriptor;
this.dimensions = dimensions;
this.metadata = metadata;
}
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
final Type type = (Type)object;
return dimensions == type.dimensions && struct.equals(type.struct);
}
@Override
public int hashCode() {
int result = struct.hashCode();
result = 31 * result + dimensions;
return result;
}
}
static class Constructor {
final String name;
final Struct owner;
final List<Type> arguments;
final List<Type> originals;
final java.lang.reflect.Constructor constructor;
final String descriptor;
private Constructor(final String name, final Struct owner,
final List<Type> arguments, final List<Type> originals,
final java.lang.reflect.Constructor constructor, final String descriptor) {
this.name = name;
this.owner = owner;
this.arguments = Collections.unmodifiableList(arguments);
this.originals = Collections.unmodifiableList(originals);
this.constructor = constructor;
this.descriptor = descriptor;
}
}
static class Method {
final String name;
final Struct owner;
final Type rtn;
final Type oreturn;
final List<Type> arguments;
final List<Type> originals;
final java.lang.reflect.Method method;
final String descriptor;
private Method(final String name, final Struct owner, final Type rtn, final Type oreturn,
final List<Type> arguments, final List<Type> originals,
final java.lang.reflect.Method method, final String descriptor) {
this.name = name;
this.owner = owner;
this.rtn = rtn;
this.oreturn = oreturn;
this.arguments = Collections.unmodifiableList(arguments);
this.originals = Collections.unmodifiableList(originals);
this.method = method;
this.descriptor = descriptor;
}
}
static class Field {
final String name;
final Struct owner;
final Type type;
final java.lang.reflect.Field field;
private Field(final String name, final Struct owner, final Type type, final java.lang.reflect.Field field) {
this.name = name;
this.owner = owner;
this.type = type;
this.field = field;
}
}
static class Struct {
final String name;
final Class<?> clazz;
final String internal;
final boolean generic;
final boolean runtime;
final Map<String, Constructor> constructors;
final Map<String, Method> functions;
final Map<String, Method> methods;
final Map<String, Field> statics;
final Map<String, Field> members;
private Struct(final String name, final Class<?> clazz, final String internal,
final boolean generic, final boolean runtime) {
this.name = name;
this.clazz = clazz;
this.internal = internal;
this.generic = generic;
this.runtime = runtime;
constructors = new HashMap<>();
functions = new HashMap<>();
methods = new HashMap<>();
statics = new HashMap<>();
members = new HashMap<>();
}
private Struct(final Struct struct) {
name = struct.name;
clazz = struct.clazz;
internal = struct.internal;
generic = struct.generic;
runtime = struct.runtime;
constructors = Collections.unmodifiableMap(struct.constructors);
functions = Collections.unmodifiableMap(struct.functions);
methods = Collections.unmodifiableMap(struct.methods);
statics = Collections.unmodifiableMap(struct.statics);
members = Collections.unmodifiableMap(struct.members);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
Struct struct = (Struct)object;
return name.equals(struct.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}
static class Cast {
final Type from;
final Type to;
Cast(final Type from, final Type to) {
this.from = from;
this.to = to;
}
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
final Cast cast = (Cast)object;
return from.equals(cast.from) && to.equals(cast.to);
}
@Override
public int hashCode() {
int result = from.hashCode();
result = 31 * result + to.hashCode();
return result;
}
}
static class Transform extends Cast {
final Cast cast;
final Method method;
final Type upcast;
final Type downcast;
private Transform(final Cast cast, Method method, final Type upcast, final Type downcast) {
super(cast.from, cast.to);
this.cast = cast;
this.method = method;
this.upcast = upcast;
this.downcast = downcast;
}
}
private static final String PROPERTIES_FILE = Definition.class.getSimpleName() + ".properties";
static Definition loadFromProperties() {
final Properties properties = new Properties();
try (final InputStream stream = Definition.class.getResourceAsStream(PROPERTIES_FILE)) {
properties.load(stream);
} catch (IOException exception) {
throw new IllegalStateException(
"Unable to load definition properties file [" + PROPERTIES_FILE + "].");
}
return loadFromProperties(properties);
}
static Definition loadFromProperties(final Properties properties) {
final Definition definition = new Definition();
for (String key : properties.stringPropertyNames()) {
try {
final String property = properties.getProperty(key);
if (key.startsWith("struct")) loadStructFromProperty(definition, property, false, false);
else if (key.startsWith("runtime")) loadStructFromProperty(definition, property, false, true);
else if (key.startsWith("generic")) loadStructFromProperty(definition, property, true, false);
else {
boolean valid = key.startsWith("constructor") || key.startsWith("function") ||
key.startsWith("method") || key.startsWith("copy") ||
key.startsWith("static") || key.startsWith("member") ||
key.startsWith("transform") || key.startsWith("numeric") ||
key.startsWith("upcast");
if (!valid) {
throw new IllegalArgumentException("Invalid property key.");
}
}
} catch (final RuntimeException exception) {
throw new RuntimeException("Error loading [" + key + "].", exception);
}
}
for (String key : properties.stringPropertyNames()) {
try {
final String property = properties.getProperty(key);
if (key.startsWith("constructor")) loadConstructorFromProperty(definition, property);
else if (key.startsWith("function")) loadMethodFromProperty(definition, property, true);
else if (key.startsWith("method")) loadMethodFromProperty(definition, property, false);
else if (key.startsWith("static")) loadFieldFromProperty(definition, property, true);
else if (key.startsWith("member")) loadFieldFromProperty(definition, property, false);
} catch (final RuntimeException exception) {
throw new RuntimeException("Error loading [" + key + "].", exception);
}
}
for (String key : properties.stringPropertyNames()) {
try {
final String property = properties.getProperty(key);
if (key.startsWith("copy")) loadCopyFromProperty(definition, property);
else if (key.startsWith("transform")) loadTransformFromProperty(definition, property);
else if (key.startsWith("numeric")) loadNumericFromProperty(definition, property);
else if (key.startsWith("upcast")) loadUpcastFromProperty(definition, property);
} catch (final Exception exception) {
throw new RuntimeException("Error loading [" + key + "].", exception);
}
}
validateMethods(definition);
buildRuntimeMap(definition);
return new Definition(definition);
}
private static void loadStructFromProperty(final Definition definition, final String property,
final boolean generic, final boolean runtime) {
final String[] split = property.split("\\s+");
if (split.length != 2) {
throw new IllegalArgumentException("Struct must be defined with exactly two arguments (name, Java class).");
}
final String namestr = split[0];
final String clazzstr = split[1];
loadStruct(definition, namestr, clazzstr, generic, runtime);
}
private static void loadConstructorFromProperty(final Definition definition, final String property) {
String parsed = property;
int index = parsed.indexOf(' ');
if (index == -1) {
throw new IllegalArgumentException("Constructor must be defined as (struct, name," +
" Java constructor name with argument types).");
}
final String ownerstr = parsed.substring(0, index);
parsed = parsed.substring(index).trim();
index = parsed.indexOf(' ');
if (index == -1) {
throw new IllegalArgumentException("Constructor must be defined as (struct, name," +
" Java constructor name with argument types).");
}
final String namestr = parsed.substring(0, index);
parsed = parsed.substring(index).trim();
final String[][] argumentsstrs = parseArgumentsStr(parsed);
loadConstructor(definition, ownerstr, namestr, argumentsstrs);
}
private static void loadMethodFromProperty(final Definition definition, final String property, final boolean statik) {
String parsed = property;
int index = parsed.indexOf(' ');
if (index == -1) {
throw new IllegalArgumentException((statik ? "Function" : "Method") +
" must be defined as (struct, name, Java method name with argument types).");
}
final String ownerstr = parsed.substring(0, index);
parsed = parsed.substring(index).trim();
index = parsed.indexOf(' ');
if (index == -1) {
throw new IllegalArgumentException((statik ? "Function" : "Method") +
" must be defined as (struct, name, Java method name with argument types).");
}
final String namestr = parsed.substring(0, index);
parsed = parsed.substring(index).trim();
index = parsed.indexOf(' ');
if (index == -1) {
throw new IllegalArgumentException((statik ? "Function" : "Method") +
" must be defined as (struct, name, Java method name with argument types).");
}
final String returnstr = parsed.substring(0, index);
parsed = parsed.substring(index).trim();
index = parsed.indexOf('(');
if (index == -1) {
throw new IllegalArgumentException((statik ? "Function" : "Method") +
" must be defined as (struct, name, Java method name with argument types).");
}
final String clazzstr = parsed.substring(0, index);
parsed = parsed.substring(index).trim();
final String[][] argumentsstrs = parseArgumentsStr(parsed);
loadMethod(definition, ownerstr, namestr, returnstr, clazzstr, argumentsstrs, statik);
}
private static void loadFieldFromProperty(final Definition definition, final String property, final boolean statik) {
final String[] split = property.split("\\s+");
if (split.length != 4) {
throw new IllegalArgumentException((statik ? "Static" : "Member") +
" must be defined with exactly four arguments (type, name, Java type, Java name).");
}
final String ownerstr = split[0];
final String namestr = split[1];
final String typestr = split[2];
final String clazzstr = split[3];
loadField(definition, ownerstr, namestr, typestr, clazzstr, statik);
}
private static void loadCopyFromProperty(final Definition definition, final String property) {
final String[] split = property.split("\\s+");
if (split.length < 2) {
throw new IllegalArgumentException(
"Copy must be defined with at least two arguments (sub type, super type(s)).");
}
final String ownerstr = split[0];
loadCopy(definition, ownerstr, split);
}
private static void loadTransformFromProperty(final Definition definition, final String property) {
final String[] split = property.split("\\s+");
if (split.length != 6) {
throw new IllegalArgumentException("Transform must be defined with exactly six arguments" +
" ([explicit/implicit], cast from type, cast to type, owner struct," +
" [function/method], call name).");
}
final String typestr = split[0];
final String fromstr = split[1];
final String tostr = split[2];
final String ownerstr = split[3];
final String staticstr = split[4];
final String methodstr = split[5];
loadTransform(definition, typestr, fromstr, tostr, ownerstr, staticstr, methodstr);
}
private static void loadNumericFromProperty(final Definition definition, final String property) {
final String[] split = property.split("\\s+");
if (split.length != 2) {
throw new IllegalArgumentException(
"Numeric cast must be defined with exactly two arguments (cast from type, cast to type).");
}
final String fromstr = split[0];
final String tostr = split[1];
loadNumeric(definition, fromstr, tostr);
}
private static void loadUpcastFromProperty(final Definition definition, final String property) {
final String[] split = property.split("\\s+");
if (split.length != 2) {
throw new IllegalArgumentException(
"Upcast must be defined with exactly two arguments (cast from type, cast to type).");
}
final String fromstr = split[0];
final String tostr = split[1];
loadUpcast(definition, fromstr, tostr);
}
private static void loadStruct(final Definition definition, final String namestr,
final String clazzstr, final boolean generic, final boolean runtime) {
if (!namestr.matches("^[_a-zA-Z][_a-zA-Z0-9]*$")) {
throw new IllegalArgumentException("Invalid struct name [" + namestr + "].");
}
if (definition.structs.containsKey(namestr)) {
throw new IllegalArgumentException("Duplicate struct name [" + namestr + "].");
}
final Class<?> clazz = getClassFromCanonicalName(clazzstr);
final String internal = clazz.getName().replace('.', '/');
final Struct struct = new Struct(namestr, clazz, internal, generic, runtime);
definition.structs.put(namestr, struct);
}
private static void loadConstructor(final Definition definition, final String ownerstr,
final String namestr, final String[][] argumentsstrs) {
final Struct owner = definition.structs.get(ownerstr);
if (owner == null) {
throw new IllegalArgumentException(
"Owner struct [" + ownerstr + "] not defined for constructor [" + namestr + "].");
}
if (!namestr.matches("^[_a-zA-Z][_a-zA-Z0-9]*$")) {
throw new IllegalArgumentException(
"Invalid constructor name [" + namestr + "] with the struct [" + ownerstr + "].");
}
if (owner.constructors.containsKey(namestr)) {
throw new IllegalArgumentException(
"Duplicate constructor name [" + namestr + "] found within the struct [" + ownerstr + "].");
}
if (owner.statics.containsKey(namestr)) {
throw new IllegalArgumentException("Constructors and functions may not have the same name" +
" [" + namestr + "] within the same struct [" + ownerstr + "].");
}
if (owner.methods.containsKey(namestr)) {
throw new IllegalArgumentException("Constructors and methods may not have the same name" +
" [" + namestr + "] within the same struct [" + ownerstr + "].");
}
final int length = argumentsstrs.length;
final Type[] arguments = new Type[length];
final Type[] originals = new Type[length];
final Class<?>[] jarguments = new Class<?>[length];
String descriptor = "(";
for (int argumentindex = 0; argumentindex < length; ++ argumentindex) {
final String[] argumentstrs = argumentsstrs[argumentindex];
if (argumentstrs.length == 1) {
arguments[argumentindex] = getTypeFromCanonicalName(definition, argumentstrs[0]);
originals[argumentindex] = arguments[argumentindex];
} else if (argumentstrs.length == 2) {
arguments[argumentindex] = getTypeFromCanonicalName(definition, argumentstrs[1]);
originals[argumentindex] = getTypeFromCanonicalName(definition, argumentstrs[0]);
} else {
throw new IllegalStateException("Argument type definition without one or two parameters." +
" Found [" + argumentstrs.length + "] parameters for argument type [" + argumentstrs[0] + "]" +
" in constructor [" + namestr + "] within the struct [" + ownerstr + "].");
}
jarguments[argumentindex] = originals[argumentindex].clazz;
descriptor += originals[argumentindex].descriptor;
}
descriptor += ")V";
final java.lang.reflect.Constructor jconstructor = getJConstructorFromJClass(owner.clazz, jarguments);
final Constructor constructor = new Constructor(namestr, owner,
Arrays.asList(arguments), Arrays.asList(originals), jconstructor, descriptor);
owner.constructors.put(namestr, constructor);
}
private static void loadMethod(final Definition definition, final String ownerstr, final String namestr,
final String returnstr, final String clazzstr,
final String[][] argumentsstrs, boolean statik) {
final Struct owner = definition.structs.get(ownerstr);
if (owner == null) {
throw new IllegalArgumentException("Owner struct [" + ownerstr + "] not defined" +
" for " + (statik ? "function" : "method") + " [" + namestr + "].");
}
if (!namestr.matches("^[_a-zA-Z][_a-zA-Z0-9]*$")) {
throw new IllegalArgumentException("Invalid " + (statik ? "function" : "method") +
" name [" + namestr + "] with the struct [" + ownerstr + "].");
}
if (owner.constructors.containsKey(namestr)) {
throw new IllegalArgumentException("Constructors and " + (statik ? "functions" : "methods") +
" may not have the same name [" + namestr + "] within the same struct [" + ownerstr + "].");
}
if (owner.statics.containsKey(namestr)) {
if (statik) {
throw new IllegalArgumentException(
"Duplicate function name [" + namestr + "] found within the struct [" + ownerstr + "].");
} else {
throw new IllegalArgumentException("Functions and methods may not have the same name" +
" [" + namestr + "] within the same struct [" + ownerstr + "].");
}
}
if (owner.methods.containsKey(namestr)) {
if (statik) {
throw new IllegalArgumentException("Functions and methods may not have the same name" +
" [" + namestr + "] within the same struct [" + ownerstr + "].");
} else {
throw new IllegalArgumentException(
"Duplicate method name [" + namestr + "] found within the struct [" + ownerstr + "].");
}
}
final String[] returnstrs = parseArgumentStr(returnstr);
Type rtn;
Type oreturn;
Class<?> jreturn;
if (returnstrs.length == 1) {
rtn = getTypeFromCanonicalName(definition, returnstrs[0]);
oreturn = rtn;
} else if (returnstrs.length == 2) {
rtn = getTypeFromCanonicalName(definition, returnstrs[1]);
oreturn = getTypeFromCanonicalName(definition, returnstrs[0]);
} else {
throw new IllegalStateException("Return type definition without one or two parameters. Found" +
" [" + returnstrs.length + "] parameters for return type [" + returnstrs[0] + "] in " +
(statik ? "function" : "method") + "[" + namestr + "] within the struct [" + ownerstr + "].");
}
jreturn = oreturn.clazz;
final int length = argumentsstrs.length;
final Type[] arguments = new Type[length];
final Type[] originals = new Type[length];
final Class<?>[] jarguments = new Class<?>[length];
String descriptor = "(";
for (int argumentindex = 0; argumentindex < length; ++ argumentindex) {
final String[] argumentstrs = argumentsstrs[argumentindex];
if (argumentstrs.length == 1) {
arguments[argumentindex] = getTypeFromCanonicalName(definition, argumentstrs[0]);
originals[argumentindex] = arguments[argumentindex];
} else if (argumentstrs.length == 2) {
arguments[argumentindex] = getTypeFromCanonicalName(definition, argumentstrs[1]);
originals[argumentindex] = getTypeFromCanonicalName(definition, argumentstrs[0]);
} else {
throw new IllegalStateException("Argument type definition without one or two parameters." +
" Found [" + returnstrs.length + "] parameters for argument type [" + returnstrs[0] + "] in " +
(statik ? "function" : "method") + "[" + namestr + "] within the struct [" + ownerstr + "].");
}
jarguments[argumentindex] = originals[argumentindex].clazz;
descriptor += originals[argumentindex].descriptor;
}
descriptor += ")" + oreturn.descriptor;
final java.lang.reflect.Method jmethod = getJMethodFromJClass(owner.clazz, clazzstr, jarguments);
if (!jreturn.equals(jmethod.getReturnType())) {
throw new IllegalArgumentException("Defined Java return type [" + jreturn.getCanonicalName() +
" does not match the Java return type [" + jmethod.getReturnType().getCanonicalName() +
"] for " + (statik ? "function" : "method") + " [" + namestr + "] " +
"within the struct [" + ownerstr + "].");
}
final Method method = new Method(namestr, owner, rtn, oreturn,
Arrays.asList(arguments), Arrays.asList(originals), jmethod, descriptor);
final int modifiers = jmethod.getModifiers();
if (statik) {
if (!java.lang.reflect.Modifier.isStatic(modifiers)) {
throw new IllegalArgumentException("Function [" + namestr + "]" +
" within the struct [" + ownerstr + "] is not linked to a static Java method.");
}
owner.functions.put(namestr, method);
} else {
if (java.lang.reflect.Modifier.isStatic(modifiers)) {
throw new IllegalArgumentException("Method [" + namestr + "]" +
" within the struct [" + ownerstr + "] is not linked to a non-static Java method.");
}
owner.methods.put(namestr, method);
}
}
private static void loadField(final Definition definition, final String ownerstr, final String namestr,
final String typestr, final String jnamestr, final boolean statik) {
final Struct owner = definition.structs.get(ownerstr);
if (owner == null) {
throw new IllegalArgumentException("Owner struct [" + ownerstr + "] not defined for " +
(statik ? "static" : "member") + " [" + namestr + "].");
}
if (!namestr.matches("^[_a-zA-Z][_a-zA-Z0-9]*$")) {
throw new IllegalArgumentException("Invalid " + (statik ? "static" : "member") +
" name [" + namestr + "] with the struct [" + ownerstr + "].");
}
if (owner.statics.containsKey(namestr)) {
if (statik) {
throw new IllegalArgumentException(
"Duplicate static name [" + namestr + "] found within the struct [" + ownerstr + "].");
} else {
throw new IllegalArgumentException("Statics and members may not have the same name " +
"[" + namestr + "] within the same struct [" + ownerstr + "].");
}
}
if (owner.members.containsKey(namestr)) {
if (statik) {
throw new IllegalArgumentException("Statics and members may not have the same name " +
"[" + namestr + "] within the same struct [" + ownerstr + "].");
} else {
throw new IllegalArgumentException(
"Duplicate member name [" + namestr + "] found within the struct [" + ownerstr + "].");
}
}
final Type type = getTypeFromCanonicalName(definition, typestr);
final java.lang.reflect.Field jfield = getJFieldFromJClass(owner.clazz, jnamestr);
final Field field = new Field(namestr, owner, type, jfield);
final int modifiers = jfield.getModifiers();
if (statik) {
if (!java.lang.reflect.Modifier.isStatic(modifiers)) {
throw new IllegalArgumentException();
}
if (!java.lang.reflect.Modifier.isFinal(modifiers)) {
throw new IllegalArgumentException("Static [" + namestr + "]" +
" within the struct [" + ownerstr + "] is not linked to static Java field.");
}
owner.statics.put(namestr, field);
} else {
if (java.lang.reflect.Modifier.isStatic(modifiers)) {
throw new IllegalArgumentException("Member [" + namestr + "]" +
" within the struct [" + ownerstr + "] is not linked to non-static Java field.");
}
owner.members.put(namestr, field);
}
}
private static void loadCopy(final Definition definition, final String ownerstr, final String[] childstrs) {
final Struct owner = definition.structs.get(ownerstr);
if (owner == null) {
throw new IllegalArgumentException("Owner struct [" + ownerstr + "] not defined for copy.");
}
for (int child = 1; child < childstrs.length; ++child) {
final Struct struct = definition.structs.get(childstrs[child]);
if (struct == null) {
throw new IllegalArgumentException("Child struct [" + childstrs[child] + "]" +
" not defined for copy to owner struct [" + ownerstr + "].");
}
try {
owner.clazz.asSubclass(struct.clazz);
} catch (ClassCastException exception) {
throw new ClassCastException("Child struct [" + childstrs[child] + "]" +
" is not a super type of owner struct [" + ownerstr + "] in copy.");
}
final boolean object = struct.clazz.equals(Object.class) &&
java.lang.reflect.Modifier.isInterface(owner.clazz.getModifiers());
for (final Method method : struct.methods.values()) {
if (owner.methods.get(method.name) == null) {
java.lang.reflect.Method jmethod = getJMethodFromJClass(object ? Object.class : owner.clazz,
method.method.getName(), method.method.getParameterTypes());
owner.methods.put(method.name,
new Method(method.name, owner, method.rtn, method.oreturn,
method.arguments, method.originals, jmethod, method.descriptor));
}
}
for (final Field field : struct.members.values()) {
if (owner.members.get(field.name) == null) {
java.lang.reflect.Field jfield = getJFieldFromJClass(owner.clazz, field.field.getName());
owner.members.put(field.name, new Field(field.name, owner, field.type, jfield));
}
}
}
}
private static void loadTransform(final Definition definition, final String typestr,
final String fromstr, final String tostr, final String ownerstr,
final String staticstr, final String methodstr) {
final Struct owner = definition.structs.get(ownerstr);
if (owner == null) {
throw new IllegalArgumentException("Owner struct [" + ownerstr + "] not defined for" +
" transform with cast type from [" + fromstr + "] and cast type to [" + tostr + "].");
}
final Type from = getTypeFromCanonicalName(definition, fromstr);
final Type to = getTypeFromCanonicalName(definition, tostr);
if (from.equals(to)) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "] cannot" +
" have cast type from [" + fromstr + "] be the same as cast type to [" + tostr + "].");
}
final Cast cast = new Cast(from, to);
if (definition.numerics.contains(cast)) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr +
"] already defined as a numeric cast.");
}
if (definition.upcasts.contains(cast)) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr +
"] already defined as an upcast.");
}
if (definition.implicits.containsKey(cast)) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr +
"] already defined as an implicit transform.");
}
if (definition.explicits.containsKey(cast)) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr +
"] already defined as an explicit transform.");
}
Method method;
Type upcast = null;
Type downcast = null;
if ("function".equals(staticstr)) {
method = owner.functions.get(methodstr);
if (method == null) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr +
"] using a function [" + methodstr + "] that is not defined.");
}
if (method.arguments.size() != 1) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr +
"] using function [" + methodstr + "] does not have a single type argument.");
}
Type argument = method.arguments.get(0);
try {
from.clazz.asSubclass(argument.clazz);
} catch (ClassCastException cce0) {
try {
argument.clazz.asSubclass(from.clazz);
upcast = argument;
} catch (ClassCastException cce1) {
throw new ClassCastException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr + "] using function [" +
methodstr + "] cannot cast from type to the function input argument type.");
}
}
final Type rtn = method.rtn;
try {
rtn.clazz.asSubclass(to.clazz);
} catch (ClassCastException cce0) {
try {
to.clazz.asSubclass(rtn.clazz);
downcast = to;
} catch (ClassCastException cce1) {
throw new ClassCastException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr + "] using function [" +
methodstr + "] cannot cast to type to the function return argument type.");
}
}
} else if ("method".equals(staticstr)) {
method = owner.methods.get(methodstr);
if (method == null) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr +
"] using a method [" + methodstr + "] that is not defined.");
}
if (!method.arguments.isEmpty()) {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr +
"] using method [" + methodstr + "] does not have a single type argument.");
}
try {
from.clazz.asSubclass(owner.clazz);
} catch (ClassCastException cce0) {
try {
owner.clazz.asSubclass(from.clazz);
upcast = getTypeFromCanonicalName(definition, owner.name);
} catch (ClassCastException cce1) {
throw new ClassCastException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr + "] using method [" +
methodstr + "] cannot cast from type to the method input argument type.");
}
}
final Type rtn = method.rtn;
try {
rtn.clazz.asSubclass(to.clazz);
} catch (ClassCastException cce0) {
try {
to.clazz.asSubclass(rtn.clazz);
downcast = to;
} catch (ClassCastException cce1) {
throw new ClassCastException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr + "] using method [" +
methodstr + "] cannot cast to type to the method return argument type.");
}
}
} else {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr + "] using function [" +
methodstr + "] is not specified to use a function or a method.");
}
final Transform transform = new Transform(cast, method, upcast, downcast);
if ("explicit".equals(typestr)) {
definition.explicits.put(cast, transform);
} else if ("implicit".equals(typestr)) {
definition.implicits.put(cast, transform);
} else {
throw new IllegalArgumentException("Transform with owner struct [" + ownerstr + "]" +
" and cast type from [" + fromstr + "] to cast type to [" + tostr + "] using" +
" function/method [" + methodstr + "] is not specified to be implicit or explicit.");
}
}
private static void loadNumeric(final Definition definition, final String fromstr, final String tostr) {
final Type from = getTypeFromCanonicalName(definition, fromstr);
final Type to = getTypeFromCanonicalName(definition, tostr);
if (from.equals(to)) {
throw new IllegalArgumentException("Numeric cast cannot have cast type from [" + fromstr + "]" +
" be the same as cast type to [" + tostr + "].");
}
final Cast cast = new Cast(from, to);
if (definition.numerics.contains(cast)) {
throw new IllegalArgumentException("Numeric cast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] already defined.");
}
if (definition.upcasts.contains(cast)) {
throw new IllegalArgumentException("Numeric cast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] already defined as an upcast.");
}
if (definition.explicits.containsKey(cast)) {
throw new IllegalArgumentException("Numeric cast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] already defined as an explicit transform.");
}
if (definition.implicits.containsKey(cast)) {
throw new IllegalArgumentException("Numeric cast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] already defined as an implicit transform.");
}
if (!from.metadata.numeric) {
throw new IllegalArgumentException("Numeric cast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] cannot have the from cast type as a non-numeric.");
}
if (!to.metadata.numeric) {
throw new IllegalArgumentException("Numeric cast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] cannot have the to cast type as a non-numeric.");
}
definition.numerics.add(cast);
}
private static void loadUpcast(final Definition definition, final String fromstr, final String tostr) {
final Type from = getTypeFromCanonicalName(definition, fromstr);
final Type to = getTypeFromCanonicalName(definition, tostr);
if (from.equals(to)) {
throw new IllegalArgumentException("Upcast cannot have cast type from [" + fromstr + "]" +
" be the same as cast type to [" + tostr + "].");
}
final Cast cast = new Cast(from, to);
if (definition.numerics.contains(cast)) {
throw new IllegalArgumentException("Upcast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] already defined as a numeric cast.");
}
if (definition.upcasts.contains(cast)) {
throw new IllegalArgumentException("Upcast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] already defined.");
}
if (definition.explicits.containsKey(cast)) {
throw new IllegalArgumentException("Upcast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] already defined as an explicit transform.");
}
if (definition.implicits.containsKey(cast)) {
throw new IllegalArgumentException(" Upcast with cast type from [" + fromstr + "]" +
" to cast type to [" + tostr + "] already defined as an implicit transform.");
}
try {
to.clazz.asSubclass(from.clazz);
} catch (ClassCastException exception) {
throw new ClassCastException("Upcast with cast type from [" + fromstr + "]" +
" is not a super type of cast type to [" + tostr + "].");
}
definition.upcasts.add(cast);
}
private static String[][] parseArgumentsStr(final String argumentstr) {
if (!argumentstr.startsWith("(") || !argumentstr.endsWith(")")) {
throw new IllegalArgumentException("Arguments [" + argumentstr + "]" +
" is missing opening and/or closing parenthesis.");
}
final String tidy = argumentstr.substring(1, argumentstr.length() - 1).replace(" ", "");
if ("".equals(tidy)) {
return new String[0][];
}
final String[] argumentsstr = tidy.split(",");
final String[][] argumentsstrs = new String[argumentsstr.length][];
for (int argumentindex = 0; argumentindex < argumentsstr.length; ++argumentindex) {
argumentsstrs[argumentindex] = parseArgumentStr(argumentsstr[argumentindex]);
}
return argumentsstrs;
}
private static String[] parseArgumentStr(final String argumentstr) {
final String[] argumentstrs = argumentstr.split("\\^");
if (argumentstrs.length == 1) {
return new String[] {argumentstrs[0]};
} else if (argumentstrs.length == 2) {
return new String[] {argumentstrs[0], argumentstrs[1]};
} else {
throw new IllegalArgumentException(
"Argument [" + argumentstr + "] must contain only one or two paremeters");
}
}
static Type getTypeFromCanonicalName(final Definition definition, final String namestr) {
final int dimensions = getArrayDimensionsFromCanonicalName(namestr);
final String structstr = dimensions == 0 ? namestr : namestr.substring(0, namestr.indexOf('['));
final Struct struct = definition.structs.get(structstr);
if (struct == null) {
throw new IllegalArgumentException("The struct with name [" + namestr + "] has not been defined.");
}
return getTypeWithArrayDimensions(struct, dimensions);
}
static Type getTypeWithArrayDimensions(final Struct struct, final int dimensions) {
if (dimensions == 0) {
final Class<?> clazz = struct.clazz;
final String internal = struct.internal;
final String descriptor = getDescriptorFromClass(clazz);
TypeMetadata metadata = TypeMetadata.OBJECT;
for (TypeMetadata value : TypeMetadata.values()) {
if (value.clazz == null) {
continue;
}
if (value.clazz.equals(struct.clazz)) {
metadata = value;
break;
}
}
return new Type(struct.name, struct, clazz, internal, descriptor, 0, metadata);
} else {
final char[] brackets = new char[dimensions*2];
for (int index = 0; index < brackets.length; ++index) {
brackets[index] = '[';
brackets[++index] = ']';
}
final String name = struct.name + new String(brackets);
final String clazzstr = struct.clazz.getCanonicalName() + new String(brackets);
final Class<?> clazz = getClassFromCanonicalName(clazzstr);
final String internal = clazz.getName().replace('.', '/');
final String descriptor = getDescriptorFromClass(clazz);
return new Type(name, struct, clazz, internal, descriptor, dimensions, TypeMetadata.ARRAY);
}
}
private static Class<?> getClassFromCanonicalName(final String namestr) {
try {
final int dimensions = getArrayDimensionsFromCanonicalName(namestr);
if (dimensions == 0) {
switch (namestr) {
case "void":
return void.class;
case "boolean":
return boolean.class;
case "byte":
return byte.class;
case "char":
return char.class;
case "short":
return short.class;
case "int":
return int.class;
case "long":
return long.class;
case "float":
return float.class;
case "double":
return double.class;
default:
return Class.forName(namestr);
}
} else {
String jclassstr = namestr.substring(0, namestr.indexOf('['));
char[] brackets = new char[dimensions];
Arrays.fill(brackets, '[');
String descriptor = new String(brackets);
switch (jclassstr) {
case "void":
descriptor += "V";
break;
case "boolean":
descriptor += "Z";
break;
case "byte":
descriptor += "B";
break;
case "char":
descriptor += "C";
break;
case "short":
descriptor += "S";
break;
case "int":
descriptor += "I";
break;
case "long":
descriptor += "J";
break;
case "float":
descriptor += "F";
break;
case "double":
descriptor += "D";
break;
default:
descriptor += "L" + jclassstr + ";";
}
return Class.forName(descriptor);
}
} catch (ClassNotFoundException exception) {
throw new IllegalArgumentException(
"Unable to create a Java class from the canonical name [" + namestr + "].");
}
}
private static int getArrayDimensionsFromCanonicalName(final String name) {
int dimensions = 0;
int index = name.indexOf('[');
if (index != -1) {
final int length = name.length();
while (index < length) {
if (name.charAt(index) == '[' && ++index < length && name.charAt(index++) == ']') {
++dimensions;
} else {
throw new IllegalArgumentException("Invalid array braces in canonical name [" + name + "].");
}
}
}
return dimensions;
}
private static String getDescriptorFromClass(final Class<?> clazz) {
final String namestr = clazz.getName();
switch (namestr) {
case "void":
return "V";
case "boolean":
return "Z";
case "byte":
return "B";
case "char":
return "C";
case "short":
return "S";
case "int":
return "I";
case "long":
return "J";
case "float":
return "F";
case "double":
return "D";
default:
String descriptor = namestr.replace('.', '/');
if (!descriptor.startsWith("[")) {
descriptor = "L" + descriptor + ";";
}
return descriptor;
}
}
private static java.lang.reflect.Constructor getJConstructorFromJClass(final Class<?> clazz,
final Class<?>[] arguments) {
try {
return clazz.getConstructor(arguments);
} catch (NoSuchMethodException exception) {
throw new IllegalArgumentException("Java constructor not found for Java class [" + clazz.getName() + "]" +
" with Java argument types [" + Arrays.toString(arguments) + "].");
}
}
private static java.lang.reflect.Method getJMethodFromJClass(final Class<?> clazz, final String namestr,
final Class<?>[] arguments) {
try {
return clazz.getMethod(namestr, arguments);
} catch (NoSuchMethodException exception) {
throw new IllegalArgumentException("Java method [" + namestr +
"] not found for Java class [" + clazz.getName() + "]" +
" with Java argument types [" + Arrays.toString(arguments) + "].");
}
}
private static java.lang.reflect.Field getJFieldFromJClass(final Class<?> clazz, final String namestr) {
try {
return clazz.getField(namestr);
} catch (NoSuchFieldException exception) {
throw new IllegalArgumentException(
"Java field [" + namestr + "] not found for Java class [" + clazz.getName() + "].");
}
}
private static void validateMethods(final Definition types) {
for (final Struct struct : types.structs.values()) {
for (final Constructor constructor : struct.constructors.values()) {
final int length = constructor.arguments.size();
final List<Type> arguments = constructor.arguments;
final List<Type> originals = constructor.originals;
for (int argument = 0; argument < length; ++argument) {
if (!validateArgument(types, arguments.get(argument), originals.get(argument))) {
throw new ClassCastException("Generic argument Java type [" +
arguments.get(argument).clazz.getCanonicalName() + "] is not a Java sub type of [" +
originals.get(argument).clazz.getCanonicalName() + "] in the constructor [" +
constructor.name + " from the struct [" + struct.name + "].");
}
}
}
for (final Method function : struct.functions.values()) {
final int length = function.arguments.size();
final List<Type> arguments = function.arguments;
final List<Type> originals = function.originals;
for (int argument = 0; argument < length; ++argument) {
if (!validateArgument(types, arguments.get(argument), originals.get(argument))) {
throw new ClassCastException("Generic argument Java type [" +
arguments.get(argument).clazz.getCanonicalName() + "] is not a Java sub type of [" +
originals.get(argument).clazz.getCanonicalName() + "] in the function [" +
function.name + " from the struct [" + struct.name + "].");
}
}
if (!validateArgument(types, function.rtn, function.oreturn)) {
throw new ClassCastException("Generic return Java type [" +
function.rtn.clazz.getCanonicalName() + "] is not a Java sub type of [" +
function.oreturn.clazz.getCanonicalName() + "] in the function [" +
function.name + " from the struct [" + struct.name + "].");
}
}
for (final Method method : struct.methods.values()) {
final int length = method.arguments.size();
final List<Type> arguments = method.arguments;
final List<Type> originals = method.originals;
for (int argument = 0; argument < length; ++argument) {
if (!validateArgument(types, arguments.get(argument), originals.get(argument))) {
throw new ClassCastException("Generic argument Java type [" +
arguments.get(argument).clazz.getCanonicalName() + "] is not a Java sub type of [" +
originals.get(argument).clazz.getCanonicalName() + "] in the method [" +
method.name + " from the struct [" + struct.name + "].");
}
}
if (!validateArgument(types, method.rtn, method.oreturn)) {
throw new ClassCastException("Generic return Java type [" +
method.rtn.clazz.getCanonicalName() + "] is not a Java sub type of [" +
method.oreturn.clazz.getCanonicalName() + "] in the method [" +
method.name + " from the struct [" + struct.name + "].");
}
}
}
}
private static boolean validateArgument(final Definition definition, final Type argument, final Type original) {
final Cast cast = new Cast(argument, original);
if (!definition.implicits.containsKey(cast)) {
try {
argument.clazz.asSubclass(original.clazz);
} catch (ClassCastException exception) {
return false;
}
}
return true;
}
private static void buildRuntimeMap(final Definition definition) {
for (final Struct struct : definition.structs.values()) {
if (struct.runtime) {
for (final Method method : struct.methods.values()) {
final String name = struct.clazz.getName() + "_" + method.name;
if (!definition.runtime.containsKey(name)) {
try {
definition.runtime.put(name,
MethodHandles.publicLookup().in(struct.clazz).unreflect(method.method));
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Unable to find method [" + method.name + "] from the" +
" struct [" + struct.name + "] to define as a runtime method.");
}
}
}
}
}
}
final Map<String, Struct> structs;
final Map<String, MethodHandle> runtime;
final Map<Cast, Transform> explicits;
final Map<Cast, Transform> implicits;
final Set<Cast> numerics;
final Set<Cast> upcasts;
private Definition() {
structs = new HashMap<>();
runtime = new HashMap<>();
explicits = new HashMap<>();
implicits = new HashMap<>();
numerics = new HashSet<>();
upcasts = new HashSet<>();
}
private Definition(Definition definition) {
final Map<String, Struct> ummodifiable = new HashMap<>();
for (final Struct struct : definition.structs.values()) {
ummodifiable.put(struct.name, new Struct(struct));
}
structs = Collections.unmodifiableMap(ummodifiable);
runtime = Collections.unmodifiableMap(definition.runtime);
explicits = Collections.unmodifiableMap(definition.explicits);
implicits = Collections.unmodifiableMap(definition.implicits);
numerics = Collections.unmodifiableSet(definition.numerics);
upcasts = Collections.unmodifiableSet(definition.upcasts);
}
}