package edu.vserver.exercises.math.essentials.layout; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import org.vaadin.jouni.animator.shared.AnimType; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Layout; import com.vaadin.ui.UI; import edu.vserver.misconception.MisconceptionData; import edu.vserver.misconception.MisconceptionPerformanceData; import edu.vserver.misconception.MisconceptionPerformanceSubject; import edu.vserver.misconception.MisconceptionTypeData; import edu.vserver.misconception.NullMisconceptionPerformanceData; import fi.utu.ville.exercises.helpers.ExerciseExecutionHelper; import fi.utu.ville.exercises.model.ExecutionSettings; import fi.utu.ville.exercises.model.ExecutionState; import fi.utu.ville.exercises.model.ExecutionStateChangeListener; import fi.utu.ville.exercises.model.Executor; import fi.utu.ville.exercises.model.ExerciseData; import fi.utu.ville.exercises.model.ExerciseException; import fi.utu.ville.exercises.model.ExerciseException.ErrorType; import fi.utu.ville.exercises.model.ResetListener; import fi.utu.ville.exercises.model.SubmissionListener; import fi.utu.ville.exercises.model.SubmissionType; import fi.utu.ville.standardutils.Localizer; import fi.utu.ville.standardutils.TempFilesManager; public abstract class AbstractMathExecutor<E extends ExerciseData, F extends MathSubInfo<P>, P extends Problem> implements Executor<E, F> { private static final long serialVersionUID = 2682119786422750060L; protected MathLayout<P> exLayout; private TimeStampHandler timeStampHandler; private MisconceptionPerformanceSubject misconceptionSubject = MisconceptionPerformanceSubject.NONE; private MisconceptionPerformanceData mpdInternal; protected Localizer localizer; private long lastProblemStartTime; private E data; // private Localizer main; private final ExerciseExecutionHelper<F> listenerHelper = new ExerciseExecutionHelper<>(); protected TimeStampHandler getTimeStampHandler() { return timeStampHandler; } protected abstract MathExerciseState<P> getMathState(); protected abstract MathExerciseView<P> getMathView(); protected abstract F newSubmissionInfo(); protected abstract void initStateAndView(E data, Localizer localizer); protected F parseSubmissionInfo() { ArrayList<P> problems = new ArrayList<>(); for (int i = 0; i < getMathState().getProblemCount(); i++) { problems.add(getMathState().getProblem(i)); } F subData = newSubmissionInfo(); subData.setTimeStamps(getTimeStampHandler()); subData.setUserProblemsAndAnswers(problems); return subData; } @Override public final void initialize(Localizer localizer, E data, F oldData, TempFilesManager tempMan, ExecutionSettings execSettings) throws ExerciseException { // MathExercise exer = MathPersistenceHandler.load(in); // You must rename these two to match your filenames. this.data = data; this.localizer = localizer; if (UI.getCurrent() != null) { UI.getCurrent().addStyleName(getBgStyleName()); } initStateAndView(data, localizer); MisconceptionPerformanceData mpd = getMathPerformanceData(); timeStampHandler = new TimeStampHandler(); timeStampHandler.add(TimeStampHandler.startExercise); exLayout = new MathLayout<>(localizer, getMathView(), getMathState(), timeStampHandler, execSettings); // exLayout.attachExecSettings(execSettings); ClickListener prevListener = e -> { timeStampHandler.add(TimeStampHandler.prevButton); exLayout.aPrev = exLayout.getProxy() .animate(exLayout.getAnimationWrapper(), AnimType.SIZE) .setDuration(500) .setDelay(50) .setData("x=+" + MathLayout.shiftSize); }; ClickListener checkListener = e -> { timeStampHandler.add(TimeStampHandler.checkButton); exLayout.aCheck = exLayout.getProxy() .animate(exLayout.getAnimationWrapper(), AnimType.FADE_OUT) .setDuration(250) .setDelay(50); }; ClickListener nextListener = e -> { timeStampHandler.add(TimeStampHandler.nextButton); exLayout.aNext = exLayout.getProxy() .animate(exLayout.getAnimationWrapper(), AnimType.SIZE) .setDuration(500) .setDelay(50) .setData("x=-" + MathLayout.shiftSize); }; // Adds executor to MathLayout so that askSubmit can be called // when last calculation has been checked. exLayout.attachExecutor(this); exLayout.attachControllers(prevListener, checkListener, nextListener); // ExecSettings are used to check if immediate feedback should be shown // or not try { getMathView().drawProblem(getMathState().nextProblem()); startMathPerformance(getMathState().getCurrentProblem()); } catch (NullPointerException | IndexOutOfBoundsException | NoSuchElementException e) { throw new ExerciseException( ErrorType.EXERCISE_LOAD_ERROR, "Failed to load " + getMathState().getClass().getName(), e); } mpd.setStartTime(); } private double checkCorrectness() { return getMathState().getCorrectness(); } @Override public final void registerSubmitListener( SubmissionListener<F> submitListener) { listenerHelper.registerSubmitListener(submitListener); } @Override public final void registerResetListener(ResetListener resetListener) { listenerHelper.registerResetListener(resetListener); } @Override public final Layout getView() { return exLayout; } @Override public final void shutdown() { UI.getCurrent().removeStyleName(getBgStyleName()); } protected String getBgStyleName() { return "math-default-bg"; } @Override public final void askReset() { MisconceptionPerformanceData mpd = getMathPerformanceData(); mpd.setSubmitted(false); // if you try this as a student first and then test it in teacher view, // mpd will get saved if (mpd.getAssigId() > 0) { mpd.save(); } getView().setEnabled(true); getMathState().reset(); exLayout.reset(); getMathView().clearFields(); getMathView().drawProblem(getMathState().nextProblem()); listenerHelper.informResetDefault(); if (timeStampHandler == null) { timeStampHandler = new TimeStampHandler(); } timeStampHandler.add(TimeStampHandler.resetExercise); } @Override public final void askSubmit(SubmissionType submType) { MisconceptionPerformanceData mpd = getMathPerformanceData(); mpd.setEndTime(); mpd.setSubmitted(true); // if you try this as a student first and then test it in teacher view, // mpd will get saved if (mpd.getAssigId() > 0) { mpd.save(); } if (timeStampHandler == null) { timeStampHandler = new TimeStampHandler(); } timeStampHandler.add(TimeStampHandler.submitExercise); listenerHelper.informSubmitDefault(checkCorrectness(), parseSubmissionInfo(), submType, null); setUnsubmittedChangesFlag(false); getView().setEnabled(false); } @Override public final void registerExecutionStateChangeListener( ExecutionStateChangeListener execStateListener) { listenerHelper .registerExerciseExecutionStateListener(execStateListener); } @Override public final ExecutionState getCurrentExecutionState() { return listenerHelper.getState(); } public void setUnsubmittedChangesFlag(boolean value) { if (value) { listenerHelper.getState().setUnSubmChangesFlag(); } else { listenerHelper.getState().clearUnSubmChangesFlag(); } } /** * Get the MathPerformanceData object, which is used to save misconception related information. * * @return MathPerformanceData object for handling misconception information */ private MisconceptionPerformanceData getMathPerformanceData() { mpdInternal = misconceptionSubject.getPerformanceData(); if (mpdInternal == null) { mpdInternal = new NullMisconceptionPerformanceData(); } return mpdInternal; } /** * * @return can misconception data be collected from this exercise. */ public boolean canCollectMisconceptionData() { return misconceptionSubject != MisconceptionPerformanceSubject.NONE; } /** Abstract method for concrete implementations to implement **/ /** * This method may be overridden if derived class wants to specify its own misconception performance-data. If this returns null, this exercise can not be * used to collect misconception performance data<br> * <br> * MisconceptionPerformanceData is a collection of MisconceptionData for a single assignment * * @return the performance data-object used to calculate misconceptions or null if no data collection is available. */ protected MisconceptionPerformanceData getMisconceptionPerformanceData() { return null; } /** * This method may be overridden if derived class wants to specify its own misconception-data.<br> * <br> * MisconceptionData contains data from a user for a single attempt at solving a problem * * @param problem * the problem to collect data from * @return The misconceptionData for that problem instance */ protected MisconceptionData getMisconceptionData(P problem) { return null; } /** * This method may be overridden if derived class wants to specify any MathMisconceptionTypeData. MisconceptionTypeData is the type of misconception * involved in this exercise. The possibilities are found in the Misconception-enum * * @param problem * the problem to collect data from * @return The different misconceptions collected for this problem instance */ protected List<MisconceptionTypeData> getMisconceptionTypeData( P problem) { return null; } protected void startMathPerformance(P problem) { lastProblemStartTime = System.currentTimeMillis(); } protected void finishMathPerformance(P problem) { MisconceptionPerformanceData mpd = getMathPerformanceData(); MisconceptionData mmd = getMisconceptionData(problem); if (mmd == null) { return; } List<MisconceptionTypeData> misconceptions = getMisconceptionTypeData(problem); if (misconceptions != null) { mmd.addMisconceptionTypes(misconceptions); } mmd.setTime(System.currentTimeMillis() - lastProblemStartTime); mpd.addMisconceptionData(mmd); mpd.save(); } @Override public final void setMisconceptionSubject( MisconceptionPerformanceSubject misconceptionSubject) { this.misconceptionSubject = misconceptionSubject; } @Override public final MisconceptionPerformanceSubject getMisconceptionSubject() { return misconceptionSubject; } }