Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
prompto.value.ConcreteInstance Maven / Gradle / Ivy
package prompto.value;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import prompto.declaration.AttributeDeclaration;
import prompto.declaration.CategoryDeclaration;
import prompto.declaration.EnumeratedCategoryDeclaration;
import prompto.declaration.GetterMethodDeclaration;
import prompto.declaration.IMethodDeclaration;
import prompto.declaration.NativeCategoryDeclaration;
import prompto.declaration.SetterMethodDeclaration;
import prompto.error.NotMutableError;
import prompto.error.NotStorableError;
import prompto.error.PromptoError;
import prompto.error.ReadWriteError;
import prompto.error.SyntaxError;
import prompto.grammar.Identifier;
import prompto.grammar.Operator;
import prompto.intrinsic.PromptoDbId;
import prompto.intrinsic.PromptoDocument;
import prompto.param.IParameter;
import prompto.runtime.Context;
import prompto.runtime.Variable;
import prompto.store.DataStore;
import prompto.store.IStorable;
import prompto.store.IStorable.IDbIdFactory;
import prompto.store.IStore;
import prompto.type.CategoryType;
import prompto.type.DecimalType;
import prompto.type.IType;
import prompto.type.IntegerType;
import prompto.type.TextType;
public class ConcreteInstance extends BaseValue implements IInstance, IMultiplyable {
CategoryDeclaration declaration;
Map values = new HashMap();
IStorable storable = null;
boolean mutable = false;
public ConcreteInstance(Context context, CategoryDeclaration declaration) {
super(new CategoryType(declaration.getId()));
this.declaration = declaration;
if(declaration.isStorable(context)) {
List categories = declaration.collectCategories(context);
storable = DataStore.getInstance().newStorable(categories, new DbIdFactory());
}
}
class DbIdFactory implements IDbIdFactory {
@Override public PromptoDbId get() { return getDbId(); }
@Override public void accept(PromptoDbId dbId) { setDbId(dbId); }
// sensitive topic: isUpdate is called only when getDbId() returns non-null
@Override public boolean isUpdate() { return true; }
}
private ConcreteInstance(CategoryType copyFrom, CategoryDeclaration declaration, Map values, String[] categories) {
super(new CategoryType(copyFrom, true));
this.declaration = declaration;
if(declaration.isStorable(null))
storable = DataStore.getInstance().newStorable(categories, new DbIdFactory());
this.values.putAll(values);
this.mutable = true;
}
@Override
public ConcreteInstance toMutable() {
String[] categories = this.storable!=null ? this.storable.getCategories() : null;
return new ConcreteInstance(this.getType(), this.declaration, this.values, categories);
}
@Override
public Object getStorableData() throws NotStorableError {
// this is called when storing the instance as a field value
// if this is an enum then we simply store the symbol name
if(this.declaration instanceof EnumeratedCategoryDeclaration)
return values.get(new Identifier("name")).getStorableData();
// otherwise we just store the dbId, the instance data itself will be collected as part of collectStorables
else if(this.storable==null)
throw new NotStorableError();
else
return this.getOrCreateDbId();
}
@Override
public boolean setMutable(boolean mutable) {
boolean result = this.mutable;
this.mutable = mutable;
return result;
}
@Override
public boolean isMutable() {
return mutable;
}
@Override
public IStorable getStorable() {
return storable;
}
@Override
public void collectStorables(Consumer collector) {
if(this.declaration instanceof EnumeratedCategoryDeclaration)
return;
if(storable==null)
throw new NotStorableError();
if(storable.isDirty()) {
getOrCreateDbId();
collector.accept(storable);
}
values.values().forEach((value)->
value.collectStorables(collector));
}
@Override
public CategoryDeclaration getDeclaration() {
return declaration;
}
@Override
public CategoryType getType() {
return (CategoryType)this.type;
}
@Override
public Set getMemberIds() {
return values.keySet();
}
// don't call getters from getters, so register them
ThreadLocal> activeGetters = new ThreadLocal>() {
@Override
protected Map initialValue() {
return new HashMap();
}
};
@Override
public IValue getMember(Context context, Identifier attrName, boolean autoCreate) throws PromptoError {
if("category".equals(attrName.toString()))
return getCategory(context);
else if("json".equals(attrName.toString()))
return super.getMember(context, attrName, autoCreate);
else
return getAttributeMember(context, attrName, autoCreate);
}
protected IValue getAttributeMember(Context context, Identifier id, boolean autoCreate) throws PromptoError {
Map activeGetters = this.activeGetters.get();
Context stacked = activeGetters.get(id);
boolean first = stacked==null;
if(first)
activeGetters.put(id, context);
try {
return getMemberAllowGetter(context, id, first);
} finally {
if(first)
activeGetters.remove(id);
}
}
protected IValue getCategory(Context context) {
NativeCategoryDeclaration decl = context.getRegisteredDeclaration(NativeCategoryDeclaration.class, new Identifier("Category"));
return new NativeCategory(decl, declaration);
}
protected IValue getMemberAllowGetter(Context context, Identifier id, boolean allowGetter) throws PromptoError {
GetterMethodDeclaration getter = allowGetter ? declaration.findGetter(context, id) : null;
if(getter!=null) {
context = context.newInstanceContext(this, false).newChildContext(); // mimic method call
return getter.interpret(context);
} else if(getDeclaration().hasAttribute(context, id) || IStore.dbIdName.equals(id.toString()))
return values.getOrDefault(id, NullValue.instance());
else if("text".equals(id.toString()))
return new TextValue(this.toString());
else
return NullValue.instance();
}
// don't call setters from setters, so register them
ThreadLocal> activeSetters = new ThreadLocal>() {
@Override
protected Map initialValue() {
return new HashMap();
}
};
@Override
public void setMember(Context context, Identifier id, IValue value) throws PromptoError {
if(!mutable)
throw new NotMutableError();
Map activeSetters = this.activeSetters.get();
Context stacked = activeSetters.get(id);
boolean first = stacked==null;
try {
if(first)
activeSetters.put(id, context);
setMember(context, id, value, first);
} finally {
if(first)
activeSetters.remove(id);
}
}
public void setMember(Context context, Identifier attrName, IValue value, boolean allowSetter) throws PromptoError {
AttributeDeclaration decl = context.getRegisteredDeclaration(AttributeDeclaration.class, attrName);
SetterMethodDeclaration setter = allowSetter ? declaration.findSetter(context,attrName) : null;
if(setter!=null) {
// use attribute name as parameter name for incoming value
context = context.newInstanceContext(this, false).newChildContext(); // mimic method call
context.registerInstance(new Variable(attrName, decl.getType()));
context.setValue(attrName, value);
value = setter.interpret(context);
}
value = autocast(decl, value);
values.put(attrName, value);
if(storable!=null && decl.isStorable(context)) {
storable.setData(attrName.toString(), value.getStorableData());
}
}
public PromptoDbId getDbId() {
try {
IValue dbId = values.get(new Identifier(IStore.dbIdName));
return dbId==null ? null : PromptoDbId.of(dbId.getStorableData());
} catch (NotStorableError e) {
throw new RuntimeException(e);
}
}
public void setDbId(PromptoDbId dbId) {
IValue value = new DbIdValue(dbId);
values.put(new Identifier(IStore.dbIdName), value);
}
public Object getOrCreateDbId() throws NotStorableError {
PromptoDbId dbId = getDbId();
if(dbId==null) {
dbId = this.storable.getOrCreateDbId();
setDbId(dbId);
}
return dbId;
}
private IValue autocast(AttributeDeclaration decl, IValue value) {
if(value!=null && value instanceof IntegerValue && decl.getType()==DecimalType.instance())
value = new DecimalValue(((IntegerValue)value).doubleValue());
return value;
}
@Override
public int hashCode() {
return Objects.hash(values);
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof ConcreteInstance))
return false;
if(declaration != ((ConcreteInstance)obj).declaration)
return false;
return this.values.equals(((ConcreteInstance)obj).values);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
for (Entry kvp : this.values.entrySet())
{
if("dbId".equals(kvp.getKey().toString()))
continue;
sb.append(kvp.getKey().toString());
sb.append(":");
sb.append(kvp.getValue().toString());
sb.append(", ");
}
if(sb.length()>2)
sb.setLength( sb.length() - 2);
sb.append("}");
return sb.toString();
}
@Override
public IValue multiply(Context context, IValue value) throws PromptoError {
try {
return interpretOperator(context, value, Operator.MULTIPLY);
} catch(SyntaxError e) {
return super.multiply(context, value);
}
}
@Override
public IValue divide(Context context, IValue value) throws PromptoError {
try {
return interpretOperator(context, value, Operator.DIVIDE);
} catch(SyntaxError e) {
return super.divide(context, value);
}
}
@Override
public IValue intDivide(Context context, IValue value) throws PromptoError {
try {
return interpretOperator(context, value, Operator.IDIVIDE);
} catch(SyntaxError e) {
return super.intDivide(context, value);
}
}
@Override
public IValue modulo(Context context, IValue value) throws PromptoError {
try {
return interpretOperator(context, value, Operator.MODULO);
} catch(SyntaxError e) {
return super.modulo(context, value);
}
}
@Override
public IValue plus(Context context, IValue value) throws PromptoError {
try {
return interpretOperator(context, value, Operator.PLUS);
} catch(SyntaxError e) {
return super.plus(context, value);
}
}
@Override
public IValue minus(Context context, IValue value) throws PromptoError {
try {
return interpretOperator(context, value, Operator.MINUS);
} catch(SyntaxError e) {
return super.minus(context, value);
}
}
private IValue interpretOperator(Context context, IValue value, Operator operator) throws PromptoError {
IMethodDeclaration decl = declaration.findOperator(context, operator, value.getType());
context = context.newInstanceContext(this, false);
Context local = context.newChildContext();
decl.registerParameters(local);
IParameter arg = decl.getParameters().getFirst();
local.setValue(arg.getId(), value);
return decl.interpret(local);
}
@Override
public JsonNode valueToJsonNode(Context context, Function producer) throws PromptoError {
ObjectNode result = JsonNodeFactory.instance.objectNode();
for(Entry entry : values.entrySet())
result.set(entry.getKey().toString(), producer.apply(entry.getValue()));
return result;
}
@Override
public void toJsonStream(Context context, JsonGenerator generator, boolean withType, Map data) throws PromptoError {
try {
if(withType) {
generator.writeStartObject();
generator.writeFieldName("type");
generator.writeString(this.getType().getTypeName());
generator.writeFieldName("value");
}
generator.writeStartObject();
for(Entry entry : values.entrySet()) {
generator.writeFieldName(entry.getKey().toString());
IValue value = entry.getValue();
if(value==null)
generator.writeNull();
else
attributeToJson(context, generator, entry, withType, data);
}
generator.writeEndObject();
if(withType)
generator.writeEndObject();
} catch(IOException e) {
throw new ReadWriteError(e.getMessage());
}
}
private void attributeToJson(Context context, JsonGenerator generator, Entry entry, boolean withType, Map data) throws IOException {
Object id = this.getDbId();
if(id==null)
id = System.identityHashCode(this);
IValue value = entry.getValue();
IType type = value.getType();
// need to wrap dbId to be consistent across all store implementations
boolean wrap = withType && IStore.dbIdName.equals(entry.getKey().toString()) && (type==IntegerType.instance() || type==TextType.instance());
if(wrap) {
generator.writeStartObject();
generator.writeFieldName("type");
generator.writeString(value.getType().getTypeName());
generator.writeFieldName("value");
}
value.toJsonStream(context, generator, withType, data);
if(wrap)
generator.writeEndObject();
}
public DocumentValue toDocumentValue(Context context) {
PromptoDocument doc = new PromptoDocument<>();
for(Entry entry : values.entrySet()) {
IValue value = entry.getValue();
if(value==null)
value = NullValue.instance();
value = value.toDocumentValue(context);
doc.put(entry.getKey(), value);
}
return new DocumentValue(context, doc, false);
}
}