package fi.utu.ville.exercises.stub;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Window.CloseEvent;
import com.vaadin.ui.Window.CloseListener;
import fi.utu.ville.exercises.model.ExerciseData;
import fi.utu.ville.exercises.model.ExerciseException;
import fi.utu.ville.exercises.model.ExerciseTypeDescriptor;
import fi.utu.ville.exercises.model.StatisticalSubmissionInfo;
import fi.utu.ville.exercises.model.StatisticsInfoColumn;
import fi.utu.ville.exercises.model.SubmissionInfo;
import fi.utu.ville.exercises.model.SubmissionStatisticsGiver;
import fi.utu.ville.exercises.model.SubmissionVisualizer;
import fi.utu.ville.standardutils.FormattingUtils;
import fi.utu.ville.standardutils.Localizer;
import fi.utu.ville.standardutils.StandardUIConstants;
import fi.utu.ville.standardutils.StandardUIFactory;
import fi.utu.ville.standardutils.TempFilesManager;
/**
* A helper wrapping {@link SubmissionStatisticsGiver}-instance and giving certain general statistics-info in addition to exercise-type specific
* statistic-infos.
*
* @author Riku Haavisto
*
* @param <E>
* {@link ExerciseData} to use
* @param <S>
* {@link SubmissionInfo} to use
*/
public class SubmDataStatHelper<E extends ExerciseData, S extends SubmissionInfo>
implements Serializable {
/**
* Constructs and returns a new {@link SubmDataStatHelper}. Wraps the other constructor method so that explicit type-parameters are not needed.
*
* @param localizer
* {@link Localizer} for localizing the UI
* @param tempMan
* {@link TempFilesManager} for managing temporary files
* @param exerName
* name (or id) of the exercise-instance to load
* @param typeDesc
* {@link ExerciseTypeDescriptor} for the exercise-type to load
* @return newly constructed {@link SubmDataStatHelper}
* @throws ExerciseException
* if something goes wrong when loading {@link SubmissionStatisticsGiver}
*/
public static SubmDataStatHelper<? extends ExerciseData, ? extends SubmissionInfo> loadFor(
Localizer localizer, TempFilesManager tempMan, String exerName,
ExerciseTypeDescriptor<?, ?> typeDesc) throws ExerciseException {
return loadWithDesc(localizer, tempMan, exerName, typeDesc);
}
/**
* Constructs and returns a new {@link SubmDataStatHelper}.
*
* @param localizer
* {@link Localizer} for localizing the UI
* @param tempMan
* {@link TempFilesManager} for managing temporary files
* @param exerName
* name (or id) of the exercise-instance to load
* @param typeDesc
* {@link ExerciseTypeDescriptor} for the exercise-type to load
* @return newly constructed {@link SubmDataStatHelper}
* @throws ExerciseException
* if something goes wrong when loading {@link SubmissionStatisticsGiver}
*/
public static <F extends ExerciseData, R extends SubmissionInfo> SubmDataStatHelper<F, R> loadWithDesc(
Localizer localizer, TempFilesManager tempMan, String exerName,
ExerciseTypeDescriptor<F, R> desc) throws ExerciseException {
List<StatisticalSubmissionInfo<R>> statInfos = StubDataFilesHandler
.loadAllSubmissions(desc, exerName, desc.getSubDataClass(),
tempMan);
F e = StubDataFilesHandler.loadExerTypeData(desc.getTypeDataClass(),
desc.newExerciseXML(), desc, exerName, tempMan);
SubmissionStatisticsGiver<F, R> toUse = desc.newStatisticsGiver();
toUse.initialize(e, statInfos, localizer, tempMan);
return new SubmDataStatHelper<F, R>(localizer, statInfos, toUse, desc,
tempMan, exerName);
}
/**
* An enum-collection of different statistic-column types and their properties. Also contains place-holders for column-types that require special treatment.
*
* @author Riku Haavisto
*
*/
public enum StatColumnType {
NAME(StandardUIConstants.NAME, StubUiConstants.STATS_COL_DESC_NAME, String.class, new StatRowParser() {
@Override
public Object parse(StatisticalSubmissionInfo<?> parseFrom) {
return "Name-place-holder";
}
}, true),
SCORE(StandardUIConstants.SCORE, StandardUIConstants.STATS_COL_DESC_SCORE, Double.class, new StatRowParser() {
@Override
public Object parse(StatisticalSubmissionInfo<?> parseFrom) {
return parseFrom.getEvalution();
}
}, true),
DONE_TIME(StandardUIConstants.DATE, StandardUIConstants.STATS_COL_DESC_DONE_TIME, Date.class, new StatRowParser() {
@Override
public Object parse(StatisticalSubmissionInfo<?> parseFrom) {
return new Date(parseFrom.getDoneTime());
}
}, true),
TIME_ON_TASK(StandardUIConstants.TIME_USED, StandardUIConstants.STATS_COL_DESC_TIME_ON_TASK, Integer.class, new StatRowParser() {
@Override
public Object parse(StatisticalSubmissionInfo<?> parseFrom) {
return parseFrom.getTimeOnTask();
}
}, true),
// PLACE_HOLDER for all the type-specific columns!
TYPE_SPEC(null, null, null, null, false),
// This is also easier to implement as a special case...
VISUALIZE_BTN(null, null, null, null, false);
private final String uiConst;
private final String descUiConst;
private final Class<?> colType;
private final StatRowParser dataParser;
private final boolean exportable;
private StatColumnType(String uiConst, String descUiConst,
Class<?> colType, StatRowParser dataParser, boolean exportable) {
this.uiConst = uiConst;
this.descUiConst = descUiConst;
this.colType = colType;
this.dataParser = dataParser;
this.exportable = exportable;
}
}
/**
* Implementors of this interface know how to parse an object-value for certain column from certain base data-row ( {@link StatisticalSubmissionInfo}).
*
* @author Riku Haavisto
*/
private interface StatRowParser {
Object parse(StatisticalSubmissionInfo<?> parseFrom);
}
private static final long serialVersionUID = -4073802188668202409L;
private final List<StatisticalSubmissionInfo<S>> answers;
private final ExerciseTypeDescriptor<E, S> exerDesc;
private final SubmissionStatisticsGiver<E, S> currStatsGiver;
private final Localizer localizer;
private final TempFilesManager tempMan;
private final HashMap<StatColumnType, StatisticsInfoColumn<?>> colsByType
= new HashMap<StatColumnType, StatisticsInfoColumn<?>>();
private List<StatisticsInfoColumn<?>> specColumns = null;
private final String exerName;
/**
* Constructs a new {@link SubmDataStatHelper}
*
* @param localizer
* {@link Localizer}
* @param subms
* list of {@link StatisticalSubmissionInfo}-objects (ie. data collected from submissions made to an exercise-instance)
* @param statsGiver
* exercise-type specific {@link SubmissionStatisticsGiver} implementor
* @param exerDesc
* {@link ExerciseTypeDescriptor} for currently used exercise-type
* @param tempMan
* {@link TempFilesManager}
* @param exerName
* name (or id) of the exercise-instance to load
*/
public SubmDataStatHelper(Localizer localizer,
List<StatisticalSubmissionInfo<S>> subms,
SubmissionStatisticsGiver<E, S> statsGiver,
ExerciseTypeDescriptor<E, S> exerDesc, TempFilesManager tempMan,
String exerName) {
this.localizer = localizer;
this.exerDesc = exerDesc;
this.exerName = exerName;
this.tempMan = tempMan;
answers = new ArrayList<StatisticalSubmissionInfo<S>>(subms);
this.currStatsGiver = statsGiver;
}
/**
* @return all generated {@link StatisticsInfoColumn}s
*/
public List<StatisticsInfoColumn<?>> getAllStatCols() {
return getStatColumns(Arrays.asList(StatColumnType.values()));
}
/**
* Generates {@link StatisticsInfoColumn}s from all specified {@link StatColumnType}s and returns the generated {@link StatisticsInfoColumn}s.
*
* @param includedCols
* {@link StatColumnType}s to include
* @return list of generated {@link StatisticsInfoColumn}s
*/
public List<StatisticsInfoColumn<?>> getStatColumns(
List<StatColumnType> includedCols) {
List<StatisticsInfoColumn<?>> res = new ArrayList<StatisticsInfoColumn<?>>();
for (StatColumnType colType : includedCols) {
if (colType == StatColumnType.TYPE_SPEC) {
if (specColumns == null) {
loadStatInfoColumn(colType);
}
res.addAll(specColumns);
} else {
StatisticsInfoColumn<?> col = colsByType.get(colType);
if (col == null) {
loadStatInfoColumn(colType);
col = colsByType.get(colType);
}
res.add(col);
}
}
return res;
}
/**
* Loads {@link StatisticsInfoColumn} matching given {@link StatColumnType} and current data in the {@link SubmDataStatHelper} if the
* {@link StatisticsInfoColumn} is not already loaded.
*
* @param colType
* {@link StatColumnType} for which to load the data
*/
private void loadStatInfoColumn(StatColumnType colType) {
if (colType == StatColumnType.TYPE_SPEC) {
specColumns = currStatsGiver.getAsTabularData();
} else if (colType == StatColumnType.VISUALIZE_BTN) {
List<Button> res = new ArrayList<Button>();
for (int i = 0; i < answers.size(); i++) {
final int ind = i;
Button btn = StandardUIFactory.getButton(
localizer.getUIText(StandardUIConstants.VISUALIZE),
null);
btn.addClickListener(new Button.ClickListener() {
/**
*
*/
private static final long serialVersionUID = -3307319962895003537L;
@Override
public void buttonClick(ClickEvent event) {
viewUserSubmission(answers.get(ind));
}
});
res.add(btn);
}
colsByType
.put(colType,
new StatisticsInfoColumn<Button>(
localizer
.getUIText(StandardUIConstants.VISUALIZE),
localizer
.getUIText(StandardUIConstants.STATS_COL_DESC_VISUALIZE),
Button.class, res, false));
} else {
StatisticsInfoColumn<?> statCol = getStatInfoColumn(
localizer.getUIText(colType.uiConst),
localizer.getUIText(colType.descUiConst), colType.colType,
colType.dataParser, answers, colType.exportable);
colsByType.put(colType, statCol);
}
}
/**
* Parses a {@link StatisticsInfoColumn} from given arguments.
*
* @param title
* title for the {@link StatisticsInfoColumn}
* @param desc
* description for the {@link StatisticsInfoColumn}
* @param type
* type of data values of the {@link StatisticsInfoColumn}
* @param parser
* {@link StatRowParser} for parsing data-values for the column
* @param stats
* list of {@link StatisticalSubmissionInfo}-objects from which to parse data
* @param exportable
* whether resulting {@link StatisticsInfoColumn} is exportable
* @return parsed {@link StatisticsInfoColumn}
*/
private static <X, S extends SubmissionInfo> StatisticsInfoColumn<X> getStatInfoColumn(
String title, String desc, Class<X> type, StatRowParser parser,
List<StatisticalSubmissionInfo<S>> stats, boolean exportable) {
List<X> res = new ArrayList<X>();
for (int i = 0; i < stats.size(); i++) {
res.add(type.cast(parser.parse(stats.get(i))));
}
return new StatisticsInfoColumn<X>(title, desc, type, res, exportable);
}
/**
* Loads a {@link SubmissionVisualizer} with given {@link StatisticalSubmissionInfo} and {@link TempFilesManager} and with parameters from current state of
* the {@link SubmDataStatHelper}.
*
* @param statInfo
* {@link StatisticalSubmissionInfo} to load
* @param tempManToUse
* {@link TempFilesManager} to use
* @return initialized {@link SubmissionVisualizer}
* @throws ExerciseException
* if something goes wrong when initializing the submission-viewer
*/
private SubmissionVisualizer<?, ?> getSubmissionViewerFor(
StatisticalSubmissionInfo<?> statInfo, TempFilesManager tempManToUse)
throws ExerciseException {
SubmissionVisualizer<E, S> res = exerDesc.newSubmissionVisualizer(exerName,localizer);
SubmissionInfo sInfo = statInfo.getSubmissionData();
// this will throw exception if SInfo is of wrong class;
// however that should not happen as long as the
// rowToVis is fetched from 'this'-SubmDataStatCollection
S castedSInfo = exerDesc.getSubDataClass().cast(sInfo);
E exerData = StubDataFilesHandler.loadExerTypeData(
exerDesc.getTypeDataClass(), exerDesc.newExerciseXML(),
exerDesc, exerName, tempManToUse);
res.initialize(exerData, castedSInfo, localizer, tempManToUse);
return res;
}
/**
* Loads and opens to a pop-up a {@link SubmissionVisualizer} for given {@link StatisticalSubmissionInfo}-object and currently used exercise-instance and
* -type.
*
* @param {@link
* StatisticalSubmissionInfo} for which to load the {@link SubmissionVisualizer}
*/
public void viewUserSubmission(StatisticalSubmissionInfo<?> statInfo) {
final Window dialogWindow = new Window(localizer.getUIText(
StubUiConstants.ANSWERS_FOR_USER, "NAME_PLACE_HOLDER"));
VerticalLayout dialL = new VerticalLayout();
dialL.setMargin(true);
dialL.setSpacing(true);
dialogWindow.setContent(dialL);
dialogWindow.setModal(true);
dialogWindow.setWidth("1100px");
dialogWindow.setHeight("95%");
dialL.addComponent(new Label(localizer
.getUIText(StandardUIConstants.TIME_USED)
+ ": "
+ FormattingUtils.formatSeconds(statInfo.getTimeOnTask())));
dialL.addComponent(new Label(localizer
.getUIText(StandardUIConstants.SCORE)
+ ": "
+ statInfo.getEvalution()));
tempMan.initialize();
final TempFilesManager tempManForVis = tempMan.createChild();
try {
SubmissionVisualizer<?, ?> vis = getSubmissionViewerFor(statInfo,
tempManForVis);
dialL.addComponent(vis.getView());
} catch (ExerciseException e) {
e.printStackTrace();
tempManForVis.shutdown();
}
dialogWindow.addCloseListener(new CloseListener() {
/**
*
*/
private static final long serialVersionUID = 8437686439637510732L;
@Override
public void windowClose(CloseEvent e) {
// not absolutely necessary but a good idea
tempManForVis.shutdown();
}
});
Button closeButton = new Button(
localizer.getUIText(StandardUIConstants.CLOSE));
closeButton.addClickListener(new Button.ClickListener() {
/**
*
*/
private static final long serialVersionUID = 7706112506186975914L;
@Override
public void buttonClick(ClickEvent event) {
UI.getCurrent().removeWindow(dialogWindow);
}
});
dialL.addComponent(closeButton);
UI.getCurrent().addWindow(dialogWindow);
}
/**
* @return the statistics-view from the current {@link SubmissionStatisticsGiver}
*/
public Component getSpecificStatsView() {
return currStatsGiver.getView();
}
}