package com.sap.runlet.interpreter.rucola; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.eclipse.emf.ecore.resource.ResourceSet; import com.sap.ap.metamodel.utils.MetamodelUtils; import com.sap.runlet.abstractinterpreter.objects.ClassTypedObject; import com.sap.runlet.abstractinterpreter.objects.EmptyObject; import com.sap.runlet.abstractinterpreter.objects.EntityObject; import com.sap.runlet.abstractinterpreter.objects.RunletObject; import com.sap.runlet.abstractinterpreter.repository.Repository; import com.sap.runlet.abstractinterpreter.repository.SnapshotIdentifier; import com.sap.runlet.abstractinterpreter.util.Base64; import com.sap.runlet.abstractinterpreter.util.Fraction; import com.sap.runlet.interpreter.Activator; import com.sap.runlet.interpreter.RunletInterpreter; import com.sap.runlet.interpreter.objects.NativeObject; import com.sap.runlet.interpreter.objects.ValueObject; import com.sap.runlet.interpreter.repository.simpleimpl.RunletInMemoryRepository; import data.classes.Association; import data.classes.AssociationEnd; import data.classes.ClassTypeDefinition; import data.classes.FunctionSignatureTypeDefinition; import data.classes.NestedTypeDefinition; import data.classes.SapClass; import data.classes.TypeDefinition; /** * A Runlet Java Connector. Serves as a factory for {@link RucolaObject}s based on * {@link RunletObject}s and based on registrations of available facade types. One * such connector instance is tied to a single {@link RunletInterpreter interpreter}. * * @author Axel Uhl D043530 * */ public class Rucola { private final RunletInterpreter interpreter; /** * Initializes a new {@link RunletInterpreter} with a new empty * {@link RunletInMemoryRepository} and wraps it by a {@link Rucola} object. */ public Rucola(ResourceSet resourceSet) { this(resourceSet, new RunletInMemoryRepository(Activator.getDefault().getModelAdapter())); } public Rucola(ResourceSet resourceSet, Repository<Association, AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition> repository) { this(new RunletInterpreter(resourceSet, repository)); } public Rucola(RunletInterpreter interpreter) { this.interpreter = interpreter; } protected RunletInterpreter getInterpreter() { return interpreter; } protected ResourceSet getResourceSet() { return getInterpreter().getResourceSet(); } protected RucolaObject createRucolaObjectForRiverObject(RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> o) { RucolaObject result; if (o == null) { result = null; } else { TypeDefinition td = o.getType(); if (td instanceof NestedTypeDefinition) { throw new RucolaException("Don't know yet how to represent NestedTypeDefinition-based objects in Rucola"); } if (td instanceof ClassTypeDefinition) { ClassTypeDefinition ctd = (ClassTypeDefinition) td; if (ctd.getClazz().isValueType()) { result = new Value(this, o); } else { result = new Entity(this, o); } } else if (td instanceof FunctionSignatureTypeDefinition) { result = new Function(this, o); } else { throw new RucolaException("Don't know how to represent " + o + " in Rucola"); } } return result; } private Value wrapNativeObject(Object o, String runletNativeClassName) { // TODO cache class type definitions ClassTypeDefinition type = MetamodelUtils.createClassTypeDefinitionExactlyOne(getResourceSet(), MetamodelUtils.findClass( getResourceSet(), runletNativeClassName)); return new Value(this, new NativeObject(type, o, getInterpreter().getDefaultSnapshot(), getInterpreter())); } /** * If <tt>o</tt> is of a type that any of the specific <tt>wrap</tt> operations supports as * argument type, that specific operation is used to wrap <tt>o</tt> as a {@link RucolaObject}. * If <tt>o</tt> already happens to be a {@link RucolaObject} instance, it is returned unchanged. * If <tt>o</tt> is of unsupported, unwrappable type, a {@link RucolaException} is thrown. */ protected RucolaObject wrap(Object o, TypeDefinition targetType) { if (o == null) { if (targetType instanceof ClassTypeDefinition) { if (((ClassTypeDefinition) targetType).getClazz().isValueType()) { return new Value(this, new EmptyObject<AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition>( targetType, getInterpreter().getModelAdapter())); } else { return new Entity(this, new EmptyObject<AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition>( targetType, getInterpreter().getModelAdapter())); } } else if (targetType instanceof FunctionSignatureTypeDefinition) { return new Function(this, new EmptyObject<AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition>( targetType, getInterpreter().getModelAdapter())); } else { throw new RucolaException("Don't know how to wrap null for target type "+targetType); } } if (o instanceof RucolaObject) { return (RucolaObject) o; } else if (o instanceof Boolean) { return wrapNativeObject(o, "Boolean"); } else if (o instanceof Integer) { return wrapNativeObject(new Fraction((Integer) o), "Number"); } else if (o instanceof Double) { return wrapNativeObject(new Fraction(new BigDecimal((Double) o)), "Number"); } else if (o instanceof Float) { return wrapNativeObject(new Fraction(new BigDecimal((Float) o)), "Number"); } else if (o instanceof Long) { return wrapNativeObject(new Fraction((Long) o), "Number"); } else if (o instanceof Fraction) { return wrapNativeObject(o, "Number"); } else if (o instanceof BigDecimal) { return wrapNativeObject(new Fraction((BigDecimal) o), "Number"); } else if (o instanceof BigInteger) { return wrapNativeObject(new Fraction((BigInteger) o), "Number"); } else if (o instanceof Date) { return wrapNativeObject(o, "TimePoint"); } else if (o instanceof String) { return wrapNativeObject(o, "String"); } else if (o instanceof Iterable<?>) { // and not a RucolaObject because that was checked first // TODO enable wrapping of Java collections into Rucola MultiObject instances throw new RucolaException("Converting Java collections into Runlet multi-objects not supported yet"); } else { throw new RucolaException("Don't know how to wrap object "+o+" of type "+o.getClass().getName()); } } @SuppressWarnings("unchecked") public void store(Entity e) { for (RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> ro : e.getObject().flatten()) { getInterpreter().storeEntity((EntityObject<Association, AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition>) ro); } } public Value commit() { try { return wrapNativeObject(getInterpreter().commit(), "Snapshot"); } catch (Exception e) { throw new RuntimeException(e); } } public Entity newEntity(String classname) { ClassTypeDefinition type = MetamodelUtils.createClassTypeDefinitionExactlyOne(getResourceSet(), MetamodelUtils.findClass(getResourceSet(), classname)); assert !type.getClazz().isValueType(); EntityObject<Association, AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition> eo = getInterpreter().createEntityObject(type); return new Entity(this, eo); } /** * Pass an ID string that you obtained from {@link Entity#getId()} and you will be * given an entity referring to the same object in the snapshot identified by the * entity from which you obtained the ID. */ public Entity getEntityById(String id) { /* * The ID consists of the Base64-encoded serialization of foud objects: * * the classname * * the UUID * * the SnapshotIdentifier * * the persistent flag */ byte[] serializedEntityObject = Base64.decode(id); ObjectInputStream ois; try { ois = new ObjectInputStream(new ByteArrayInputStream(serializedEntityObject)); String classname = (String) ois.readObject(); UUID uuid = (UUID) ois.readObject(); SnapshotIdentifier snapshotIdentifier = (SnapshotIdentifier) ois.readObject(); boolean isPersistent = ois.readBoolean(); ClassTypeDefinition ctd = MetamodelUtils.createClassTypeDefinitionExactlyOne(getResourceSet(), MetamodelUtils.findClass(getResourceSet(), classname)); EntityObject<Association, AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition> result = new EntityObject<Association, AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition>( ctd, uuid, snapshotIdentifier, getInterpreter().getModelAdapter(), getInterpreter()); result.setPersistent(isPersistent); return new Entity(this, result); } catch (Exception e) { throw new RucolaException("Invalid entity ID "+id, e); } } /** * Obtains a value of the class identified by <tt>classname</tt> that has no * equality-relevant association ends. */ public Value value(String classname) { return value(classname, null); } /** * Obtains a value of the class identified by <tt>classname</tt> with the equality-relevant * association ends identified by the keys of <tt>values</tt> set to the values of the * <tt>values</tt> map.<p> * * The <tt>values</tt> argument may be <tt>null</tt> in case no equality-relevant * links shall be set on the value or the value class does not have any equality-relevant * association ends. */ public Value value(String classname, Map<String, Object> values) { ClassTypeDefinition type = MetamodelUtils.createClassTypeDefinitionExactlyOne(getResourceSet(), MetamodelUtils.findClass(getResourceSet(), classname)); SapClass valueClass = type.getClazz(); assert valueClass.isValueType(); Collection<AssociationEnd> equalityRelevantEnds = valueClass.getEqualityRelevantAssociationEnds(); Map<AssociationEnd, Collection<ClassTypedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>> propertyValues = new HashMap<AssociationEnd, Collection<ClassTypedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>>(); if (values != null) { for (AssociationEnd equalityRelevantEnd : equalityRelevantEnds) { AssociationEnd farEnd = equalityRelevantEnd.otherEnd(); Object value = values.get(farEnd.getName()); if (value != null) { RucolaObject rucolaValue = wrap(value, farEnd.getType()); Collection<ClassTypedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> riverValues = new ArrayList<ClassTypedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>(1); riverValues.add((ClassTypedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>) rucolaValue.getObject()); propertyValues.put(farEnd, riverValues); } } } ValueObject result = getInterpreter().createValueObject(type, propertyValues); return new Value(this, result); } }