package org.egonet.io;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.egonet.exceptions.DuplicateQuestionException;
import org.egonet.exceptions.EgonetException;
import org.egonet.exceptions.MalformedQuestionException;
import org.egonet.model.Study;
import org.egonet.model.Shared.AlterNameModel;
import org.egonet.model.Shared.AlterSamplingModel;
import org.egonet.model.answer.*;
import org.egonet.model.question.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import electric.xml.Document;
import electric.xml.Element;
import electric.xml.Elements;
import electric.xml.ParseException;
public class StudyReader {
final private static Logger logger = LoggerFactory.getLogger(StudyReader.class);
private File studyFile;
public StudyReader(File studyFile)
{
this.studyFile = studyFile;
}
public Study getStudy() throws EgonetException {
try {
Study readStudy = readPackageStudy(new Document(studyFile));
readStudy.verifyStudy();
return readStudy;
} catch (ParseException ex) {
throw new EgonetException("Could not read study file", ex);
}
}
private Study readPackageStudy(Document document) throws MalformedQuestionException, DuplicateQuestionException, EgonetException
{
Study study = new Study();
Element root = document.getRoot();
try {
study.setStudyId(root.getAttributeValue("Id"));
}
catch (NumberFormatException nfe) {
throw new EgonetException("Unable to parse the unique ID for this study", nfe);
}
root = root.getElement("Study");
if(root == null) {
throw new EgonetException("Couldn't find a study element in this file, may not be a study file.");
}
if (root.getElement("name") != null) {
study.setStudyName(root.getTextString("name"));
}
//Retrocompatibility for old versions of Egonet. Some studies had
//alternumberfixed meaning limited mode, and numalters as the min/max alters.
//If alternumberfixed were false meant that there weren't max number of alters.
if(root.getElement("alternumberfixed") != null)
{
boolean limitedMode = root.getBoolean("alternumberfixed");
study.setUnlimitedMode(!limitedMode);
if(root.getElement("numalters") != null)
{
if(limitedMode)
{
study.setMaximumNumberOfAlters(root.getInt("numalters"));
}
study.setMinimumNumberOfAlters(root.getInt("numalters"));
}
}
// if either new XML alters element is missing, default back to numalters
if(root.getElement("minalters") == null || root.getElement("maxalters") == null) {
if (root.getElement("numalters") != null) {
int i = root.getInt("numalters");
study.setMinimumNumberOfAlters(i);
study.setMaximumNumberOfAlters(i);
}
}
if (root.getElement("altermodeunlimited") != null)
{
boolean b = root.getBoolean("altermodeunlimited");
study.setUnlimitedMode(b);
}
if (root.getElement("minalters") != null) {
int i = root.getInt("minalters");
study.setMinimumNumberOfAlters(i);
}
if (root.getElement("maxalters") != null) {
int i = root.getInt("maxalters");
study.setMaximumNumberOfAlters(i);
}
if(root.getElement("altersamplingmodel") != null) {
study.setAlterSamplingModel(AlterSamplingModel.values()[root.getInt("altersamplingmodel")]);
}
if(root.getElement("alternamemodel") != null) {
int mod = root.getInt("alternamemodel");
study.setAlterNameModel(AlterNameModel.values()[mod]);
//logger.info(mod + " : " + study.getAlterNameModel());
}
if(root.getElement("altersamplingparameter") != null) {
study.setAlterSamplingParameter(root.getInt("altersamplingparameter"));
}
if(root.getElement("allowskipquestions") != null) {
//System.out.println("FOUND ALLOW SKIPS");
study.setAllowSkipQuestions(root.getBoolean("allowskipquestions"));
}
Elements elements = root.getElements("questionorder");
while (elements.hasMoreElements()) {
Element element = elements.next();
String questionType = element.getAttribute("questiontype");
Class<? extends Question> qType = Question.asSubclass(questionType);
List<Long> questionOrder = study.getQuestionOrder(qType);
Elements ids = element.getElements("id");
while (ids.hasMoreElements())
{
questionOrder.add(new Long(ids.next().getLong()));
}
}
List<Question> questions = getQuestions();
for(Question q : questions)
study.addQuestion(q);
return study;
}
private static List<Question> getQuestions(Element root) throws MalformedQuestionException, DuplicateQuestionException, EgonetException
{
root = root.getElement("QuestionList");
Elements questions = root.getElements("Question");
List<Question> questionList = new ArrayList<Question>();
while (questions.hasMoreElements())
{
Question q = readQuestion(questions.next());
questionList.add(q);
}
return questionList;
}
public static List<Question> getQuestions(File questionFile) throws MalformedQuestionException, DuplicateQuestionException, EgonetException
{
try {
Document document = new Document(questionFile);
Element root = document.getRoot();
return getQuestions(root);
} catch (ParseException e) {
throw new EgonetException(e);
}
}
public List<Question> getQuestions() throws MalformedQuestionException, DuplicateQuestionException, EgonetException
{
Document document;
try {
document = new Document(studyFile);
} catch (ParseException e) {
throw new EgonetException(e);
}
Element root = document.getRoot();
return getQuestions(root);
}
public boolean isStudyInUse() throws EgonetException {
Document document;
try {
document = new Document(studyFile);
} catch (ParseException e) {
throw new EgonetException(e);
}
Element root = document.getRoot();
String inUse = root.getAttribute("InUse");
return ((inUse != null) && inUse.equals("Y"));
}
@Deprecated
private enum QuestionType {
STUDY_CONFIG(StudyQuestion.class.getCanonicalName()),
EGO(EgoQuestion.class.getCanonicalName()),
ALTER_PROMPT(AlterPromptQuestion.class.getCanonicalName()),
ALTER(AlterQuestion.class.getCanonicalName()),
ALTER_PAIR(AlterPairQuestion.class.getCanonicalName())
;
public final String className;
QuestionType(String className) {
this.className = className;
}
}
@Deprecated
private enum AnswerType {
CATEGORICAL(CategoricalAnswer.class.getCanonicalName()),
NUMERICAL(NumericalAnswer.class.getCanonicalName()),
TEXT(TextAnswer.class.getCanonicalName()),
INFORMATIONAL(InformationalAnswer.class.getCanonicalName()),
;
public final String className;
AnswerType(String className) {
this.className = className;
}
}
@SuppressWarnings({"deprecation"})
public static Question readQuestion(Element question) throws MalformedQuestionException
{
Question q;
String questionType = question.getString("QuestionType");
if(!questionType.toLowerCase().contains(".class".toLowerCase())) {
int intQuestiontype = -1;
try {
intQuestiontype = Integer.parseInt(questionType);
}
catch (Exception ex) {
throw new MalformedQuestionException("QuestionType did not contain canonical name, but I couldn't parse an integer");
}
String clazz = null;
QuestionType [] types = {
QuestionType.STUDY_CONFIG,
QuestionType.EGO,
QuestionType.ALTER_PROMPT,
QuestionType.ALTER,
QuestionType.ALTER_PAIR
};
for(QuestionType t : types) {
if(t.ordinal() == intQuestiontype) {
clazz = t.className;
}
}
if(clazz == null) {
throw new MalformedQuestionException("QuestionType did not contain canonical name or integer");
}
questionType = clazz;
}
q = Question.newInstance(questionType);
String answerType = question.getString("AnswerType");
if(!answerType.toLowerCase().contains(".class".toLowerCase())) {
int intAnswerType = -1;
try {
intAnswerType = Integer.parseInt(answerType);
}
catch (Exception ex) {
throw new MalformedQuestionException("Question's AnswerType did not contain canonical name, but I couldn't parse an integer");
}
String clazz = null;
AnswerType [] types = {
AnswerType.CATEGORICAL,
AnswerType.NUMERICAL,
AnswerType.TEXT,
AnswerType.INFORMATIONAL
};
for(AnswerType t : types) {
if(t.ordinal() == intAnswerType) {
clazz = t.className;
}
}
if(clazz == null) {
throw new MalformedQuestionException("Question's AnswerType did not contain canonical name or integer");
}
answerType = clazz;
}
q.answerType = Answer.asSubclass(answerType);
// force alter prompt to be a text answer?
if (q instanceof AlterPromptQuestion) {
q.answerType = TextAnswer.class;
}
if(question.getElement("QuestionTitle") == null) {
q.title = "";
} else if(question.getElement("QuestionText") == null) {
q.text = "";
}
q.title = question.getTextString("QuestionTitle");
q.title = (q.title == null) ? "" : q.title;
q.text = question.getTextString("QuestionText");
q.text = (q.text == null) ? "" : q.text;
q.citation = question.getTextString("Citation");
q.citation = (q.citation == null) ? "" : q.citation;
if(question.hasElement("FollowUpOnly")) {
boolean foo = question.getBoolean("FollowUpOnly");
q.followupOnly = foo;
}
q.UniqueId = new Long(question.getLong("Id"));
if (question.getAttribute("CentralityMarker") != null) {
boolean centrality = question.getAttribute("CentralityMarker").equals("true");
if (centrality && (!(q instanceof AlterPairQuestion))) {
//logger.info("ID:" + q.UniqueId + " title:"+ q.title);
throw (new MalformedQuestionException("Centrality marker on non-alter pair question"));
}
}
Element link = question.getElement("Link");
if (link != null) {
Answer answer = Answer.newInstance(q.answerType);
answer.setQuestionId(link.getLong("Id"));
q.link.setAnswer(answer);
q.link.getAnswer().setValue(link.getInt("value"));
/* Only support questions with single answers for link */
q.link.getAnswer().string = link.getTextString("string");
}
if (q.answerType.equals(CategoricalAnswer.class)) {
Element answerList = question.getElement("Answers");
if (answerList != null) {
Elements selections = answerList.getElements("AnswerText");
if (selections.size() == 0) {
throw (new MalformedQuestionException("zero selections, impossible for a categorical question!"));
}
/*
* temp vars for determining statable, a question must have at
* least one of each selection type to be statable
*/
boolean adjacent = false;
boolean nonadjacent = false;
q.setSelections(new ArrayList<Selection>(selections.size()));
while (selections.hasMoreElements()) {
Element selection = selections.next();
int index = Integer.parseInt(selection.getAttributeValue("index"));
Selection ptr = new Selection();
q.getSelections().set(index, ptr);
try {
ptr.setString(selection.getTextString());
ptr.setValue(Integer.parseInt(selection.getAttributeValue("value")));
ptr.setAdjacent(Boolean.valueOf(selection.getAttributeValue("adjacent")).booleanValue());
ptr.setIndex(index);
} catch (NumberFormatException ex) {
//logger.info("Throwing exception");
ptr.setValue(selections.size() - (index + 1));
ptr.setAdjacent(false);
}
if (ptr.isAdjacent())
adjacent = true;
else
nonadjacent = true;
}
/*
* a question must have at least one of each selection type to
* be statable
*/
q.setStatable(adjacent && nonadjacent);
if(!q.isStatable()) {
// THIS MAY BE OK IF IT ISN'T AN ALTER PAIR QUESTION
String str = "Study readQuestion found that there wasn't a valid adjacency map, marking alter pair question NON statable; q: " + q.getString();
logger.error(str);
//throw new RuntimeException(str);
}
else {
logger.info("Successfully determined that this study is statable");
}
/* Check to make sure all answers are contiguous */
for (int i = 0; i < selections.size(); i++) {
if (q.getSelections().get(i) == null) {
throw (new MalformedQuestionException("saved a null as a selection"));
}
}
}
}
return q;
}
}