/**
*
*/
package xapi.model.impl;
import java.util.Objects;
import xapi.annotation.compile.MagicMethod;
import xapi.collect.X_Collect;
import xapi.collect.api.ClassTo;
import xapi.collect.api.StringTo;
import xapi.dev.source.CharBuffer;
import xapi.inject.X_Inject;
import xapi.model.api.Model;
import xapi.model.api.ModelDeserializationContext;
import xapi.model.api.ModelKey;
import xapi.model.api.ModelManifest;
import xapi.model.api.ModelSerializationContext;
import xapi.model.api.ModelSerializer;
import xapi.model.api.PrimitiveSerializer;
import xapi.model.service.ModelService;
import xapi.source.api.CharIterator;
import xapi.source.impl.StringCharIterator;
import xapi.util.api.SuccessHandler;
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class AbstractModelService implements ModelService
{
protected final StringTo<ModelSerializer> serializers = X_Collect.newStringMap(ModelSerializer.class);
protected final ClassTo<String> classToTypeName = X_Collect.newClassMap(String.class);
protected final StringTo<Class<? extends Model>> typeNameToClass = X_Collect.newStringMap(Class.class.cast(Class.class));
@Override
@MagicMethod(doNotVisit=true, documentation="This magic method re-routes to the same provider as X_Inject.instance()")
public <T extends Model> T create(final Class<T> key) {
// GWT dev will make it here, and it can handle non-class-literal injection.
// GWT prod requires magic method injection here.
final T instance = X_Inject.instance(key);
if (instance == null) {
return null;
}
typeNameToClass.put(instance.getType(), key);
classToTypeName.put(key, instance.getType());
return instance;
}
@Override
public <M extends Model> void persist(final M model, final SuccessHandler<M> callback) {
assert Objects.nonNull(model) : "Cannot persist a null model";
doPersist(model.getType(), model, callback);
}
protected abstract <M extends Model> void doPersist(String type, M model, SuccessHandler<M> callback);
protected <M extends Model> M deserialize(final String cls, final CharIterator model) {
return deserialize((Class<M>)typeNameToClass.get(cls), model);
}
@Override
public <M extends Model> M deserialize(final Class<M> cls, final CharIterator model) {
if (model == null) {
return null;
}
final ModelDeserializationContext context = new ModelDeserializationContext(create(cls), model, this, null);
context.setClientToServer(isClientToServer());
final ModelSerializer<M> serializer = getSerializer(getTypeName(cls));
return serializer.modelFromString(model, context);
}
/**
* By default, all JRE environments will be considered server to client, and client
* implementations of the model service will override this method to return true
*/
protected boolean isClientToServer() {
return false;
}
@Override
public <M extends Model> M deserialize(final ModelManifest manifest, final CharIterator model) {
if (model == null) {
return null;
}
final Class<M> cls = (Class<M>) typeNameToClass.get(manifest.getType());
final ModelDeserializationContext context = new ModelDeserializationContext(create(cls), model, this, manifest);
context.setClientToServer(isClientToServer());
final ModelSerializer<M> serializer = getSerializer(manifest.getType());
return serializer.modelFromString(model, context);
}
@Override
public PrimitiveSerializer primitiveSerializer() {
return new PrimitiveSerializerDefault();
}
protected String getTypeName(final Class<? extends Model> cls) {
String known = classToTypeName.get(cls);
if (known != null) {
return known;
}
if (!cls.isInterface()) {
// For non-interfaces, lets look for the most specfic Model interface
Class<?> winner = cls;
for (final Class<?> iface : cls.getInterfaces()) {
if (Model.class.isAssignableFrom(iface)) {
if (winner == cls || winner.isAssignableFrom(iface)) {
winner = iface;
}
}
}
if (winner != cls) {
known = classToTypeName.get(winner);
if (known != null) {
classToTypeName.put(cls, known);
typeNameToClass.put(known, cls);
return known;
}
}
}
final String name = ModelUtil.guessModelType(cls);
classToTypeName.put(cls, name);
typeNameToClass.put(name, cls);
return name;
}
protected <M extends Model> ModelSerializer<M> getSerializer(final String type) {
ModelSerializer serializer = serializers.get(type);
if (serializer == null) {
serializer = getDefaultSerializer(type);
}
return serializer;
}
protected <M extends Model> ModelSerializer getDefaultSerializer(final String type) {
return new ModelSerializerDefault<M>();
}
@Override
public <M extends Model> CharBuffer serialize(final Class<M> cls, final M model) {
return serialize(getTypeName(cls), model);
}
protected <M extends Model> CharBuffer serialize(final String type, final M model) {
if (model == null) {
return null;
}
final CharBuffer buffer = new CharBuffer();
final ModelSerializationContext context = new ModelSerializationContext(buffer, this, null);
context.setClientToServer(isClientToServer());
return getSerializer(type).modelToString(model, context);
}
@Override
public <M extends Model> CharBuffer serialize(final ModelManifest manifest, final M model) {
if (model == null) {
return null;
}
final CharBuffer buffer = new CharBuffer();
final ModelSerializationContext context = new ModelSerializationContext(buffer, this, manifest);
context.setClientToServer(isClientToServer());
final ModelSerializer<Model> serializer = getSerializer(manifest.getType());
return serializer.modelToString(model, context);
}
@Override
public String register(final Class<? extends Model> model) {
final String typeName = getTypeName(model);
classToTypeName.put(model, typeName);
typeNameToClass.put(typeName, model);
final ModelSerializer serializer = getSerializer(typeName);
serializers.put(typeName, serializer);
return typeName;
}
/**
* @see xapi.model.service.ModelService#keyFromString(java.lang.String)
*/
@Override
public ModelKey keyFromString(final String key) {
final CharIterator chars = new StringCharIterator(key);
final PrimitiveSerializer primitives = primitiveSerializer();
return deserializeKey(chars, primitives);
}
protected ModelKey deserializeKey(final CharIterator chars, final PrimitiveSerializer primitives) {
ModelKey parent = null;
if (!chars.hasNext()) {
return null;
}
final int parentState = primitives.deserializeInt(chars);
if (parentState == -1) {// no parent key specified
final String namespace = primitives.deserializeString(chars);
final String kind = primitives.deserializeString(chars);
final int keyType = primitives.deserializeInt(chars);
final String id = primitives.deserializeString(chars);
return newKey(namespace, kind, id).setKeyType(keyType);
} else {
final String parentString = chars.consume(parentState).toString();
assert parentString != null;
parent = keyFromString(parentString);
final String kind = primitives.deserializeString(chars);
final int keyType = primitives.deserializeInt(chars);
final String id = primitives.deserializeString(chars);
return parent.getChild(kind, id).setKeyType(keyType);
}
}
/**
* @see xapi.model.service.ModelService#keyToString(xapi.model.api.ModelKey)
*/
@Override
public String keyToString(final ModelKey key) {
final StringBuilder b = new StringBuilder();
final PrimitiveSerializer primitives = primitiveSerializer();
if (key.getParent() == null) {
b.append(primitives.serializeInt(-1));
b.append(primitives.serializeString(key.getNamespace()));
} else {
b.append(primitives.serializeString(keyToString(key.getParent())));
}
b.append(primitives.serializeString(key.getKind()));
b.append(primitives.serializeInt(key.getKeyType()));
b.append(primitives.serializeString(key.getId()));
return b.toString();
}
/**
* @see xapi.model.service.ModelService#newKey(java.lang.String, java.lang.String)
*/
@Override
public ModelKey newKey(final String namespace, final String kind) {
return new ModelKeyDefault(namespace, kind);
}
/**
* @see xapi.model.service.ModelService#newKey(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public ModelKey newKey(final String namespace, final String kind, final String id) {
return new ModelKeyDefault(namespace, kind, id);
}
}