/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
*/
package org.olat.course.condition.interpreter;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.Collection;
import org.olat.core.gui.translator.Translator;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.Util;
import org.olat.course.condition.Condition;
import org.olat.course.condition.interpreter.score.GetPassedFunction;
import org.olat.course.condition.interpreter.score.GetPassedWithCourseIdFunction;
import org.olat.course.condition.interpreter.score.GetScoreFunction;
import org.olat.course.condition.interpreter.score.GetScoreWithCourseIdFunction;
import org.olat.course.db.interpreter.GetUserCourseDBFunction;
import org.olat.course.editor.CourseEditorEnv;
import org.olat.course.run.userview.UserCourseEnvironment;
import com.neemsoft.jmep.Environment;
import com.neemsoft.jmep.Expression;
import com.neemsoft.jmep.XExpression;
import com.neemsoft.jmep.XIllegalOperation;
import com.neemsoft.jmep.XIllegalStatus;
import com.neemsoft.jmep.XUndefinedFunction;
import com.neemsoft.jmep.XUndefinedUnit;
import com.neemsoft.jmep.XUndefinedVariable;
import de.bps.course.condition.interpreter.score.GetOnyxTestOutcomeAnumFunction;
import de.bps.course.condition.interpreter.score.GetOnyxTestOutcomeNumFunction;
/**
* Initial Date: Jan 27, 2004
* @author gnaegi
* Comment:
*/
public class ConditionInterpreter {
private OLog log = Tracing.createLoggerFor(this.getClass());
/** static Integer(1) object */
public static final Integer INT_TRUE = new Integer(1);
/** static Integer(0) object */
public static final Integer INT_FALSE = new Integer(0);
protected Environment env;
protected Translator translator;
protected UserCourseEnvironment uce;
/**
* ConditionInterpreter interpretes course conditions.
*
* @param userCourseEnv
*/
public ConditionInterpreter(UserCourseEnvironment userCourseEnv) {
uce = userCourseEnv;
//
CourseEditorEnv cev = uce.getCourseEditorEnv();
if (cev != null) {
translator = Util.createPackageTranslator(ConditionInterpreter.class, cev.getEditorEnvLocale());
}
env = new Environment();
// constants: add for user convenience
env.addConstant("true", 1);
env.addConstant("false", 0);
// variables
env.addVariable(NowVariable.name, new NowVariable(userCourseEnv));
env.addVariable(TodayVariable.name, new TodayVariable(userCourseEnv));
env.addVariable(NeverVariable.name, new NeverVariable(userCourseEnv));
env.addVariable(AnyCourseVariable.name, new AnyCourseVariable());
// functions
env.addFunction(DateFunction.name, new DateFunction(userCourseEnv));
env.addFunction("inGroup", new InLearningGroupFunction(userCourseEnv, "inGroup")); // legacy
env.addFunction("inLearningGroup", new InLearningGroupFunction(userCourseEnv, "inLearningGroup"));
env.addFunction("isLearningGroupFull", new IsLearningGroupFullFunction(userCourseEnv));
env.addFunction(InRightGroupFunction.name, new InRightGroupFunction(userCourseEnv));
env.addFunction(InLearningAreaFunction.name, new InLearningAreaFunction(userCourseEnv));
env.addFunction(IsUserFunction.name, new IsUserFunction(userCourseEnv));
env.addFunction(IsGuestFunction.name, new IsGuestFunction(userCourseEnv));
env.addFunction(IsGlobalAuthorFunction.name, new IsGlobalAuthorFunction(userCourseEnv));
env.addFunction(Sleep.name, new Sleep(userCourseEnv));
EvalAttributeFunction eaf;
eaf = new EvalAttributeFunction(userCourseEnv, EvalAttributeFunction.FUNCTION_TYPE_HAS_ATTRIBUTE);
env.addFunction(eaf.name, eaf);
eaf = new EvalAttributeFunction(userCourseEnv, EvalAttributeFunction.FUNCTION_TYPE_IS_IN_ATTRIBUTE);
env.addFunction(eaf.name, eaf);
eaf = new EvalAttributeFunction(userCourseEnv, EvalAttributeFunction.FUNCTION_TYPE_HAS_NOT_ATTRIBUTE);
env.addFunction(eaf.name, eaf);
eaf = new EvalAttributeFunction(userCourseEnv, EvalAttributeFunction.FUNCTION_TYPE_IS_NOT_IN_ATTRIBUTE);
env.addFunction(eaf.name, eaf);
eaf = new EvalAttributeFunction(userCourseEnv, EvalAttributeFunction.FUNCTION_TYPE_ATTRIBUTE_ENDS_WITH);
env.addFunction(eaf.name, eaf);
eaf = new EvalAttributeFunction(userCourseEnv, EvalAttributeFunction.FUNCTION_TYPE_ATTRIBUTE_STARTS_WITH);
env.addFunction(eaf.name, eaf);
EvalUserPropertyFunction eupf;
eupf = new EvalUserPropertyFunction(userCourseEnv, EvalUserPropertyFunction.FUNCTION_TYPE_HAS_NOT_PROPERTY);
env.addFunction(EvalUserPropertyFunction.FUNCTION_NAME_HAS_NOT_PROPERTY, eupf);
eupf = new EvalUserPropertyFunction(userCourseEnv, EvalUserPropertyFunction.FUNCTION_TYPE_HAS_PROPERTY);
env.addFunction(EvalUserPropertyFunction.FUNCTION_NAME_HAS_PROPERTY, eupf);
eupf = new EvalUserPropertyFunction(userCourseEnv, EvalUserPropertyFunction.FUNCTION_TYPE_IS_IN_PROPERTY);
env.addFunction(EvalUserPropertyFunction.FUNCTION_NAME_IS_IN_PROPERTY, eupf);
eupf = new EvalUserPropertyFunction(userCourseEnv, EvalUserPropertyFunction.FUNCTION_TYPE_IS_NOT_IN_PROPERTY);
env.addFunction(EvalUserPropertyFunction.FUNCTION_NAME_IS_NOT_IN_PROPERTY, eupf);
eupf = new EvalUserPropertyFunction(userCourseEnv, EvalUserPropertyFunction.FUNCTION_TYPE_PROPERTY_ENDS_WITH);
env.addFunction(EvalUserPropertyFunction.FUNCTION_NAME_PROPERTY_ENDS_WITH, eupf);
eupf = new EvalUserPropertyFunction(userCourseEnv, EvalUserPropertyFunction.FUNCTION_TYPE_PROPERTY_STARTS_WITH);
env.addFunction(EvalUserPropertyFunction.FUNCTION_NAME_PROPERTY_STARTS_WITH, eupf);
env.addFunction(GetUserPropertyFunction.name, new GetUserPropertyFunction(userCourseEnv));
env.addFunction(GetUserCourseDBFunction.name, new GetUserCourseDBFunction(userCourseEnv));
env.addFunction(HasLanguageFunction.name, new HasLanguageFunction(userCourseEnv));
env.addFunction(InInstitutionFunction.name, new InInstitutionFunction(userCourseEnv));
env.addFunction(IsCourseCoachFunction.name, new IsCourseCoachFunction(userCourseEnv));
env.addFunction(IsCourseParticipantFunction.name, new IsCourseParticipantFunction(userCourseEnv));
env.addFunction(IsCourseAdministratorFunction.name, new IsCourseAdministratorFunction(userCourseEnv));
env.addFunction(IsAssessmentModeFunction.name, new IsAssessmentModeFunction(userCourseEnv));
env.addFunction(GetCourseBeginDateFunction.name, new GetCourseBeginDateFunction(userCourseEnv));
env.addFunction(GetCourseEndDateFunction.name, new GetCourseEndDateFunction(userCourseEnv));
env.addFunction(GetInitialCourseLaunchDateFunction.name, new GetInitialCourseLaunchDateFunction(userCourseEnv));
env.addFunction(GetRecentCourseLaunchDateFunction.name, new GetRecentCourseLaunchDateFunction(userCourseEnv));
env.addFunction(GetAttemptsFunction.name, new GetAttemptsFunction(userCourseEnv));
env.addFunction(GetLastAttemptDateFunction.name, new GetLastAttemptDateFunction(userCourseEnv));
// enrollment building block specific functions
env.addFunction(GetInitialEnrollmentDateFunction.name, new GetInitialEnrollmentDateFunction(userCourseEnv));
env.addFunction(GetRecentEnrollmentDateFunction.name, new GetRecentEnrollmentDateFunction(userCourseEnv));
// functions to calculate score
env.addFunction(GetPassedFunction.name, new GetPassedFunction(userCourseEnv));
env.addFunction(GetScoreFunction.name, new GetScoreFunction(userCourseEnv));
env.addFunction(GetPassedWithCourseIdFunction.name, new GetPassedWithCourseIdFunction(userCourseEnv));
env.addFunction(GetScoreWithCourseIdFunction.name, new GetScoreWithCourseIdFunction(userCourseEnv));
env.addFunction(GetOnyxTestOutcomeNumFunction.name, new GetOnyxTestOutcomeNumFunction(userCourseEnv));
env.addFunction(GetOnyxTestOutcomeAnumFunction.name, new GetOnyxTestOutcomeAnumFunction(userCourseEnv));
// units
env.addUnit("min", new MinuteUnit());
env.addUnit("h", new HourUnit());
env.addUnit("d", new DayUnit());
env.addUnit("w", new WeekUnit());
env.addUnit("m", new MonthUnit());
}
public UserCourseEnvironment getUserCourseEnvironment() {
return uce;
}
/**
* @param expression
* @return null if no error, else the error msg
*/
public ConditionErrorMessage[] syntaxTestExpression(ConditionExpression condExpr) {
try {
/*
* the functions, units, variables from the condition expression access
* the active condition expression through the
* CourseEditorEnv.getActiveConditionExpression(). Whereas the active
* condition expression is determined by calling
* CourseEditorEnv.validateConditionExpression(). Hence
* syntaxTestExpression should never be called directly or in a non editor
* environment.
*/
String conditionString = condExpr.getExptressionString();
Expression exp = new Expression(conditionString, env);
exp.evaluate();
Exception[] condExceptions = condExpr.getExceptions();
ConditionErrorMessage[] cems = null;
if (condExceptions != null && condExceptions.length > 0) {
// create an error message from the first error in the expression
cems=new ConditionErrorMessage[condExceptions.length];
for (int i = 0; i < condExceptions.length; i++) {
cems[i]=handleExpressionExceptions(condExceptions[i]);
}
return cems;
}// else return null, at the end of the method!
} catch (Exception e) {
// catches every non ArgumentParseException
return new ConditionErrorMessage[] {handleExpressionExceptions(e)};
}
// if no exception was found, thus no condition error message to report.
return null;
}
/**
* Check an expression on syntactical errors.
*
* @param expression
* @return Null if syntactically correct, error message otherwise.
* @deprecated TODO: remove as it is no longer referenced, except test?
*/
public ConditionErrorMessage syntaxTestCalculation(String expression) {
try {
Expression exp = new Expression(expression, env);
exp.evaluate();
} catch (Exception e) {
return handleExpressionExceptions(e);
}
return null;
}
/**
* evaluation of expression may throw exceptions, the handling of these is
* done here.
*
* @param e
* @return
*/
private ConditionErrorMessage handleExpressionExceptions(Exception e) {
String msg = "";
String[] params = null;
String solutionMsg = "";
try {
throw e;
//TODO:pb:b do no rethrow, but test for instanceof
} catch (XIllegalOperation xe) {
msg = "error.illegal.operation.at";
params = new String[] { Integer.toString(xe.getPosition()) };
} catch (XUndefinedFunction xe) {
msg = "error.undefined.function.at";
params = new String[] { Integer.toString(xe.getPosition()) };
} catch (XUndefinedUnit xe) {
msg = "error.undefined.unit.at";
params = new String[] { Integer.toString(xe.getPosition()) };
} catch (XUndefinedVariable xe) {
msg = "error.undefined.variable.at";
params = new String[] { Integer.toString(xe.getPosition()) };
solutionMsg = "solution.error.undefvariable";
} catch (XIllegalStatus xe) {
// illegal status in the condition interpreter, this must not happen!
throw new OLATRuntimeException(xe.getMessage(), xe);
} catch (XExpression xe) {
msg = "error.inexpression.at";
params = new String[] { Integer.toString(xe.getPosition()) };
solutionMsg = "solution.error.inexpression";
} catch (ArgumentParseException apex) {
// the function, units etc, are responsible to provide reasonable error
// messages (translation keys)
msg = apex.getWhatsWrong();
params = new String[] { apex.getFunctionName(), apex.getWrongArgs() };
solutionMsg = apex.getSolutionProposal();
} catch (ClassCastException ccex) {
// the function, units etc, are responsible to provide reasonable error
// messages (translation keys)
msg = "error.undefined.variable.at";
params = new String[]{};
} catch(ArithmeticException aex) {
msg = "error.divide.by.zero";
params = new String[]{};
} catch (Exception ex) {
// this must not happen!
throw new OLATRuntimeException(ex.getMessage(), ex);
}
return new ConditionErrorMessage(msg, solutionMsg, params);
}
/**
* Evaluates a condition.
*
* @param c
* @return True if evaluation successfull.
*/
public boolean evaluateCondition(Condition c) {
return evaluateCondition(c.getConditionExpression());
}
/**
* Evaluates a condition.
*
* @param condition
* @return True if evaluation successfull.
*/
public boolean evaluateCondition(String condition) {
boolean ok = false;
try {
// TODO: lookup in Map: key = c -> cached Expression
// if not null then: ok = evaluateCondition(Expression cachedExpression)
ok = doEvaluateCondition(condition);
} catch (ParseException e) {
log.error("ParseException in evaluateCondition:" + condition, e);
ok = false;
}
return ok;
}
/**
* Evaluates a calculation.
*
* @param calculation
* @return True if evaluation successfull.
*/
public float evaluateCalculation(String calculation) {
float res = -100000000000000000000000000000000f;
try {
res = doEvaluateCalculation(calculation);
} catch (ParseException e) {
log.info("ParseException in evaluateCalculation:" + e);
throw new AssertException("parse or execute error in calculation:" + calculation + " exception=" + e.getMessage());
}
return res;
}
private float doEvaluateCalculation(String calculation) throws ParseException {
try {
Expression exp = new Expression(calculation, env);
Object result = exp.evaluate();
if (result instanceof Double) {
return ((Double) result).floatValue();
} else if (result instanceof Integer) {
return ((Integer) result).floatValue();
} else throw new ArgumentParseException("Parse exception: expected Double or Integer, but got:"
+ (result == null ? "no object(null)" : result.getClass().getName()));
} catch (XExpression xe) {
throw new ParseException("Parse exception for calculation: " + calculation + ". " + xe.getMessage(), xe.getPosition());
}
}
public Collection<Object> getParsedTokens(String condition)
throws Exception {
Expression exp = new Expression(condition, env);
exp.evaluate();
Field tokenListField = Expression.class.getDeclaredField("oTokenList");
tokenListField.setAccessible(true);
@SuppressWarnings("unchecked")
Collection<Object> tokenList = (Collection<Object>)tokenListField.get(exp);
return tokenList;
}
/**
* Evaluate a condition using the jmep expression parser
*
* @param condition The condition as a java script text
* @return true if condition matches, false otherwhise
*/
private boolean doEvaluateCondition(String condition) throws ParseException {
try {
Expression exp = new Expression(condition, env);
Object result = exp.evaluate();
if (result instanceof Double) {
return (((Double) result).doubleValue() == 1.0) ? true : false;
} else if (result instanceof Integer) {
return (((Integer) result).intValue() == 1) ? true : false;
} else return false;
} catch (XExpression xe) {
throw new ParseException("Parse exception for condition: " + condition + ". " + xe.getMessage(), xe.getPosition());
}
}
}