package net.sf.egonet.persistence; import java.util.ArrayList; import java.util.List; import java.util.Map; import net.sf.egonet.model.Alter; import net.sf.egonet.model.Answer; import net.sf.egonet.model.Expression; import net.sf.egonet.model.Interview; import net.sf.egonet.model.Question; import net.sf.egonet.model.QuestionOption; import net.sf.egonet.model.Study; import net.sf.egonet.model.Answer.SkipReason; import net.sf.egonet.model.Expression.Operator; import net.sf.egonet.model.Question.QuestionType; import net.sf.functionalj.tuple.Pair; import net.sf.functionalj.tuple.PairUni; import net.sf.functionalj.tuple.Triple; import net.sf.functionalj.tuple.TripleUni; import org.hibernate.Session; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class Expressions { public static List<Expression> forStudy(final Long studyId) { return DB.withTx(new Function<Session,List<Expression>>() { public List<Expression> apply(Session session) { return forStudy(session,studyId); } }); } public static List<Expression> forStudy(Session session, Long studyId) { return session.createQuery("from Expression e where e.studyId = :studyId and e.active = 1" + " order by name") .setLong("studyId", studyId).list(); } public static Expression get(final Long expressionId) { return DB.withTx(new Function<Session,Expression>() { public Expression apply(Session session) { return get(session,expressionId); } }); } public static Expression get(Session session, Long expressionId) { return (Expression) session.createQuery("from Expression e where e.id = :id and e.active = 1") .setLong("id", expressionId).list().get(0); } public static List<Expression> getSimpleExpressionsForQuestion(Session session, Long questionId) { return session.createQuery("from Expression e where e.questionId = :questionId and e.active = 1") .setLong("questionId", questionId).list(); } public static void delete(final Expression expression) { DB.withTx(new Function<Session,Object>(){ public Object apply(Session session) { delete(session,expression); return null; } }); } public static void delete(Session session, Expression expression) { for(Expression otherExpression : forStudy(session,expression.getStudyId())) { if(otherExpression.getType().equals(Expression.Type.Compound)) { List<Long> expressionIds = (List<Long>) otherExpression.getValue(); expressionIds.remove(expression.getId()); otherExpression.setValue(expressionIds); DB.save(session,otherExpression); } else if(otherExpression.getType().equals(Expression.Type.Comparison)) { Pair<Integer,Long> numberExpr = (Pair<Integer,Long>) otherExpression.getValue(); if(numberExpr.getSecond().equals(expression.getId())) { delete(session,otherExpression); } } else if(otherExpression.getType().equals(Expression.Type.Counting)) { Triple<Integer,List<Long>,List<Long>> numberExprsQuests = (Triple<Integer,List<Long>,List<Long>>) otherExpression.getValue(); if(numberExprsQuests.getSecond().contains(expression.getId())) { List<Long> newExprs = Lists.newArrayList(); for(Long expr : numberExprsQuests.getSecond()) { if(! expr.equals(expression.getId())) { newExprs.add(expr); } } otherExpression.setValue(new Triple<Integer,List<Long>,List<Long>>( numberExprsQuests.getFirst(), newExprs, numberExprsQuests.getThird())); DB.save(session, otherExpression); } } } Long studyId = expression.getStudyId(); Study study = Studies.getStudy(session, studyId); if(study.getAdjacencyExpressionId() != null && study.getAdjacencyExpressionId().equals(expression.getId())) { study.setAdjacencyExpressionId(null); } for(Question question : Questions.getQuestionsForStudy(session, studyId, null)) { if(question.getAnswerReasonExpressionId() != null && question.getAnswerReasonExpressionId().equals(expression.getId())) { question.setAnswerReasonExpressionId(null); } } DB.delete(session,expression); } public static class EvaluationContext { public Map<Long,Question> qidToQuestion = Maps.newHashMap(); public Map<Long,Expression> eidToExpression = Maps.newHashMap(); public Map<Long,QuestionOption> idToOption = Maps.newHashMap(); public Map<Long,Answer> qidToEgoAnswer = Maps.newHashMap(); public Map<PairUni<Long>,Answer> qidAidToAlterAnswer = Maps.newHashMap(); public Map<TripleUni<Long>,Answer> qidA1idA2idToAlterPairAnswer = Maps.newHashMap(); public Map<Long,Answer> qidToNetworkAnswer = Maps.newHashMap(); } public static EvaluationContext getContext(final Interview interview) { return new DB.Action<EvaluationContext>() { public EvaluationContext get() { return getContext(session,interview); } }.execute(); } public static EvaluationContext getContext(Session session, Interview interview) { EvaluationContext context = new EvaluationContext(); Long studyId = interview.getStudyId(); Long interviewId = interview.getId(); for (Question question : Questions.getQuestionsForStudy(session, studyId, null)) { context.qidToQuestion.put(question.getId(), question); } for(Expression expression : Expressions.forStudy(session, studyId)) { context.eidToExpression.put(expression.getId(), expression); } for(QuestionOption option : Options.getOptionsForStudy(session, studyId)) { context.idToOption.put(option.getId(), option); } for(Answer answer : Answers.getAnswersForInterview(session, interviewId, Question.QuestionType.EGO)) { context.qidToEgoAnswer.put(answer.getQuestionId(), answer); } for(Answer answer : Answers.getAnswersForInterview(session, interviewId, Question.QuestionType.EGO_ID)) { context.qidToEgoAnswer.put(answer.getQuestionId(), answer); } for(Answer answer : Answers.getAnswersForInterview(session, interviewId, Question.QuestionType.ALTER)) { context.qidAidToAlterAnswer.put( new PairUni<Long>( answer.getQuestionId(), answer.getAlterId1()), answer); } for(Answer answer : Answers.getAnswersForInterview(session, interviewId, Question.QuestionType.ALTER_PAIR)) { context.qidA1idA2idToAlterPairAnswer.put( new TripleUni<Long>( answer.getQuestionId(), answer.getAlterId1(), answer.getAlterId2()), answer); context.qidA1idA2idToAlterPairAnswer.put( new TripleUni<Long>( answer.getQuestionId(), answer.getAlterId2(), answer.getAlterId1()), answer); } for(Answer answer : Answers.getAnswersForInterview(session, interviewId, Question.QuestionType.NETWORK)) { context.qidToNetworkAnswer.put(answer.getQuestionId(), answer); } return context; } public static Object evaluate(Expression expression, ArrayList<Alter> alters, EvaluationContext context) { return evaluate(expression,alters, context.qidToQuestion,context.eidToExpression,context.idToOption, context.qidToEgoAnswer,context.qidAidToAlterAnswer, context.qidA1idA2idToAlterPairAnswer,context.qidToNetworkAnswer); } public static Boolean evaluateAsBool(Expression expression, ArrayList<Alter> alters, EvaluationContext context) { return evaluateAsBool(expression,alters, context.qidToQuestion,context.eidToExpression,context.idToOption, context.qidToEgoAnswer,context.qidAidToAlterAnswer, context.qidA1idA2idToAlterPairAnswer,context.qidToNetworkAnswer); } public static Integer evaluateAsInt(Expression expression, ArrayList<Alter> alters, EvaluationContext context) { return evaluateAsInt(expression,alters, context.qidToQuestion,context.eidToExpression,context.idToOption, context.qidToEgoAnswer,context.qidAidToAlterAnswer, context.qidA1idA2idToAlterPairAnswer,context.qidToNetworkAnswer); } private static Boolean evaluateAsBool(Expression expression, ArrayList<Alter> alters, Map<Long,Question> qidToQuestion, Map<Long,Expression> eidToExpression, Map<Long,QuestionOption> idToOption, Map<Long,Answer> qidToEgoAnswer, Map<PairUni<Long>,Answer> qidAidToAlterAnswer, Map<TripleUni<Long>,Answer> qidA1idA2idToAlterPairAnswer, Map<Long,Answer> qidToNetworkAnswer) { Object result = evaluate(expression, alters, qidToQuestion, eidToExpression, idToOption, qidToEgoAnswer, qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer, qidToNetworkAnswer); if(result instanceof Boolean) { return (Boolean) result; } if(result instanceof Integer) { return ! result.equals(new Integer(0)); } throw new RuntimeException("Unable to coerce "+result.getClass()+" to Boolean: "+result); } private static Integer evaluateAsInt(Expression expression, ArrayList<Alter> alters, Map<Long,Question> qidToQuestion, Map<Long,Expression> eidToExpression, Map<Long,QuestionOption> idToOption, Map<Long,Answer> qidToEgoAnswer, Map<PairUni<Long>,Answer> qidAidToAlterAnswer, Map<TripleUni<Long>,Answer> qidA1idA2idToAlterPairAnswer, Map<Long,Answer> qidToNetworkAnswer) { Object result = evaluate(expression, alters, qidToQuestion, eidToExpression, idToOption, qidToEgoAnswer, qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer, qidToNetworkAnswer); if(result instanceof Boolean) { return ((Boolean) result) ? 1 : 0; } if(result instanceof Integer) { return (Integer) result; } throw new RuntimeException("Unable to coerce "+result.getClass()+" to Integer: "+result); } private static Object evaluate(Expression expression, ArrayList<Alter> alters, Map<Long,Question> qidToQuestion, Map<Long,Expression> eidToExpression, Map<Long,QuestionOption> idToOption, Map<Long,Answer> qidToEgoAnswer, Map<PairUni<Long>,Answer> qidAidToAlterAnswer, Map<TripleUni<Long>,Answer> qidA1idA2idToAlterPairAnswer, Map<Long,Answer> qidToNetworkAnswer) { if(expression.getType().equals(Expression.Type.Compound)) { return evaluateCompoundExpression(expression, alters, qidToQuestion, eidToExpression, idToOption, qidToEgoAnswer, qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer,qidToNetworkAnswer); } if(expression.getType().equals(Expression.Type.Counting)) { return evaluateCountingExpression(expression, alters, qidToQuestion, eidToExpression, idToOption, qidToEgoAnswer, qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer,qidToNetworkAnswer); } if(expression.getType().equals(Expression.Type.Comparison)) { return evaluateComparisonExpression(expression, alters, qidToQuestion, eidToExpression, idToOption, qidToEgoAnswer, qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer,qidToNetworkAnswer); } return evaluateSimpleExpression(expression, qidToQuestion.get(expression.getQuestionId()), alters, qidToEgoAnswer, qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer, qidToNetworkAnswer); } private static Boolean evaluateSimpleExpression(Expression expression, Question question, ArrayList<Alter> alters, Map<Long,Answer> qidToEgoAnswer, Map<PairUni<Long>,Answer> qidAidToAlterAnswer, Map<TripleUni<Long>,Answer> qidA1idA2idToAlterPairAnswer,Map<Long,Answer> qidToNetworkAnswer) { QuestionType qType = question.getType(); Answer answer = null; if(qType.equals(QuestionType.EGO) || qType.equals(QuestionType.EGO_ID)) { answer = qidToEgoAnswer.get(question.getId()); } if(qType.equals(QuestionType.ALTER)) { if(alters.isEmpty()) { return expression.getResultForUnanswered(); } else if(alters.size() > 1) { for(Alter alter : alters) { if( ! evaluateSimpleExpression(expression,question,Lists.newArrayList(alter), null,qidAidToAlterAnswer,null,null)) { return false; } return true; } } answer = qidAidToAlterAnswer.get(new PairUni<Long>(question.getId(),alters.get(0).getId())); } if(qType.equals(QuestionType.ALTER_PAIR)) { if(alters.size() < 2) { return expression.getResultForUnanswered(); } answer = qidA1idA2idToAlterPairAnswer.get( new TripleUni<Long>( question.getId(), alters.get(0).getId(), alters.get(1).getId())); } if(qType.equals(QuestionType.NETWORK)) { answer = qidToNetworkAnswer.get(question.getId()); } if(answer == null || answer.isSkipped()) { return expression.getResultForUnanswered(); } Expression.Type eType = expression.getType(); if(eType.equals(Expression.Type.Number)) { return evaluateNumericalExpression(expression, answer); } if(eType.equals(Expression.Type.Selection)) { return evaluateSelectionExpression(expression, answer); } if(eType.equals(Expression.Type.Text)) { return evaluateTextualExpression(expression, answer); } throw new RuntimeException( "evaluateSimpleExpression doesn't know how to evaluate expression of type "+eType); } private static Boolean evaluateComparisonExpression(Expression expression, ArrayList<Alter> alters, Map<Long,Question> qidToQuestion, Map<Long,Expression> eidToExpression, Map<Long,QuestionOption> idToOption, Map<Long,Answer> qidToEgoAnswer, Map<PairUni<Long>,Answer> qidAidToAlterAnswer, Map<TripleUni<Long>,Answer> qidA1idA2idToAlterPairAnswer,Map<Long,Answer> qidToNetworkAnswer) { if(! expression.getType().equals(Expression.Type.Comparison)) { throw new RuntimeException("using evaluateComparisonExpression for " + "expression of type "+expression.getType()); } Pair<Integer,Long> numberExpr = (Pair<Integer,Long>) expression.getValue(); Expression subExpression = eidToExpression.get(numberExpr.getSecond()); if(! subExpression.getType().equals(Expression.Type.Counting)) { throw new RuntimeException("Unable to evaluate counting expression " + expression.getName() + " because its subexpression " + subExpression.getName() + " has type "+expression.getType()); } Integer subResult = evaluateCountingExpression(subExpression,alters, qidToQuestion, eidToExpression, idToOption, qidToEgoAnswer, qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer, qidToNetworkAnswer); Integer number = numberExpr.getFirst(); Expression.Operator operator = expression.getOperator(); if(subResult > number) { return operator.equals(Expression.Operator.Greater) || operator.equals(Expression.Operator.GreaterOrEqual); } if(subResult < number) { return operator.equals(Expression.Operator.Less) || operator.equals(Expression.Operator.LessOrEqual); } return operator.equals(Expression.Operator.Equals) || operator.equals(Expression.Operator.GreaterOrEqual) || operator.equals(Expression.Operator.LessOrEqual); } private static Integer evaluateCountingExpression(Expression expression, ArrayList<Alter> alters, Map<Long,Question> qidToQuestion, Map<Long,Expression> eidToExpression, Map<Long,QuestionOption> idToOption, Map<Long,Answer> qidToEgoAnswer, Map<PairUni<Long>,Answer> qidAidToAlterAnswer, Map<TripleUni<Long>,Answer> qidA1idA2idToAlterPairAnswer,Map<Long,Answer> qidToNetworkAnswer) { if(! expression.getType().equals(Expression.Type.Counting)) { throw new RuntimeException("using evaluateCountingExpression for " + "expression of type "+expression.getType()); } Expression.Operator operator = expression.getOperator(); Triple<Integer,List<Long>,List<Long>> numberExprsQuests = (Triple<Integer,List<Long>,List<Long>>) expression.getValue(); Integer total = 0; for(Long expressionId : numberExprsQuests.getSecond()) { Expression subExpression = eidToExpression.get(expressionId); if(subExpression == null) { throw new RuntimeException("Unable to find expression#"+expressionId+ " referenced in expression#"+expression.getId()+": "+ expression.getName()+"("+expression.getType()+")"); } ArrayList<ArrayList<Alter>> alterGroups = Lists.newArrayList(); Boolean simple = subExpression.isSimpleExpression(); if(simple && qidToQuestion.get(subExpression.getQuestionId()).isAboutAlter()) { for(Alter alter : alters) { // If alter question, needs one alter at a time. alterGroups.add(Lists.newArrayList(alter)); } } else { alterGroups.add(alters); } for(ArrayList<Alter> alterGroup : alterGroups) { if(operator.equals(Operator.Count)) { if(evaluateAsBool(subExpression,alterGroup, qidToQuestion,eidToExpression,idToOption, qidToEgoAnswer,qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer, qidToNetworkAnswer)) { total++; } } else if(operator.equals(Operator.Sum)) { total += evaluateAsInt(subExpression,alterGroup, qidToQuestion,eidToExpression,idToOption, qidToEgoAnswer,qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer, qidToNetworkAnswer); } else { throw new RuntimeException( "Unrecognized operator for counting expression: "+operator); } } } for(Long questionId : numberExprsQuests.getThird()) { Question question = qidToQuestion.get(questionId); if(question == null) { throw new RuntimeException("Unable to find expression#"+questionId+ " referenced in expression#"+expression.getId()+": "+ expression.getName()+"("+expression.getType()+")"); } List<Answer> answers = Lists.newArrayList(); if(question.isAboutAlter()) { for(Alter alter : alters) { // If alter question, needs one alter at a time. answers.add(qidAidToAlterAnswer.get( new PairUni<Long>(questionId,alter.getId()))); } } else if(question.isAboutRelationship() && alters.size() > 1) { answers.add( qidA1idA2idToAlterPairAnswer.get( new TripleUni<Long>( questionId, alters.get(0).getId(), alters.get(1).getId()))); } else if(question.isAboutEgo()){ answers.add(qidToEgoAnswer.get(questionId)); } else if(question.isAboutNetwork()){ answers.add(qidToNetworkAnswer.get(questionId)); } for(Answer answer : answers) { if(answer != null && answer.getSkipReason().equals(SkipReason.NONE)) { List<Integer> results = Lists.newArrayList(); String answerString = answer.getValue(); // set result, depending on question type and answer if(question.getAnswerType().equals(Answer.AnswerType.MULTIPLE_SELECTION) || question.getAnswerType().equals(Answer.AnswerType.SELECTION)) { if(! answerString.isEmpty()) { for(String optionIdString : answerString.split(",")) { QuestionOption option = idToOption.get(Long.parseLong(optionIdString)); try { results.add(Integer.parseInt(option.getValue().trim())); } catch(Exception ex) { } } } } else if(question.getAnswerType().equals(Answer.AnswerType.NUMERICAL)) { results.add(Integer.parseInt(answerString)); } else if(question.getAnswerType().equals(Answer.AnswerType.TEXTUAL)) { results.add(answerString == null || answerString.isEmpty() ? 0 : 1); } else { throw new RuntimeException( "Unrecognized answer type: "+question.getAnswerType()); } // increment total, depending on count vs sum for(Integer result : results) { if(operator.equals(Operator.Count)) { if(! result.equals(new Integer(0))) { total++; } } else if(operator.equals(Operator.Sum)) { total += result; } else { throw new RuntimeException( "Unrecognized operator for counting expression: "+operator); } } } } } return total * numberExprsQuests.getFirst(); } private static Boolean evaluateCompoundExpression(Expression expression, ArrayList<Alter> alters, Map<Long,Question> qidToQuestion, Map<Long,Expression> eidToExpression, Map<Long,QuestionOption> idToOption, Map<Long,Answer> qidToEgoAnswer, Map<PairUni<Long>,Answer> qidAidToAlterAnswer, Map<TripleUni<Long>,Answer> qidA1idA2idToAlterPairAnswer,Map<Long,Answer> qidToNetworkAnswer) { Expression.Operator operator = expression.getOperator(); for(Long expressionId : (List<Long>) expression.getValue()) { Expression subExpression = eidToExpression.get(expressionId); if(subExpression == null) { throw new RuntimeException("Unable to find expression#"+expressionId+ " referenced in expression#"+expression.getId()+": "+ expression.getName()+"("+expression.getType()+")"); } ArrayList<ArrayList<Alter>> alterGroups = Lists.newArrayList(); Boolean simple = subExpression.isSimpleExpression(); if(simple && qidToQuestion.get(subExpression.getQuestionId()).isAboutAlter()) { for(Alter alter : alters) { // If alter question, needs one alter at a time. alterGroups.add(Lists.newArrayList(alter)); } } else { alterGroups.add(alters); } for(ArrayList<Alter> alterGroup : alterGroups) { Boolean result = evaluateAsBool(subExpression,alterGroup, qidToQuestion,eidToExpression,idToOption, qidToEgoAnswer,qidAidToAlterAnswer, qidA1idA2idToAlterPairAnswer, qidToNetworkAnswer); if(result) { if(operator.equals(Operator.Some)) { return true; } if(operator.equals(Operator.None)) { return false; } } else { if(operator.equals(Operator.All)) { return false; } } } } return operator.equals(Operator.All) || operator.equals(Operator.None); } private static Boolean evaluateSelectionExpression(Expression expression, Answer answer) { try { List<String> selectedStrings = Lists.newArrayList(answer.getValue().split(",")); Operator operator = expression.getOperator(); for(Long id : (List<Long>) expression.getValue()) { if(selectedStrings.contains(id.toString())) { if(operator.equals(Operator.Some)) { return true; } if(operator.equals(Operator.None)) { return false; } } else { if(operator.equals(Operator.All)) { return false; } } } return operator.equals(Operator.All) || operator.equals(Operator.None); } catch(Exception ex) { return expression.getResultForUnanswered(); } } private static Boolean evaluateTextualExpression(Expression expression, Answer answer) { try { String answerString = answer.getValue(); if(answerString == null) { answerString = ""; } String expressionString = (String) expression.getValue(); if(expressionString == null) { expressionString = ""; } Operator operator = expression.getOperator(); if(operator.equals(Expression.Operator.Equals)) { return answerString.equals((String) expression.getValue()); } if(operator.equals(Expression.Operator.Contains)) { return answerString.contains((String) expression.getValue()); } return false; } catch(Exception ex) { return expression.getResultForUnanswered(); } } private static Boolean evaluateNumericalExpression(Expression expression, Answer answer) { try { Long answerNumber = Long.parseLong(answer.getValue()); Long expressionNumber = ((Number) expression.getValue()).longValue(); Operator operator = expression.getOperator(); if(answerNumber > expressionNumber) { return operator.equals(Expression.Operator.Greater) || operator.equals(Expression.Operator.GreaterOrEqual); } if(answerNumber < expressionNumber) { return operator.equals(Expression.Operator.Less) || operator.equals(Expression.Operator.LessOrEqual); } return operator.equals(Expression.Operator.Equals) || operator.equals(Expression.Operator.GreaterOrEqual) || operator.equals(Expression.Operator.LessOrEqual); } catch(Exception ex) { return expression.getResultForUnanswered(); } } }