package de.dhbw.humbuch.view.components; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.ui.CheckBox; import com.vaadin.ui.CustomComponent; import com.vaadin.ui.TreeTable; import de.dhbw.humbuch.model.entity.BorrowedMaterial; import de.dhbw.humbuch.model.entity.Grade; import de.dhbw.humbuch.model.entity.Student; import de.dhbw.humbuch.model.entity.TeachingMaterial; /** * This class represents the StudentMaterialSelector. It is displayed as a tree * table with grades on the top level and the teaching materials as leafes of * the tree. * * @author Henning Muszynski * */ public class StudentMaterialSelector extends CustomComponent { private static final long serialVersionUID = -618911643102742679L; private static final int MAX_STUDENTS_BEFORE_COLLAPSE = 12; private static final String TREE_TABLE_HEADER_DATA = "Daten auswählen"; private static final String TREE_TABLE_HEADER_DATE = "Ausgeliehen bis"; private static final String GRADE = "Klasse "; private static final String EVERYTHING_SELECT = "Alle Klassen auswählen"; private static final String EVERYTHING_DESELECT = "Auswahl aufheben"; private TreeTable treeTableContent; private Map<Grade, Map<Student, List<BorrowedMaterial>>> gradeAndStudentsWithMaterials; private ArrayList<StudentMaterialSelectorObserver> registeredObservers; private CheckBox checkBoxEverything; private Object checkBoxEverythingId; private LinkedHashMap<CheckBox, Object> allGradeCheckBoxes; private LinkedHashMap<CheckBox, Object> allStudentCheckBoxes; private LinkedHashMap<CheckBox, Object> allMaterialCheckBoxes; private String filterString; private boolean collapseGrades; private boolean changedCollapseFlag; private ValueChangeListener gradeListener; private ValueChangeListener studentListener; private ValueChangeListener materialListener; /** * Default constructor. Instantiiates a new StudentMaterialSelector * instance. * */ public StudentMaterialSelector() { init(); buildLayout(); } /* * Initialize and configure member variables */ private void init() { treeTableContent = new TreeTable(); gradeAndStudentsWithMaterials = new LinkedHashMap<Grade, Map<Student, List<BorrowedMaterial>>>(); checkBoxEverything = new CheckBox(EVERYTHING_SELECT); allGradeCheckBoxes = new LinkedHashMap<CheckBox, Object>(); allStudentCheckBoxes = new LinkedHashMap<CheckBox, Object>(); allMaterialCheckBoxes = new LinkedHashMap<CheckBox, Object>(); collapseGrades = false; changedCollapseFlag = false; treeTableContent.setSizeFull(); treeTableContent.setWidth("100%"); treeTableContent.setImmediate(true); treeTableContent.addContainerProperty(TREE_TABLE_HEADER_DATA, CheckBox.class, null); treeTableContent.addContainerProperty(TREE_TABLE_HEADER_DATE, String.class, ""); implementListeners(); } private void buildLayout() { setCompositionRoot(treeTableContent); } /* * Implement the code for the different listeners on the CheckBox components * displayed in the TreeTable Whenever a CheckBox changes its value (gets * selected / deselected) all registered observers get notified. */ private void implementListeners() { /* * The everything listener is above the top level elements to select / * deselect all elements at once */ checkBoxEverything.addValueChangeListener(new ValueChangeListener() { private static final long serialVersionUID = -7395293342050339912L; @Override public void valueChange(ValueChangeEvent event) { boolean state = checkBoxEverything.getValue(); if (state == true) { checkBoxEverything.setCaption(EVERYTHING_DESELECT); } else { checkBoxEverything.setCaption(EVERYTHING_SELECT); } for (CheckBox checkBoxGrade : allGradeCheckBoxes.keySet()) { checkBoxGrade.setValue(state); } notifyObserver(); } }); /* * The grade listener is added to all top level elements (grades) in the * tree. Whenever a grade is selected / deselected all Students * belonging to that grade are selected / deselected, too. */ gradeListener = new ValueChangeListener() { private static final long serialVersionUID = -7395293342050339912L; @Override public void valueChange(ValueChangeEvent event) { CheckBox checkBoxGrade = (CheckBox) event.getProperty(); Grade grade = (Grade) checkBoxGrade.getData(); boolean gradeSelected = checkBoxGrade.getValue(); for (CheckBox checkBoxStudent : allStudentCheckBoxes.keySet()) { Student student = (Student) checkBoxStudent.getData(); if (student.getGrade().equals(grade)) { checkBoxStudent.setValue(gradeSelected); } } notifyObserver(); } }; /* * The student listener is added to all student elements in the tree. * When a student is selected / deselected all of its materials are * selected / deselected, too. */ studentListener = new ValueChangeListener() { private static final long serialVersionUID = 5610970965368269295L; @Override public void valueChange(ValueChangeEvent event) { CheckBox checkBoxStudent = (CheckBox) event.getProperty(); Student student = (Student) checkBoxStudent.getData(); boolean studentSelected = checkBoxStudent.getValue(); for (CheckBox checkBoxMaterial : allMaterialCheckBoxes.keySet()) { BorrowedMaterial material = (BorrowedMaterial) checkBoxMaterial .getData(); if (material.getStudent().equals(student)) { checkBoxMaterial.setValue(studentSelected); } } notifyObserver(); } }; /* * The material listener is added to all leafes (materials) in the tree. * It only notifies all registered observers about the value change. */ materialListener = new ValueChangeListener() { private static final long serialVersionUID = 614006288313075687L; @Override public void valueChange(ValueChangeEvent event) { notifyObserver(); } }; } /** * Call this method when the data which you want to display within the * StudentMaterialSelector changes. This methods not only saves the new data * in a member variable but also updates the TreeTable which is responsible * for showing the data. * * @param newGradeAndStudentsWithMaterials * the new data to be displayed by the StudentMaterialSelector * */ public void setGradesAndStudentsWithMaterials( Map<Grade, Map<Student, List<BorrowedMaterial>>> newGradeAndStudentsWithMaterials) { this.gradeAndStudentsWithMaterials = newGradeAndStudentsWithMaterials; rebuildTable(newGradeAndStudentsWithMaterials); } /** * Returns all currently selected grades of the StudentMaterialSelector or * an empty HashSet. Will never return <code>null</code>. * * @return the currently selected grades of the StudentMaterialSelector or * an empty HashSet if none are selected * */ public HashSet<Grade> getCurrentlySelectedGrades() { HashSet<Grade> currentlySelectedGrades = new HashSet<Grade>(); for (CheckBox checkBox : allGradeCheckBoxes.keySet()) { if (checkBox.getValue() == true) { currentlySelectedGrades.add((Grade) checkBox.getData()); } } return currentlySelectedGrades; } /** * Returns all currently selected students of the StudentMaterialSelector or * an empty HashSet. Will never return <code>null</code>. * * @return the currently selected students of the StudentMaterialSelector or * an empty HashSet if none are selected * */ public HashSet<Student> getCurrentlySelectedStudents() { HashSet<Student> currentlySelectedStudents = new HashSet<Student>(); for (CheckBox checkBox : allStudentCheckBoxes.keySet()) { if (checkBox.getValue() == true) { currentlySelectedStudents.add((Student) checkBox.getData()); } } return currentlySelectedStudents; } /** * Returns all currently selected borrowed materials of the * StudentMaterialSelector or an empty HashSet. Will never return * <code>null</code>. * * @return the currently selected materials of the StudentMaterialSelector * or an empty HashSet if none are selected * */ public HashSet<BorrowedMaterial> getCurrentlySelectedBorrowedMaterials() { HashSet<BorrowedMaterial> currentlySelectedBorrowedMaterials = new HashSet<BorrowedMaterial>(); for (CheckBox checkBox : allMaterialCheckBoxes.keySet()) { if (checkBox.getValue() == true) { currentlySelectedBorrowedMaterials .add((BorrowedMaterial) checkBox.getData()); } } return currentlySelectedBorrowedMaterials; } /** * Register any class implementing the StudentMaterialSelectorObserver * interface to get notified when any selection changes. * * @param observer * the class implementing the interface which should be notified * of all changes * */ public void registerAsObserver(StudentMaterialSelectorObserver observer) { if (registeredObservers == null) { registeredObservers = new ArrayList<StudentMaterialSelectorObserver>(); } else if (registeredObservers.contains(observer)) { return; } registeredObservers.add(observer); } /** * Call this method to filter for specific students. The filtering is * case-insensitve and filters for the firstname and lastname of any student * in the StudentMaterialSelector. It does not only saves the filter string * in a member variable but updates the TreeTable to only show students * matching the filter. When passing null it is treated as an empty string. * * @param filterString * the string on which filtering should be based upon * */ public void setFilterString(String filterString) { if (filterString == null) { filterString = ""; } this.filterString = filterString; filterTableContent(); } /* * Method used for filtering. It filters the currently used data structure * inside the StudentMaterialSelector and filters every students first and * lastname (ignoring case). After filtering is done this method rebuilds * the TreeTable to visualize the result of the filter process. */ private void filterTableContent() { LinkedHashMap<Grade, Map<Student, List<BorrowedMaterial>>> gradeAndStudentsWithMaterialsFiltered = new LinkedHashMap<Grade, Map<Student, List<BorrowedMaterial>>>(); for (Grade grade : gradeAndStudentsWithMaterials.keySet()) { Map<Student, List<BorrowedMaterial>> entry = gradeAndStudentsWithMaterials .get(grade); Map<Student, List<BorrowedMaterial>> filteredEntries = new LinkedHashMap<Student, List<BorrowedMaterial>>(); for (Student student : entry.keySet()) { // match firstname and lastname ignoring case String fullName = student.getFirstname() + " " + student.getLastname(); fullName = fullName.toLowerCase(); if (fullName.contains(filterString.toLowerCase())) { filteredEntries.put(student, entry.get(student)); } } if (filteredEntries.size() != 0) { gradeAndStudentsWithMaterialsFiltered.put(grade, filteredEntries); } } rebuildTable(gradeAndStudentsWithMaterialsFiltered); } /* * Helper method used for rebuilding the TreeTable. It removes all items * currently showing and resets the member variables holding information * about the current state. It adds all grades, students and materials * within the provided structure to the newly build TreeTable. This method * resets the expand states of the components in the TreeTable. * * @param newGradeAndStudentsWithMaterials the new data structure which * should be visualized by the StudentMaterialSelector */ private void rebuildTable( Map<Grade, Map<Student, List<BorrowedMaterial>>> newGradeAndStudentWithMaterials) { treeTableContent.removeAllItems(); allGradeCheckBoxes.clear(); allStudentCheckBoxes.clear(); allMaterialCheckBoxes.clear(); collapseGrades = false; changedCollapseFlag = false; for (Grade grade : newGradeAndStudentWithMaterials.keySet()) { addGradeToTree(grade); Map<Student, List<BorrowedMaterial>> entry = newGradeAndStudentWithMaterials .get(grade); for (Student student : entry.keySet()) { addStudentToTree(student); for (BorrowedMaterial material : entry.get(student)) { addMaterialToTree(material); } } } validateTableContent(); } /* * Helper method used to update or rebuild or the TreeTable. This method * takes care of adding the grade to the tree and storing all information in * the corresponding member variables. * * @return the item id of the newly added grade or null if there has been an * error */ private Object addGradeToTree(Grade grade) { if (grade == null) { return null; } if (checkBoxEverythingId == null || !treeTableContent.containsId(checkBoxEverythingId)) { checkBoxEverythingId = treeTableContent.addItem(new Object[] { checkBoxEverything, "" }, null); treeTableContent.setCollapsed(checkBoxEverythingId, false); } CheckBox checkBoxGrade = new CheckBox(GRADE + grade.getGrade() + grade.getSuffix()); checkBoxGrade.setData(grade); checkBoxGrade.addValueChangeListener(gradeListener); Object gradeItemId = treeTableContent.addItem(new Object[] { checkBoxGrade, "" }, null); treeTableContent.setCollapsed(gradeItemId, collapseGrades); treeTableContent.setParent(gradeItemId, checkBoxEverythingId); allGradeCheckBoxes.put(checkBoxGrade, gradeItemId); return gradeItemId; } /* * @see StudentMaterialSelector.addGradeToTree(Grade grade) This method * takes care of putting the student under right grade to maintain a valid * hierarchy. * * @return the item id of the newly added student or null if there has been * an error */ private Object addStudentToTree(Student student) { if (student == null) { return null; } CheckBox checkBoxStudent = new CheckBox(student.getFirstname() + " " + student.getLastname()); checkBoxStudent.setData(student); checkBoxStudent.addValueChangeListener(studentListener); Object studentItemId = treeTableContent.addItem(new Object[] { checkBoxStudent, "" }, null); Object parentGradeItemId = null; for (CheckBox checkBoxGrade : allGradeCheckBoxes.keySet()) { Grade grade = (Grade) checkBoxGrade.getData(); if (grade.equals(student.getGrade())) { parentGradeItemId = allGradeCheckBoxes.get(checkBoxGrade); break; } } if (parentGradeItemId == null) { parentGradeItemId = addGradeToTree(student.getGrade()); } treeTableContent.setParent(studentItemId, parentGradeItemId); allStudentCheckBoxes.put(checkBoxStudent, studentItemId); if (allStudentCheckBoxes.size() > MAX_STUDENTS_BEFORE_COLLAPSE && changedCollapseFlag == false) { collapseGrades = true; changedCollapseFlag = true; for (CheckBox checkBoxGrade : allGradeCheckBoxes.keySet()) { treeTableContent.setCollapsed( allGradeCheckBoxes.get(checkBoxGrade), collapseGrades); } } return studentItemId; } /* * @see StudentMaterialSelector.addGradeToTree(Grade grade) This method * takes care of putting the material under right student to maintain a * valid hierarchy. * * @return the item id for the newly added material or null if there has * been an error */ private Object addMaterialToTree(BorrowedMaterial material) { if (material == null) { return null; } CheckBox checkBoxMaterial = new CheckBox(material.getTeachingMaterial() .getName()); checkBoxMaterial.setData(material); checkBoxMaterial.addValueChangeListener(materialListener); Date dateBorrowedUntil = material.getBorrowUntil(); String borrowedUntil = ""; if (dateBorrowedUntil != null) { borrowedUntil = new SimpleDateFormat("dd.MM.YYYY") .format(dateBorrowedUntil); } else { TeachingMaterial teachingMaterial = material.getTeachingMaterial(); String to = GRADE + teachingMaterial.getToGrade() + ", " + teachingMaterial.getToTerm(); borrowedUntil = to; } Object materialItemId = treeTableContent.addItem(new Object[] { checkBoxMaterial, borrowedUntil }, null); treeTableContent.setChildrenAllowed(materialItemId, false); Object parentStudentItemId = null; for (CheckBox checkBoxStudent : allStudentCheckBoxes.keySet()) { Student student = (Student) checkBoxStudent.getData(); if (student.equals(material.getStudent())) { parentStudentItemId = allStudentCheckBoxes.get(checkBoxStudent); break; } } if (parentStudentItemId == null) { parentStudentItemId = addStudentToTree(material.getStudent()); } treeTableContent.setParent(materialItemId, parentStudentItemId); allMaterialCheckBoxes.put(checkBoxMaterial, materialItemId); return materialItemId; } /* * Validates the tree table so that empty grades / students etc. are not * displayed. */ private void validateTableContent() { if (allGradeCheckBoxes.isEmpty()) { treeTableContent.removeItem(checkBoxEverythingId); } removeEmptyGrades(); removeEmptyStudents(); } private void removeEmptyGrades() { for (CheckBox checkBoxGrade : allGradeCheckBoxes.keySet()) { Object gradeItemId = allGradeCheckBoxes.get(checkBoxGrade); if (treeTableContent.getChildren(gradeItemId) == null) { treeTableContent.removeItem(gradeItemId); } } } private void removeEmptyStudents() { for (CheckBox checkBoxStudent : allStudentCheckBoxes.keySet()) { Object studentItemId = allStudentCheckBoxes.get(checkBoxStudent); if (treeTableContent.getChildren(studentItemId) == null) { treeTableContent.removeItem(studentItemId); } } } /* * Notifies all registered observers that the selection changed by calling * their update method. */ private void notifyObserver() { if (registeredObservers == null) { return; } for (StudentMaterialSelectorObserver observer : registeredObservers) { observer.update(); } } }