/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/sections/trunk/sections-app-util/src/java/org/sakaiproject/tool/section/decorator/SectionDecorator.java $
* $Id: SectionDecorator.java 124760 2013-05-21 15:17:30Z azeckoski@unicon.net $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.tool.section.decorator;
import java.io.Serializable;
import java.sql.Time;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.section.api.coursemanagement.Course;
import org.sakaiproject.section.api.coursemanagement.CourseSection;
import org.sakaiproject.section.api.coursemanagement.Meeting;
import org.sakaiproject.tool.section.jsf.JsfUtil;
import org.sakaiproject.tool.section.jsf.RowGroupable;
import org.sakaiproject.util.ResourceLoader;
import org.sakaiproject.time.cover.TimeService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.component.cover.ComponentManager;
/**
* Decorates a CourseSection for use in the instructor's (and TA's) page views.
*
* @author <a href="mailto:jholtzman@berkeley.edu">Josh Holtzman</a>
*
*/
public class SectionDecorator implements RowGroupable,Serializable, Comparable{
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(SectionDecorator.class);
public static final int NAME_TRUNCATION_LENGTH = 20;
public static final int LOCATION_TRUNCATION_LENGTH = 15;
public static final String READ_ONLY_SECTION_CATEGORIES = "section.info.readonly.section.categories";
protected CourseSection section;
protected String categoryForDisplay;
protected List<MeetingDecorator> decoratedMeetings;
protected List<String> instructorNames;
protected int totalEnrollments;
protected String spotsAvailable;
private boolean flaggedForRemoval;
/* Whether this decorator should show the number of spots available as a negative
* number or zero when the section is overenrolled */
protected boolean showNegativeSpots;
// SAK-23495
protected boolean readOnly;
private static Set<String> readOnlyCategories;
/**
* Creates a SectionDecorator from a vanilla CourseSection.
*
* @param section
*/
public SectionDecorator(CourseSection section, boolean showNegativeSpots) {
this.section = section;
this.showNegativeSpots = showNegativeSpots;
this.decoratedMeetings = new ArrayList<MeetingDecorator>();
if(section.getMeetings() != null) {
for(Iterator iter = section.getMeetings().iterator(); iter.hasNext();) {
decoratedMeetings.add(new MeetingDecorator((Meeting)iter.next()));
}
}
if (readOnlyCategories == null) {
readOnlyCategories = new HashSet<String>();
ServerConfigurationService serverConfigurationService = (ServerConfigurationService) ComponentManager.get(ServerConfigurationService.class);
if (serverConfigurationService != null) {
String[] readOnlySectionCategories = serverConfigurationService.getStrings(READ_ONLY_SECTION_CATEGORIES);
if (readOnlySectionCategories != null) {
readOnlyCategories = new HashSet<String>(Arrays.asList(readOnlySectionCategories));
}
}
}
this.readOnly = readOnlyCategories.contains(section.getCategory());
}
/**
* Creates a SectionDecorator with more contextual information about the section.
*
* @param section The CourseSection to decorate
* @param categoryForDisplay The CourseSection's category label
* @param instructorNames The names of TAs in this CourseSection
* @param totalEnrollments The total number of enrollments in this CourseSection
*/
public SectionDecorator(CourseSection section, String categoryForDisplay,
List<String> instructorNames, int totalEnrollments, boolean showNegativeSpots) {
this(section, showNegativeSpots);
this.categoryForDisplay = categoryForDisplay;
this.instructorNames = instructorNames;
this.totalEnrollments = totalEnrollments;
if(section.getMaxEnrollments() == null) {
spotsAvailable = JsfUtil.getLocalizedMessage("section_max_size_unlimited");
} else {
int spots = section.getMaxEnrollments().intValue() - totalEnrollments;
if(spots < 0 && ! showNegativeSpots) {
spotsAvailable = "0";
} else {
spotsAvailable = Integer.toString(spots);
}
}
}
public SectionDecorator() {
// Needed for serialization
}
public boolean isReadOnly() {
return readOnly;
}
public List getInstructorNames() {
return instructorNames;
}
public String getSpotsAvailable() {
return spotsAvailable;
}
public int getTotalEnrollments() {
return totalEnrollments;
}
public boolean isFlaggedForRemoval() {
return flaggedForRemoval;
}
public void setFlaggedForRemoval(boolean flaggedForRemoval) {
this.flaggedForRemoval = flaggedForRemoval;
}
public int compareTo(Object o) {
return this.getTitle().toLowerCase().compareTo(((SectionDecorator)o).getTitle().toLowerCase());
}
/**
* Compares SectionDecorators by the section's title.
*
* @param sortAscending
* @return
*/
public static final Comparator<SectionDecorator> getTitleComparator(final boolean sortAscending) {
return new Comparator<SectionDecorator>() {
public int compare(SectionDecorator section1, SectionDecorator section2) {
int categoryNameComparison = section1.getCategory().compareTo(section2.getCategory());
if(categoryNameComparison == 0) {
int comparison = section1.getTitle().toLowerCase().compareTo(section2.getTitle().toLowerCase());
return sortAscending ? comparison : (-1 * comparison);
} else {
return categoryNameComparison;
}
}
};
}
/**
* Compares SectionDecorators by the section's first meeting times.
*
* @param sortAscending
* @return
*/
public static final Comparator<SectionDecorator> getTimeComparator(final boolean sortAscending) {
return new Comparator<SectionDecorator>() {
public int compare(SectionDecorator section1, SectionDecorator section2) {
// First compare the category name, then compare the time
int categoryNameComparison = section1.getCategory().compareTo(section2.getCategory());
if(categoryNameComparison == 0) {
// These are in the same category, so compare by the first meeting time
List meetings1 = section1.getDecoratedMeetings();
List meetings2 = section2.getDecoratedMeetings();
MeetingDecorator meeting1 = (MeetingDecorator)meetings1.get(0);
MeetingDecorator meeting2 = (MeetingDecorator)meetings2.get(0);
Time startTime1 = meeting1.getStartTime();
Time startTime2 = meeting2.getStartTime();
if(startTime1 == null && startTime2 != null) {
return sortAscending? -1 : 1 ;
}
if(startTime2 == null && startTime1 != null) {
return sortAscending? 1 : -1 ;
}
if(startTime1 == null && startTime2 == null ||
startTime1.equals(startTime2)) {
return getTitleComparator(sortAscending).compare(section1, section2);
}
return sortAscending ? startTime1.compareTo(startTime2) : startTime2.compareTo(startTime1);
} else {
return categoryNameComparison;
}
}
};
}
/**
* Compares SectionDecorators by the section's first meeting days.
*
* @param sortAscending
* @return
*/
public static final Comparator<SectionDecorator> getDayComparator(final boolean sortAscending) {
return new Comparator<SectionDecorator>() {
public int compare(SectionDecorator section1, SectionDecorator section2) {
// First compare the category name, then compare the time
int categoryNameComparison = section1.getCategory().compareTo(section2.getCategory());
if(categoryNameComparison == 0) {
// These are in the same category, so compare by the first meeting time
List<MeetingDecorator> meetings1 = section1.getDecoratedMeetings();
List<MeetingDecorator> meetings2 = section2.getDecoratedMeetings();
String sortString1 = generateSortableDayString(meetings1.get(0));
String sortString2 = generateSortableDayString(meetings2.get(0));
int diff = sortString1.compareTo(sortString2);
if(diff == 0) {
return getTitleComparator(sortAscending).compare(section1, section2);
}
return sortAscending ? diff : -1*diff ;
} else {
return categoryNameComparison;
}
}
};
}
/**
* Generate a string that contains information on the meeting days for a section
* meeting, and is sortable.
*
* @param meeting A meeting we're interested in sorting by day of the week.
* @return A string that sorts in the order of the meetings' days of the week.
*/
private static final String generateSortableDayString(MeetingDecorator meeting) {
StringBuilder sb = new StringBuilder();
if(meeting.isMonday()) {
sb.append("a");
}
if(meeting.isTuesday()) {
sb.append("b");
}
if(meeting.isWednesday()) {
sb.append("c");
}
if(meeting.isThursday()) {
sb.append("d");
}
if(meeting.isFriday()) {
sb.append("e");
}
if(meeting.isSaturday()) {
sb.append("f");
}
if(meeting.isSunday()) {
sb.append("g");
}
return sb.toString();
}
/**
* Compares SectionDecorators by the section's first meeting location.
*
* @param sortAscending
* @return
*/
public static final Comparator<SectionDecorator> getLocationComparator(final boolean sortAscending) {
return new Comparator<SectionDecorator>() {
public int compare(SectionDecorator section1, SectionDecorator section2) {
// First compare the category name, then compare the time
int categoryNameComparison = section1.getCategory().compareTo(section2.getCategory());
if(categoryNameComparison == 0) {
// These are in the same category, so compare by the first meeting time
List meetings1 = section1.getDecoratedMeetings();
List meetings2 = section2.getDecoratedMeetings();
MeetingDecorator meeting1 = (MeetingDecorator)meetings1.get(0);
MeetingDecorator meeting2 = (MeetingDecorator)meetings2.get(0);
String location1 = meeting1.getLocation();
String location2 = meeting2.getLocation();
if(location1 == null && location2 != null) {
return sortAscending? -1 : 1 ;
}
if(location2 == null && location1 != null) {
return sortAscending? 1 : -1 ;
}
if(location1 == null && location2 == null ||
location1.equals(location2)) {
return getTitleComparator(sortAscending).compare(section1, section2);
}
return sortAscending ? location1.compareTo(location2) : location2.compareTo(location1);
} else {
return categoryNameComparison;
}
}
};
}
/**
* Compares SectionDecorators by the section's TA names.
*
* @param sortAscending
* @return
*/
public static final Comparator<SectionDecorator> getManagersComparator(final boolean sortAscending) {
return new Comparator<SectionDecorator>() {
public int compare(SectionDecorator section1, SectionDecorator section2) {
// First compare the category name, then compare the time
int categoryNameComparison = section1.getCategory().compareTo(section2.getCategory());
if(categoryNameComparison == 0) {
// These are in the same category, so compare by the list of managers
List managers1 = section1.getInstructorNames();
List managers2 = section2.getInstructorNames();
if(managers1.isEmpty() && ! managers2.isEmpty()) {
return sortAscending? -1 : 1 ;
}
if(managers2.isEmpty() && ! managers1.isEmpty()) {
return sortAscending? 1 : -1 ;
}
if(managers1.isEmpty() && managers2.isEmpty()) {
return getTitleComparator(sortAscending).compare(section1, section2);
}
int managersComparison = managers1.get(0).toString().compareTo(managers2.get(0).toString());
if(managersComparison == 0) {
return getTitleComparator(sortAscending).compare(section1, section2);
}
return sortAscending ? managersComparison : (-1 * managersComparison);
}
// These are in different categories, so sort them by category name
return categoryNameComparison;
}
};
}
/**
* Compares SectionDecorators by the section's enrollments.
*
* @param sortAscending Whether to sort ascending or descending
* @param useAvailable Whether to use the number of available enrollments, or the total number of enrollments to sort
* @return
*/
public static final Comparator<SectionDecorator> getEnrollmentsComparator(final boolean sortAscending, final boolean useAvailable) {
return new Comparator<SectionDecorator>() {
public int compare(SectionDecorator section1, SectionDecorator section2) {
// First compare the category name, then compare available spots
int categoryNameComparison = section1.getCategory().compareTo(section2.getCategory());
if(categoryNameComparison == 0) {
// These are in the same category, so compare by total
Integer maxEnrollments1 = section1.getMaxEnrollments();
Integer maxEnrollments2 = section2.getMaxEnrollments();
int total1 = section1.getTotalEnrollments();
int total2 = section2.getTotalEnrollments();
int availEnrollmentComparison;
if(useAvailable) {
if(maxEnrollments1 == null && maxEnrollments2 != null) {
return sortAscending? 1 : -1 ;
}
if(maxEnrollments2 == null && maxEnrollments1 != null) {
return sortAscending? -1 : 1 ;
}
if(maxEnrollments1 == null && maxEnrollments2 == null) {
return getTitleComparator(sortAscending).compare(section1, section2);
}
availEnrollmentComparison = (maxEnrollments1.intValue() - section1.totalEnrollments) -
(maxEnrollments2.intValue() - section2.totalEnrollments);
} else {
availEnrollmentComparison = total1 - total2;
}
// If these are in the same category, and have the same number of enrollments, use the title to sort
if(availEnrollmentComparison == 0) {
return getTitleComparator(sortAscending).compare(section1, section2);
}
return sortAscending ? availEnrollmentComparison : (-1 * availEnrollmentComparison);
}
// These are in different categories, so sort them by category name
return categoryNameComparison;
}
};
}
public CourseSection getSection() {
return section;
}
public List<MeetingDecorator> getDecoratedMeetings() {
return decoratedMeetings;
}
// Decorator methods
public String getCategoryForDisplay() {
return categoryForDisplay;
}
// Delegate methods
public String getCategory() {
return section.getCategory();
}
public Course getCourse() {
return section.getCourse();
}
public Integer getMaxEnrollments() {
return section.getMaxEnrollments();
}
public String getTitle() {
return section.getTitle();
}
public String getUuid() {
return section.getUuid();
}
public class MeetingDecorator implements Serializable {
private static final long serialVersionUID = 1L;
private Meeting meeting;
public MeetingDecorator() {
// Needed for serialization
}
public MeetingDecorator(Meeting meeting) {
this.meeting = meeting;
}
private List<String> getDayList() {
List<String> list = new ArrayList<String>();
if(meeting.isMonday())
list.add("day_of_week_monday");
if(meeting.isTuesday())
list.add("day_of_week_tuesday");
if(meeting.isWednesday())
list.add("day_of_week_wednesday");
if(meeting.isThursday())
list.add("day_of_week_thursday");
if(meeting.isFriday())
list.add("day_of_week_friday");
if(meeting.isSaturday())
list.add("day_of_week_saturday");
if(meeting.isSunday())
list.add("day_of_week_sunday");
return list;
}
private List<String> getAbbreviatedDayList() {
List<String> list = new ArrayList<String>();
ResourceLoader rl = new ResourceLoader();
DateFormatSymbols dfs = new DateFormatSymbols(rl.getLocale());
String[] daysOfWeek = dfs.getShortWeekdays();
if(meeting.isMonday())
list.add(daysOfWeek[Calendar.MONDAY]);
if(meeting.isTuesday())
list.add(daysOfWeek[Calendar.TUESDAY]);
if(meeting.isWednesday())
list.add(daysOfWeek[Calendar.WEDNESDAY]);
if(meeting.isThursday())
list.add(daysOfWeek[Calendar.THURSDAY]);
if(meeting.isFriday())
list.add(daysOfWeek[Calendar.FRIDAY]);
if(meeting.isSaturday())
list.add(daysOfWeek[Calendar.SATURDAY]);
if(meeting.isSunday())
list.add(daysOfWeek[Calendar.SUNDAY]);
return list;
}
public String getTimes() {
String timeSepChar = ",";
StringBuilder sb = new StringBuilder();
// Start time
ResourceLoader rl = new ResourceLoader();
DateFormat df = new SimpleDateFormat(JsfUtil.TIME_PATTERN_LONG, rl.getLocale());
df.setTimeZone(TimeService.getLocalTimeZone());
sb.append(" ");
if(meeting.getStartTime() != null) {
sb.append(df.format(new Date(meeting.getStartTime().getTime())).toLowerCase());
}
// End time
if(meeting.getStartTime() != null &&
meeting.getEndTime() != null) {
sb.append(timeSepChar);
}
if(meeting.getEndTime() != null) {
sb.append(df.format(new Date(meeting.getEndTime().getTime())).toLowerCase());
}
if(log.isDebugEnabled()) log.debug("Meeting times = " + sb.toString());
return sb.toString();
}
public String getAbbreviatedDays() {
String daySepChar = ",";
StringBuilder sb = new StringBuilder();
for(Iterator iter = getAbbreviatedDayList().iterator(); iter.hasNext();) {
String day = (String)iter.next();
sb.append(day);
if(iter.hasNext()) {
sb.append(daySepChar);
}
}
if(log.isDebugEnabled()) log.debug("Meeting days = " + sb.toString());
return sb.toString();
}
public String getDays() {
String daySepChar = ",";
StringBuilder sb = new StringBuilder();
for(Iterator iter = getDayList().iterator(); iter.hasNext();) {
String day = (String)iter.next();
sb.append(day);
if(iter.hasNext()) {
sb.append(daySepChar);
}
}
if(log.isDebugEnabled()) log.debug("Meeting days = " + sb.toString());
return sb.toString();
}
// Meeting delegate methods
public Time getEndTime() {
return meeting.getEndTime();
}
public String getLocation() {
return meeting.getLocation();
}
public Time getStartTime() {
return meeting.getStartTime();
}
public boolean isFriday() {
return meeting.isFriday();
}
public boolean isMonday() {
return meeting.isMonday();
}
public boolean isSaturday() {
return meeting.isSaturday();
}
public boolean isSunday() {
return meeting.isSunday();
}
public boolean isThursday() {
return meeting.isThursday();
}
public boolean isTuesday() {
return meeting.isTuesday();
}
public boolean isWednesday() {
return meeting.isWednesday();
}
}
public String getRowGroupId() {
return section.getCategory();
}
public String getRowGroupTitle() {
return categoryForDisplay;
}
}