package com.laytonsmith.core.natives.interfaces;
import com.laytonsmith.annotations.nofield;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CNull;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* An MObject is a definition of an array with a certain configuration. While an MObject can be constructed
* directly, this is generally discouraged, since the reusability is lower this way. Instead, subclass it,
* add direct accessors for various properties, and construct that instead. The static methods
* provided allow for creation of an instance, given a CArray.
*
* Subclasses should note that a typical implementation will look like this:
* <pre>
* public Integer x;
* ...
* public void setX(Integer x, Target t){
* super.set("x", x, t);
* }
* ...
* </pre>
*
* Additionally, the alias function can be overriden to provide for aliased
* values, which will be accepted as non-dynamic parameters that point to
* the real fields. They will not be included during serialization, however,
* reads and writes to these fields will work correctly.
*
* If an object contains fields that should not be handled as instance fields,
* they must be set to private, and they will be
* ignored. All other fields should be public. If a field MUST be public, but
* not accessible, you may annotate it with the {@link nofield} annotation, though
* this is highly discouraged, since it breaks future compatibility.
*
* Note that in the example, an Integer is used. This is appropriate, though Construct
* types may be used as well. The supported POJO types are only the following,
* and conversions are automatic: All 8 primitives' object wrappers, String, MList,
* MObject (and subclasses) and MMap.
*
*/
public class MObject {
public static <T extends MObject> T Construct(Class<T> type, CArray data){
T instance;
try {
instance = type.newInstance();
} catch (InstantiationException ex) {
throw new RuntimeException(type.getName() + " does not have a default constructor.");
} catch (IllegalAccessException ex) {
throw new RuntimeException(type.getName() + "'s default constructor is not public.");
}
return null; //TODO
}
/**
* Constructs a fully dynamic MObject based on the given array. This is discouraged
* from direct use.
* @param data
* @return
*/
public static MObject Construct(CArray data){
return Construct(MObject.class, data);
}
private Map<String, Construct> fields = new HashMap<String, Construct>();
/**
* If a field can have an alias, this should return the proper
* name given this alias. If this is not an alias, return null, and
* the parameter will be added as a dynamic parameter, or if it actually
* represents a field, that is set instead. If both the value and the
* alias are set, the actual value takes priority, should the two values
* be different.
* @param field
* @return
*/
protected String alias(String field){
return null;
}
/**
* Sets the field to the given parameter. If the field is a non-dynamic
* property, it is actually set in the object (and converted properly),
* otherwise it is simply added to the dynamic field list.
* @param field
* @param value
* @param t
*/
public final void set(String field, Construct value, Target t){
String alias = alias(field);
if(alias != null){
field = alias;
}
for(Field f : this.getClass().getFields()){
if(f.isAnnotationPresent(nofield.class)){
//Skip this one
continue;
}
if(f.getName().equals(field)){
//This is it, so let's set it, (converting if necessary) then break
Object val;
Class fType = f.getType();
if(value instanceof CNull){ //TODO
//Easy case
val = null;
} else {
if(fType == byte.class){
val = Static.getInt8(value, t);
} else if(fType == short.class){
val = Static.getInt16(value, t);
} else if(fType == int.class){
val = Static.getInt32(value, t);
} else if(fType == long.class){
val = Static.getInt(value, t);
} else if(fType == char.class){
if(value.val().length() == 0){
val = null;
} else {
val = value.val().charAt(0);
}
} else if(fType == boolean.class){
val = Static.getBoolean(value);
} else if(fType == float.class){
val = Static.getDouble32(value, t);
} else if(fType == double.class){
val = Static.getDouble(value, t);
} else if(fType == MMap.class){
CArray ca = Static.getArray(value, t);
MMap m = new MMap();
for(String key : ca.stringKeySet()){
m.put(key, ca.get(key, t));
}
val = m;
} else if(fType == MList.class){
CArray ca = Static.getArray(value, t);
MList m = new MList();
if(ca.inAssociativeMode()){
throw new CRECastException("Expected non-associative array, but an associative array was found instead.", t);
}
for(int i = 0; i < ca.size(); i++){
m.add(ca.get(i, t));
}
val = m;
} else if(Construct.class.isAssignableFrom(fType)){
val = value;
} else if(MObject.class.isAssignableFrom(fType)){
CArray ca = Static.getArray(value, t);
val = MObject.Construct(fType, ca);
} else {
//Programming error.
throw new Error(this.getClass().getName() + " contained the public field "
+ f.getName() + " of type " + fType.getName() + ", which is an unsupported field type.");
}
}
try {
//val is now set correctly, guaranteed.
f.set(this, val);
//These exceptions cannot happen.
} catch (IllegalArgumentException ex) {
throw new Error(ex);
} catch (IllegalAccessException ex) {
throw new Error(ex);
}
}
}
//Always put the dynamic parameter, regardless
fields.put(field, value);
}
/**
* Retrieves a Construct from the
* @param field
* @return
*/
public Construct get(String field){
return null; //TODO
}
}