/*******************************************************************************
*
* Copyright (C) 2008 Fujitsu Services Ltd.
*
* Author: Nick Battle
*
* This file is part of VDMJ.
*
* VDMJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VDMJ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VDMJ. If not, see <http://www.gnu.org/licenses/>.
*
******************************************************************************/
package org.overture.interpreter.values;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import org.overture.ast.analysis.AnalysisException;
import org.overture.ast.definitions.AExplicitOperationDefinition;
import org.overture.ast.definitions.AImplicitOperationDefinition;
import org.overture.ast.definitions.AStateDefinition;
import org.overture.ast.definitions.ASystemClassDefinition;
import org.overture.ast.definitions.PDefinition;
import org.overture.ast.definitions.SClassDefinition;
import org.overture.ast.expressions.PExp;
import org.overture.ast.factory.AstFactory;
import org.overture.ast.intf.lex.ILexLocation;
import org.overture.ast.intf.lex.ILexNameToken;
import org.overture.ast.lex.Dialect;
import org.overture.ast.lex.LexKeywordToken;
import org.overture.ast.lex.LexNameList;
import org.overture.ast.lex.VDMToken;
import org.overture.ast.modules.AModuleModules;
import org.overture.ast.patterns.APatternListTypePair;
import org.overture.ast.patterns.PPattern;
import org.overture.ast.statements.PStm;
import org.overture.ast.types.AFieldField;
import org.overture.ast.types.AOperationType;
import org.overture.ast.types.PType;
import org.overture.ast.util.Utils;
import org.overture.config.Release;
import org.overture.config.Settings;
import org.overture.interpreter.assistant.IInterpreterAssistantFactory;
import org.overture.interpreter.assistant.definition.SClassDefinitionAssistantInterpreter;
import org.overture.interpreter.messages.Console;
import org.overture.interpreter.messages.rtlog.RTExtendedTextMessage;
import org.overture.interpreter.messages.rtlog.RTLogger;
import org.overture.interpreter.messages.rtlog.RTMessage.MessageType;
import org.overture.interpreter.messages.rtlog.RTOperationMessage;
import org.overture.interpreter.runtime.ClassContext;
import org.overture.interpreter.runtime.ClassInterpreter;
import org.overture.interpreter.runtime.Context;
import org.overture.interpreter.runtime.Interpreter;
import org.overture.interpreter.runtime.ModuleInterpreter;
import org.overture.interpreter.runtime.ObjectContext;
import org.overture.interpreter.runtime.PatternMatchException;
import org.overture.interpreter.runtime.RootContext;
import org.overture.interpreter.runtime.RuntimeValidator;
import org.overture.interpreter.runtime.StateContext;
import org.overture.interpreter.runtime.ValueException;
import org.overture.interpreter.runtime.VdmRuntime;
import org.overture.interpreter.scheduler.AsyncThread;
import org.overture.interpreter.scheduler.BasicSchedulableThread;
import org.overture.interpreter.scheduler.CPUResource;
import org.overture.interpreter.scheduler.Holder;
import org.overture.interpreter.scheduler.ISchedulableThread;
import org.overture.interpreter.scheduler.InitThread;
import org.overture.interpreter.scheduler.Lock;
import org.overture.interpreter.scheduler.MessageRequest;
import org.overture.interpreter.scheduler.MessageResponse;
import org.overture.interpreter.scheduler.ResourceScheduler;
import org.overture.interpreter.solver.IConstraintSolver;
import org.overture.interpreter.solver.SolverFactory;
import org.overture.parser.config.Properties;
public class OperationValue extends Value
{
private static final long serialVersionUID = 1L;
public final AExplicitOperationDefinition expldef;
public final AImplicitOperationDefinition impldef;
public final ILexNameToken name;
public final AOperationType type;
public final List<PPattern> paramPatterns;
public final PStm body;
public final FunctionValue precondition;
public final FunctionValue postcondition;
public final AStateDefinition state;
public final SClassDefinition classdef;
private ILexNameToken stateName = null;
private Context stateContext = null;
private ObjectValue self = null;
public boolean isConstructor = false;
public boolean isStatic = false;
public boolean isAsync = false;
private PExp guard = null;
private int hashAct = 0; // Number of activations
private int hashFin = 0; // Number of finishes
private int hashReq = 0; // Number of requests
public int getHashAct()
{
return this.hashAct;
}
public int getHashFin()
{
return this.hashFin;
}
public int getHashReq()
{
return this.hashReq;
}
private long priority = 0;
private boolean traceRT = true;
public OperationValue(AExplicitOperationDefinition def,
FunctionValue precondition, FunctionValue postcondition,
AStateDefinition state,
IInterpreterAssistantFactory assistantFactory)
{
this(def, precondition, postcondition, state, assistantFactory.createPAccessSpecifierAssistant().isAsync(def.getAccess()));
}
private OperationValue(AExplicitOperationDefinition def,
FunctionValue precondition, FunctionValue postcondition,
AStateDefinition state, boolean async)
{
this.expldef = def;
this.impldef = null;
this.name = def.getName();
this.type = (AOperationType) def.getType();
this.paramPatterns = def.getParameterPatterns();
this.body = def.getBody();
this.precondition = precondition;
this.postcondition = postcondition;
this.state = state;
this.classdef = def.getClassDefinition();
this.isAsync = async;
traceRT = Settings.dialect == Dialect.VDM_RT && classdef != null
&& !(classdef instanceof ASystemClassDefinition)
&& !classdef.getName().getName().equals("CPU")
&& !classdef.getName().getName().equals("BUS")
&& !name.getName().equals("thread")
&& !name.getName().startsWith("inv_");
}
public OperationValue(AImplicitOperationDefinition def,
FunctionValue precondition, FunctionValue postcondition,
AStateDefinition state,
IInterpreterAssistantFactory assistantFactory)
{
this(def, precondition, postcondition, state, assistantFactory.createPAccessSpecifierAssistant().isAsync(def.getAccess()));
}
private OperationValue(AImplicitOperationDefinition def,
FunctionValue precondition, FunctionValue postcondition,
AStateDefinition state, boolean async)
{
this.impldef = def;
this.expldef = null;
this.name = def.getName();
this.type = (AOperationType) def.getType();
this.paramPatterns = new Vector<PPattern>();
for (APatternListTypePair ptp : def.getParameterPatterns())
{
paramPatterns.addAll(ptp.getPatterns());
}
this.body = def.getBody();
this.precondition = precondition;
this.postcondition = postcondition;
this.state = state;
this.classdef = def.getClassDefinition();
this.isAsync = async;
traceRT = Settings.dialect == Dialect.VDM_RT && classdef != null
&& !(classdef instanceof ASystemClassDefinition)
&& !classdef.getName().getName().equals("CPU")
&& !classdef.getName().getName().equals("BUS")
&& !name.getName().equals("thread");
}
@Override
public String toString()
{
return type.toString();
}
public void setSelf(ObjectValue self)
{
if (!isStatic)
{
this.self = self;
}
}
public ObjectValue getSelf()
{
return self;
}
public void setGuard(PExp add, boolean isMutex)
{
if (guard == null)
{
guard = add;
} else
{
// Create "old and new" expression
ILexLocation where = isMutex ? guard.getLocation()
: add.getLocation();
guard = AstFactory.newAAndBooleanBinaryExp(guard.clone(), new LexKeywordToken(VDMToken.AND, where), add.clone());
}
}
public void prepareGuard(ObjectContext ctxt)
{
if (guard != null)
{
ValueListener vl = new GuardValueListener(getGuardLock(ctxt.assistantFactory));
for (Value v : ctxt.assistantFactory.createPExpAssistant().getValues(guard, ctxt))
{
UpdatableValue uv = (UpdatableValue) v;
uv.addListener(vl);
}
}
}
public Value eval(ILexLocation from, ValueList argValues, Context ctxt)
throws AnalysisException
{
// Note args cannot be Updateable, so we convert them here. This means
// that TransactionValues pass the local "new" value to the far end.
ValueList constValues = argValues.getConstant();
if (Settings.dialect == Dialect.VDM_RT)
{
if (!isStatic && (ctxt.threadState.CPU != self.getCPU() || isAsync))
{
return asyncEval(constValues, ctxt);
} else
{
return localEval(from, constValues, ctxt, true);
}
} else
{
return localEval(from, constValues, ctxt, true);
}
}
public Value localEval(ILexLocation from, ValueList argValues,
Context ctxt, boolean logreq) throws AnalysisException
{
if (state != null && stateName == null)
{
stateName = state.getName();
stateContext = ctxt.assistantFactory.createAStateDefinitionAssistant().getStateContext(state);
}
RootContext argContext = newContext(from, toTitle(), ctxt);
req(logreq);
notifySelf(ctxt.assistantFactory);
if (guard != null)
{
guard(argContext);
} else
{
act(); // Still activated, even if no guard
}
notifySelf(ctxt.assistantFactory);
if (argValues.size() != paramPatterns.size())
{
abort(4068, "Wrong number of arguments passed to " + name.getName(), ctxt);
}
ListIterator<Value> valIter = argValues.listIterator();
Iterator<PType> typeIter = type.getParameters().iterator();
NameValuePairMap args = new NameValuePairMap();
for (PPattern p : paramPatterns)
{
try
{
// Note values are assumed to be constant, as enforced by eval()
Value pv = valIter.next().convertTo(typeIter.next(), ctxt);
for (NameValuePair nvp : ctxt.assistantFactory.createPPatternAssistant().getNamedValues(p, pv, ctxt))
{
Value v = args.get(nvp.name);
if (v == null)
{
args.put(nvp);
} else
// Names match, so values must also
{
if (!v.equals(nvp.value))
{
abort(4069, "Parameter patterns do not match arguments", ctxt);
}
}
}
} catch (PatternMatchException e)
{
abort(e.number, e, ctxt);
}
}
if (self != null)
{
argContext.put(name.getSelfName(), self);
}
// Note: arg name/values hide member values
argContext.putAll(args);
Value originalSigma = null;
MapValue originalValues = null;
if (postcondition != null)
{
if (stateName != null)
{
Value sigma = argContext.lookup(stateName);
originalSigma = (Value) sigma.clone();
} else if (self != null)
{
// originalSelf = self.shallowCopy();
LexNameList oldnames = ctxt.assistantFactory.createPExpAssistant().getOldNames(postcondition.body);
originalValues = self.getOldValues(oldnames);
} else if (classdef != null)
{
LexNameList oldnames = ctxt.assistantFactory.createPExpAssistant().getOldNames(postcondition.body);
SClassDefinitionAssistantInterpreter assistant = ctxt.assistantFactory.createSClassDefinitionAssistant();
originalValues = assistant.getOldValues(classdef, oldnames);
}
}
// Make sure the #fin is updated with ErrorExceptions, using the
// finally clause...
Value rv = null;
try
{
if (precondition != null && Settings.prechecks)
{
ValueList preArgs = new ValueList(argValues);
if (stateName != null)
{
preArgs.add(argContext.lookup(stateName));
} else if (self != null)
{
preArgs.add(self);
}
// We disable the swapping and time (RT) as precondition checks should be "free".
try
{
ctxt.threadState.setAtomic(true);
ctxt.setPrepost(4071, "Precondition failure: ");
precondition.eval(from, preArgs, ctxt);
} finally
{
ctxt.setPrepost(0, null);
ctxt.threadState.setAtomic(false);
}
}
if (body == null)
{
IConstraintSolver solver = SolverFactory.getSolver(Settings.dialect);
if (solver != null)
{
rv = invokeSolver(ctxt, argContext, args, rv, solver);
} else
{
abort(4066, "Cannot call implicit operation: " + name, ctxt);
}
} else
{
if (Settings.release == Release.VDM_10 && !type.getPure() && ctxt.threadState.isPure())
{
abort(4166, "Cannot call impure operation: " + name, ctxt);
}
rv = body.apply(VdmRuntime.getStatementEvaluator(), argContext);
}
if (isConstructor)
{
rv = self;
} else
{
rv = rv.convertTo(type.getResult(), argContext);
}
if (postcondition != null && Settings.postchecks)
{
ValueList postArgs = new ValueList(argValues);
if (!(rv instanceof VoidValue))
{
postArgs.add(rv);
}
if (stateName != null)
{
postArgs.add(originalSigma);
Value sigma = argContext.lookup(stateName);
postArgs.add(sigma);
} else if (self != null)
{
postArgs.add(originalValues);
postArgs.add(self);
} else if (classdef != null)
{
postArgs.add(originalValues);
}
// We disable the swapping and time (RT) as postcondition checks should be "free".
try
{
ctxt.threadState.setAtomic(true);
ctxt.setPrepost(4072, "Postcondition failure: ");
postcondition.eval(from, postArgs, ctxt);
} finally
{
ctxt.setPrepost(0, null);
ctxt.threadState.setAtomic(false);
}
}
} catch (AnalysisException e)
{
if (e instanceof ValueException)
{
throw (ValueException) e;
}
e.printStackTrace();
} finally
{
fin();
notifySelf(ctxt.assistantFactory);
}
return rv;
}
public Value invokeSolver(Context ctxt, RootContext argContext,
NameValuePairMap args, Value rv, IConstraintSolver solver)
throws ValueException
{
try
{
Map<String, String> argExps = new HashMap<String, String>();
for (Entry<ILexNameToken, Value> argVal : args.entrySet())
{
argExps.put(argVal.getKey().getName(), argVal.getValue().toString());
}
Map<String, String> stateExps = new HashMap<String, String>();
if (stateContext != null)
{
for (Entry<ILexNameToken, Value> argVal : stateContext.entrySet())
{
if (argVal.getKey().parent() instanceof AFieldField)
{
stateExps.put(argVal.getKey().getName(), argVal.getValue().toString());
}
}
} else
{
// TODO
if (self != null)
{
for (Entry<ILexNameToken, Value> argVal : self.getMemberValues().entrySet())
{
if (argVal.getValue() instanceof FunctionValue
|| argVal.getValue() instanceof OperationValue)
{
continue;
}
if (argVal.getValue() instanceof UpdatableValue)
{
stateExps.put(argVal.getKey().getName(), argVal.getValue().toString());
}
}
}
}
Interpreter interpreter = Interpreter.getInstance();
List<PDefinition> allDefs = new Vector<PDefinition>();
if (interpreter instanceof ClassInterpreter)
{
for (SClassDefinition c : ((ClassInterpreter) interpreter).getClasses())
{
allDefs.addAll(c.getDefinitions());
}
} else if (interpreter instanceof ModuleInterpreter)
{
for (AModuleModules c : ((ModuleInterpreter) interpreter).getModules())
{
allDefs.addAll(c.getDefs());
}
}
PStm res = solver.solve(allDefs, name.getName(), this.impldef, stateExps, argExps, Console.out, Console.err);
rv = res.apply(VdmRuntime.getStatementEvaluator(), argContext);
} catch (Exception e)
{
e.printStackTrace();
abort(4066, "Cannot call implicit operation: " + name, ctxt);
}
return rv;
}
private RootContext newContext(ILexLocation from, String title, Context ctxt)
{
RootContext argContext;
if (self != null)
{
argContext = new ObjectContext(Interpreter.getInstance().getAssistantFactory(), from, title, ctxt, self);
} else if (classdef != null)
{
argContext = new ClassContext(Interpreter.getInstance().getAssistantFactory(), from, title, ctxt, classdef);
} else
{
argContext = new StateContext(Interpreter.getInstance().getAssistantFactory(), from, title, ctxt, stateContext);
}
return argContext;
}
private Lock getGuardLock(IInterpreterAssistantFactory assistantFactory)
{
if (classdef != null)
{
return VdmRuntime.getNodeState(assistantFactory, classdef).guardLock;
} else if (self != null)
{
return self.guardLock;
} else
{
return null;
}
}
private Object getGuardObject(Context ctxt)
{
if (ctxt instanceof ClassContext)
{
ClassContext cctxt = (ClassContext) ctxt;
return cctxt.classdef;
} else
{
return self;
}
}
private void guard(Context ctxt) throws ValueException
{
ISchedulableThread th = BasicSchedulableThread.getThread(Thread.currentThread());
if (th == null || th instanceof InitThread)
{
act();
return; // Probably during initialization.
}
Lock lock = getGuardLock(ctxt.assistantFactory);
lock.lock(ctxt, guard.getLocation());
while (true)
{
synchronized (getGuardObject(ctxt)) // So that test and act() are atomic
{
// We have to suspend thread swapping round the guard,
// else we will reschedule another CPU thread while
// having self locked, and that locks up everything!
boolean ok = false;
try
{
debug("guard TEST");
ctxt.threadState.setAtomic(true);
try
{
ok = guard.apply(VdmRuntime.getExpressionEvaluator(), ctxt).boolValue(ctxt);
} catch (AnalysisException e)
{
if (e instanceof ValueException)
{
throw (ValueException) e;
}
e.printStackTrace();
}
} finally
{
ctxt.threadState.setAtomic(false);
}
if (ok)
{
debug("guard OK");
act();
break; // Out of while loop
}
}
// The guardLock list is signalled by the GuardValueListener
// and by notifySelf when something changes. The guardOp
// is set to indicate the guard state to any breakpoints.
debug("guard WAIT");
ctxt.guardOp = this;
lock.block(ctxt, guard.getLocation());
ctxt.guardOp = null;
debug("guard WAKE");
}
lock.unlock();
}
private void notifySelf(IInterpreterAssistantFactory assistantFactory)
{
Lock lock = getGuardLock(assistantFactory);
if (lock != null)
{
debug("Signal guard");
lock.signal();
}
}
private Value asyncEval(ValueList argValues, Context ctxt)
throws ValueException
{
// Spawn a thread, send a message, wait for a reply...
CPUValue from = ctxt.threadState.CPU;
CPUValue to = self.getCPU();
boolean stepping = ctxt.threadState.isStepping();
long threadId = BasicSchedulableThread.getThread(Thread.currentThread()).getId();
// Async calls have the OpRequest made by the caller using the
// "from" CPU, whereas the OpActivate and OpComplete are made
// by the called object, using self's CPU (see trace(msg)).
RTLogger.log(new RTOperationMessage(MessageType.Request, this, from.resource, threadId));
if (from != to) // Remote CPU call
{
BUSValue bus = BUSValue.lookupBUS(from, to);
if (bus == null)
{
abort(4140, "No BUS between CPUs " + from.getName() + " and "
+ to.getName(), ctxt);
}
if (isAsync) // Don't wait
{
MessageRequest request = new MessageRequest(ctxt.threadState.dbgp, bus, from, to, self, this, argValues, null, stepping);
bus.transmit(request);
return new VoidValue();
} else
{
Holder<MessageResponse> result = new Holder<MessageResponse>();
MessageRequest request = new MessageRequest(ctxt.threadState.dbgp, bus, from, to, self, this, argValues, result, stepping);
bus.transmit(request);
MessageResponse reply = result.get(ctxt, name.getLocation());
return reply.getValue(); // Can throw a returned exception
}
} else
// local, must be async so don't wait
{
MessageRequest request = new MessageRequest(ctxt.threadState.dbgp, null, from, to, self, this, argValues, null, stepping);
AsyncThread t = new AsyncThread(request);
t.start();
RuntimeValidator.validateAsync(this, t);
return new VoidValue();
}
}
@Override
public boolean equals(Object other)
{
if (other instanceof Value)
{
Value val = ((Value) other).deref();
if (val instanceof OperationValue)
{
OperationValue ov = (OperationValue) val;
return ov.type.equals(type);
}
}
return false;
}
@Override
public int hashCode()
{
return type.hashCode();
}
@Override
public String kind()
{
return "operation";
}
@Override
protected Value convertValueTo(PType to, Context ctxt, Set<PType> done)
throws AnalysisException
{
if (ctxt.assistantFactory.createPTypeAssistant().isType(to, AOperationType.class))
{
return this;
} else
{
return super.convertValueTo(to, ctxt, done);
}
}
@Override
public OperationValue operationValue(Context ctxt)
{
return this;
}
@Override
public Object clone()
{
if (expldef != null)
{
return new OperationValue(expldef, precondition, postcondition, state, isAsync);
} else
{
return new OperationValue(impldef, precondition, postcondition, state, isAsync);
}
}
private synchronized void req(boolean logreq)
{
hashReq++;
MessageType type = MessageType.Request;
RuntimeValidator.validate(this, type);
if (logreq) // Async OpRequests are made in asyncEval
{
trace(type);
}
debug("#req = " + hashReq);
}
private synchronized void act()
{
hashAct++;
if (!ResourceScheduler.isStopping())
{
MessageType type = MessageType.Activate;
RuntimeValidator.validate(this, type);
trace(type);
debug("#act = " + hashAct);
}
}
private synchronized void fin()
{
hashFin++;
if (!ResourceScheduler.isStopping())
{
MessageType type = MessageType.Completed;
RuntimeValidator.validate(this, type);
trace(type);
debug("#fin = " + hashFin);
}
}
private void trace(MessageType kind)
{
if (traceRT)
{
ISchedulableThread ct = BasicSchedulableThread.getThread(Thread.currentThread());
if (isStatic)
{
CPUResource cpu = null;
if (ct instanceof InitThread)
{
cpu = CPUValue.vCPU.resource; // Initialization on vCPU
} else
{
cpu = ct.getCPUResource();
}
RTLogger.log(new RTOperationMessage(kind, this, cpu, ct.getId()));
} else
{
RTLogger.log(new RTOperationMessage(kind, this, self.getCPU().resource, ct.getId()));
}
}
}
/**
* @param string
*/
private void debug(String string)
{
if (Properties.diags_guards)
{
if (Settings.dialect == Dialect.VDM_PP)
{
System.err.println(String.format("%s %s %s", BasicSchedulableThread.getThread(Thread.currentThread()), name, string));
} else
{
RTLogger.log(new RTExtendedTextMessage(String.format("-- %s %s %s", BasicSchedulableThread.getThread(Thread.currentThread()), name, string)));
}
}
}
public synchronized void setPriority(long priority)
{
this.priority = priority;
}
public synchronized long getPriority()
{
return priority;
}
public synchronized CPUValue getCPU()
{
return self == null ? CPUValue.vCPU : self.getCPU();
}
public String toTitle()
{
return name.getName()
+ Utils.listToString("(", paramPatterns, ", ", ")");
}
}