Skip to content

Instantly share code, notes, and snippets.

@rascio
Created July 28, 2016 21:18
Show Gist options
  • Save rascio/7f9dc1edc2462cc2693e7eab9cdc885c to your computer and use it in GitHub Desktop.
Save rascio/7f9dc1edc2462cc2693e7eab9cdc885c to your computer and use it in GitHub Desktop.
MetaModel
package it.r.meta;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* Created by rascio on 28/07/16.
*/
public class Content {
private final JsonObject data;
public Content(JsonObject values) {
this.data = values;
}
public JsonElement get(String key) {
return data.get(key);
}
public JsonObject getData() {
return data;
}
}
package it.r.meta;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonElement;
import java.util.Set;
/**
* Created by rascio on 28/07/16.
*/
public class EnumValueType extends StringValue{
public static EnumValueType of(String...values) {
return new EnumValueType(ImmutableSet.copyOf(values));
}
private final Set<String> values;
public EnumValueType(Set<String> values) {
this.values = values;
}
@Override
public ValidationResult validate(JsonElement value) {
return super.validate(value)
.and(() -> values.contains(value.getAsString())
? ValidationResult.valid()
: ValidationResult.error(String.format("%s is not a valid value, should be one of %s", value, values))
);
}
}
package it.r.meta;
import com.google.gson.JsonElement;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Created by rascio on 28/07/16.
*/
public class ListValue<T> extends ValueType<List<T>>{
public static <T> ListValue<T> of(ValueType<T> type) {
return new ListValue<>(type);
}
private final ValueType<T> innerType;
public ListValue(ValueType<T> innerType) {
this.innerType = innerType;
}
@Override
public ValidationResult validate(JsonElement value) {
return value.isJsonArray()
? stream(value)
.map(innerType::validate)
.filter(ValidationResult::isError)
.reduce((v1, v2) -> v1.map(msg -> msg + ", " + v2.error()))
.orElse(ValidationResult.valid())
: ValidationResult.error("is not a list");
}
@Override
public List<T> value(JsonElement value) {
return stream(value)
.map(innerType::value)
.collect(Collectors.toList());
}
private Stream<JsonElement> stream(JsonElement value) {
return StreamSupport.stream(value.getAsJsonArray().spliterator(), false);
}
}
package it.r.meta;
import com.google.gson.JsonElement;
import java.util.Locale;
/**
* Created by rascio on 28/07/16.
*/
public class LocaleValueType extends ValueType<Locale>{
@Override
public ValidationResult validate(JsonElement value) {
if (!value.isJsonPrimitive()) {
return ValidationResult.error("is not a string");
}
else if (!value.getAsString().matches("[a-zA-Z]{2}(_[a-zA-Z]{2})?")) {
return ValidationResult.error("is not a valid Locale");
}
else {
return ValidationResult.valid();
}
}
@Override
public Locale value(JsonElement value) {
return new Locale(value.getAsString());
}
}
package it.r.meta;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Created by rascio on 28/07/16.
*/
public class MetaModel {
public static Builder definition(String name) {
return new Builder(name);
}
private final String name;
private final Map<String, ValueType<?>> properties;
private final List<Validator> validators;
public MetaModel(String name, Map<String, ValueType<?>> properties, List<Validator> validators) {
this.name = name;
this.properties = properties;
this.validators = validators;
}
public List<String> validate(Content content) {
final List<String> propertiesValidation = properties.entrySet().stream()
.map(e -> e.getValue()
.validation(content.get(e.getKey()))
.map(message -> String.format("[%s] %s", e.getKey(), message))
)
.filter(ValidationResult::isError)
.map(ValidationResult::error)
.collect(Collectors.toList());
if (propertiesValidation.isEmpty()) {
return validators.stream()
.map(v -> v.apply(content.getData()))
.filter(ValidationResult::isError)
.map(ValidationResult::error)
.collect(Collectors.toList());
}
else {
return propertiesValidation;
}
}
public interface Validator extends Function<JsonObject, ValidationResult> {}
public static class Builder {
private final String name;
private final Map<String, ValueType<?>> properties = new HashMap<>();
private final List<Validator> validators = new LinkedList<>();
public Builder(String name) {
this.name = name;
}
public Builder property(String name, ValueType<?> type) {
this.properties.put(name, type);
return this;
}
public Builder validation(Validator validator) {
this.validators.add(validator);
return this;
}
public MetaModel build() {
return new MetaModel(name, ImmutableMap.copyOf(this.properties), ImmutableList.copyOf(this.validators));
}
}
}
package it.r.meta;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
* Created by rascio on 28/07/16.
*/
public class ModelSet {
public static void main(String[] args) {
final MetaModel gdSnippet = MetaModel.definition("gd_snippet")
.property("locale", OptionalValue.of(new LocaleValueType()))
.property("text", new StringValue())
.property("tags", ListValue.of(EnumValueType.of("quick-view", "test")))
.validation(json ->
json.get("text").getAsString().length() > 5 && json.get("locale") != null
? ValidationResult.error("if text is more than 5 chars locale should be null")
: ValidationResult.valid()
)
.build();
final JsonObject data = new JsonObject();
final JsonArray tags = new JsonArray();
tags.add("pippo");
tags.add("quick-view");
tags.add("pluto");
data.addProperty("locale", "it_IT");
data.addProperty("text", "asdasd");
data.add("tags", tags);
final Content content = new Content(data);
gdSnippet.validate(content)
.forEach(System.out::println);
}
}
package it.r.meta;
import com.google.gson.JsonElement;
/**
* Created by rascio on 28/07/16.
*/
public class OptionalValue<T> extends ValueType<T>{
public static <T> OptionalValue<T> of(ValueType<T> type) {
return new OptionalValue<>(type);
}
private final ValueType<T> innerType;
public OptionalValue(ValueType<T> innerType) {
this.innerType = innerType;
}
@Override
public ValidationResult validate(JsonElement value) {
return value != null
? innerType.validate(value)
: ValidationResult.valid();
}
@Override
public T value(JsonElement value) {
return value != null
? innerType.value(value)
: null;
}
}
package it.r.meta;
import com.google.gson.JsonElement;
/**
* Created by rascio on 28/07/16.
*/
public class StringValue extends ValueType<String>{
@Override
public ValidationResult validate(JsonElement value) {
return value.isJsonPrimitive()
? ValidationResult.valid()
: ValidationResult.error("is not a string");
}
@Override
public String value(JsonElement value) {
return value.getAsString();
}
}
package it.r.meta;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Created by rascio on 28/07/16.
*/
public interface ValidationResult {
public static ValidationResult valid() {
return new ValidResult();
}
public static ValidationResult error(String message) {
return new ErrorResult(message);
}
boolean isError();
String error();
ValidationResult map(Function<String, String> mapping);
ValidationResult and(Supplier<ValidationResult> other);
static class ValidResult implements ValidationResult {
@Override
public boolean isError() {
return false;
}
@Override
public String error() {
throw new IllegalStateException("Is not an error!");
}
@Override
public ValidationResult map(Function<String, String> mapping) {
return this;
}
@Override
public ValidationResult and(Supplier<ValidationResult> other) {
return other.get();
}
}
static class ErrorResult implements ValidationResult {
private final String message;
public ErrorResult(String message) {
this.message = message;
}
@Override
public boolean isError() {
return true;
}
@Override
public String error() {
return this.message;
}
@Override
public ValidationResult map(Function<String, String> mapping) {
return new ErrorResult(mapping.apply(this.message));
}
@Override
public ValidationResult and(Supplier<ValidationResult> other) {
return this;
}
}
}
package it.r.meta;
/**
* Created by rascio on 28/07/16.
*/
public class Value<T> {
}
package it.r.meta;
import com.google.gson.JsonElement;
/**
* Created by rascio on 28/07/16.
*/
public abstract class ValueType<T> {
public ValidationResult validation(JsonElement value) {
if (value == null) {
return ValidationResult.error("is missing");
}
else {
return validate(value);
}
}
/*
* Maybe a ValidationResult object instead of two methods is better
*/
protected abstract T value(JsonElement value);
protected abstract ValidationResult validate(JsonElement value);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment