me.snowdrop.istio.api.internal.ClassWithInterfaceFieldsRegistry Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of istio-common Show documentation
Show all versions of istio-common Show documentation
Common classes used by Istio Java API
/*
* *
* * Copyright (C) 2018 Red Hat, Inc.
* *
* * Licensed 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 me.snowdrop.istio.api.internal;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
/**
* @author Christophe Laprun
*/
public class ClassWithInterfaceFieldsRegistry {
private static final Map> classNameToFieldInfos = new HashMap<>();
private static final Set supportedSimpleTypes = new HashSet<>();
static {
Collections.addAll(supportedSimpleTypes, "integer", "string", "number", "boolean");
}
private static class Classes {
@JsonProperty
private List classes;
}
private static class ClassInfo {
@JsonProperty("class")
private String className;
@JsonProperty
private Map fields;
}
static {
// load interfaces information
YAMLMapper mapper = new YAMLMapper();
final InputStream dataIs = Thread.currentThread().getContextClassLoader().getResourceAsStream("classes-with-interface-fields.yml");
try {
final Classes classes = mapper.readValue(dataIs, Classes.class);
classes.classes.forEach(ci -> {
final Map infos = new HashMap<>(ci.fields.size());
for (Map.Entry entry : ci.fields.entrySet()) {
final String type = entry.getValue();
final String fieldName = entry.getKey();
FieldInfo info;
if (supportedSimpleTypes.contains(type)) {
info = new FieldInfo(fieldName, type);
} else if (type.startsWith("is")) {
final String interfaceName = type.substring(type.lastIndexOf('_') + 1);
final String target = interfaceName.substring(0, 1).toLowerCase() + interfaceName.substring(1);
final String impl = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
info = new ObjectFieldInfo(target, impl + interfaceName);
} else if (type.startsWith("map")) {
info = new MapFieldInfo(fieldName, type);
} else {
info = new ObjectFieldInfo(fieldName, type);
}
infos.put(fieldName, info);
}
classNameToFieldInfos.put(ci.className, infos);
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* percent, integer => percent, integer
* grpcStatus, isHTTPFaultInjection_Abort_ErrorType => errorType, GrpcStatusErrorType
*
* @param targetClassName
* @param fieldName
* @return
*/
static FieldInfo getFieldInfo(String targetClassName, String fieldName) {
final FieldInfo info = classNameToFieldInfos.getOrDefault(targetClassName, Collections.emptyMap()).get(fieldName);
if (info == null) {
throw new IllegalArgumentException("Unknown field '" + fieldName + "'");
}
return info;
}
static Set getKnownClasses() {
return Collections.unmodifiableSet(classNameToFieldInfos.keySet());
}
static class FieldInfo {
private final String target;
private final String type;
private FieldInfo(String target, String type) {
this.target = target;
this.type = type;
}
public String target() {
return target;
}
public String type() {
return type;
}
Object deserialize(JsonNode node, String fieldName, Class targetClass, DeserializationContext ctxt) throws IOException {
final JsonNode value = node.get(fieldName);
switch (type()) {
case "integer":
return value.intValue();
case "string":
return value.textValue();
case "number":
return value.doubleValue();
case "boolean":
return value.booleanValue();
default:
throw new IllegalArgumentException("Unknown simple type '" + type + "'");
}
}
}
static class MapFieldInfo extends FieldInfo {
private static final Pattern MAP_PATTERN = Pattern.compile("\\s*map<([^,]*),([^)]*)>");
private final String keyType;
private final String valueType;
private MapFieldInfo(String target, String type) {
super(target, type);
final Matcher matcher = MAP_PATTERN.matcher(type);
if (matcher.matches()) {
keyType = matcher.group(1).trim();
valueType = matcher.group(2).trim();
} else {
throw new IllegalArgumentException("Expected map field format 'map', got: " + type);
}
}
@Override
public String toString() {
return String.format("map<%s,%s>", keyType, valueType);
}
public String keyType() {
return keyType;
}
public String valueType() {
return valueType;
}
Object deserialize(JsonNode node, String fieldName, Class targetClass, DeserializationContext ctxt) throws IOException {
final String type = getFieldClassFQN(targetClass, valueType);
try {
// load class of the field
final Class> fieldClass = Thread.currentThread().getContextClassLoader().loadClass(type);
// create a map type matching the type of the field from the mapping information
final YAMLMapper codec = (YAMLMapper) ctxt.getParser().getCodec();
MapType mapType = codec.getTypeFactory().constructMapType(Map.class, String.class, fieldClass);
// get a parser taking the current value as root
final JsonParser traverse = node.get(fieldName).traverse(codec);
// and use it to deserialize the subtree as the map type we just created
return codec.readValue(traverse, mapType);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Unsupported type '" + type + "' for field '" + fieldName +
"' on '" + targetClass.getName() + "' class. Full type was " + this, e);
}
}
}
static class ObjectFieldInfo extends FieldInfo {
private ObjectFieldInfo(String target, String type) {
super(target, type);
}
Object deserialize(JsonNode node, String fieldName, Class targetClass, DeserializationContext ctxt) throws IOException {
final String type = getFieldClassFQN(targetClass, type());
try {
final Class> fieldClass = Thread.currentThread().getContextClassLoader().loadClass(type);
// check if we have embedded interface field, in which case the target field won't be declared on the target
// class but rather on the class pointed to by the field
// e.g. given:
// trafficPolicy:
// loadBalancer:
// simple: ROUND_ROBIN
// LoadBalancerSettings (loadBalancer field) doesn't have a `simple` field, this is just an unwrapped
// implementation of the LbPolicy interface, so we need to check the type of the `simple` field and deserialize
// the node as an instance of that class instead.
final Optional field = getField(targetClass, fieldName);
final ObjectCodec codec = ctxt.getParser().getCodec();
if (field.isPresent()) {
// if field is present, deserialize it with the specified field class
return codec.treeToValue(getTargetNode(node, fieldName), fieldClass);
} else {
// otherwise, get the type of the field
final Class> childFieldClass = fieldClass.getDeclaredField(fieldName).getType();
// deserialize the field using that new class
Object childObject = codec.treeToValue(getTargetNode(node, fieldName), childFieldClass);
// finally, build the instance assigning it the deserialized child field
// NOTE: this only works if the interface implementation only has one field (since otherwise, there won't
// be a single-argument constructor accepting an instance of the newly created instance
return fieldClass.getDeclaredConstructor(childFieldClass).newInstance(childObject);
}
} catch (ClassNotFoundException | JsonProcessingException | NoSuchFieldException | NoSuchMethodException |
InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Unsupported type '" + type + "' for field '" + fieldName + "' on '" + targetClass.getName() + "' class", e);
}
}
protected JsonNode getTargetNode(JsonNode node, String fieldName) {
return node.get(fieldName);
}
}
private static Optional getField(Class> objectClass, String fieldName) {
try {
return Optional.of(objectClass.getField(fieldName));
} catch (NoSuchFieldException e) {
return Optional.empty();
}
}
private static String getFieldClassFQN(Class targetClass, String type) {
// if type contains a '.', we have a fully qualified target type so use it, otherwise use the target
// class package
return type.contains(".") ? type : targetClass.getPackage().getName() + '.' + type;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy