001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/sections/tags/sakai_2-4-1/sections-app-util/src/java/org/sakaiproject/tool/section/decorator/SectionDecorator.java $
003: * $Id: SectionDecorator.java 20507 2007-01-20 21:33:16Z louis@media.berkeley.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2005, 2006 The Regents of the University of California and The Regents of the University of Michigan
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.tool.section.decorator;
021:
022: import java.io.Serializable;
023: import java.sql.Time;
024: import java.text.DateFormat;
025: import java.text.SimpleDateFormat;
026: import java.util.ArrayList;
027: import java.util.Comparator;
028: import java.util.Date;
029: import java.util.Iterator;
030: import java.util.List;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.commons.logging.LogFactory;
034: import org.sakaiproject.section.api.coursemanagement.Course;
035: import org.sakaiproject.section.api.coursemanagement.CourseSection;
036: import org.sakaiproject.section.api.coursemanagement.Meeting;
037: import org.sakaiproject.tool.section.jsf.JsfUtil;
038: import org.sakaiproject.tool.section.jsf.RowGroupable;
039:
040: /**
041: * Decorates a CourseSection for use in the instructor's (and TA's) page views.
042: *
043: * @author <a href="mailto:jholtzman@berkeley.edu">Josh Holtzman</a>
044: *
045: */
046: public class SectionDecorator implements RowGroupable, Serializable,
047: Comparable {
048: private static final long serialVersionUID = 1L;
049: private static final Log log = LogFactory
050: .getLog(SectionDecorator.class);
051:
052: public static final int NAME_TRUNCATION_LENGTH = 20;
053: public static final int LOCATION_TRUNCATION_LENGTH = 15;
054:
055: protected CourseSection section;
056: protected String categoryForDisplay;
057: protected List<MeetingDecorator> decoratedMeetings;
058:
059: protected List<String> instructorNames;
060: protected int totalEnrollments;
061: protected String spotsAvailable;
062: private boolean flaggedForRemoval;
063:
064: /* Whether this decorator should show the number of spots available as a negative
065: * number or zero when the section is overenrolled */
066: protected boolean showNegativeSpots;
067:
068: /**
069: * Creates a SectionDecorator from a vanilla CourseSection.
070: *
071: * @param section
072: */
073: public SectionDecorator(CourseSection section,
074: boolean showNegativeSpots) {
075: this .section = section;
076: this .showNegativeSpots = showNegativeSpots;
077: this .decoratedMeetings = new ArrayList<MeetingDecorator>();
078: if (section.getMeetings() != null) {
079: for (Iterator iter = section.getMeetings().iterator(); iter
080: .hasNext();) {
081: decoratedMeetings.add(new MeetingDecorator(
082: (Meeting) iter.next()));
083: }
084: }
085: }
086:
087: /**
088: * Creates a SectionDecorator with more contextual information about the section.
089: *
090: * @param section The CourseSection to decorate
091: * @param categoryForDisplay The CourseSection's category label
092: * @param instructorNames The names of TAs in this CourseSection
093: * @param totalEnrollments The total number of enrollments in this CourseSection
094: */
095: public SectionDecorator(CourseSection section,
096: String categoryForDisplay, List<String> instructorNames,
097: int totalEnrollments, boolean showNegativeSpots) {
098: this (section, showNegativeSpots);
099: this .categoryForDisplay = categoryForDisplay;
100:
101: this .instructorNames = instructorNames;
102: this .totalEnrollments = totalEnrollments;
103:
104: if (section.getMaxEnrollments() == null) {
105: spotsAvailable = JsfUtil
106: .getLocalizedMessage("section_max_size_unlimited");
107: } else {
108: int spots = section.getMaxEnrollments().intValue()
109: - totalEnrollments;
110: if (spots < 0 && !showNegativeSpots) {
111: spotsAvailable = "0";
112: } else {
113: spotsAvailable = Integer.toString(spots);
114: }
115: }
116: }
117:
118: public SectionDecorator() {
119: // Needed for serialization
120: }
121:
122: public List getInstructorNames() {
123: return instructorNames;
124: }
125:
126: public String getSpotsAvailable() {
127: return spotsAvailable;
128: }
129:
130: public int getTotalEnrollments() {
131: return totalEnrollments;
132: }
133:
134: public boolean isFlaggedForRemoval() {
135: return flaggedForRemoval;
136: }
137:
138: public void setFlaggedForRemoval(boolean flaggedForRemoval) {
139: this .flaggedForRemoval = flaggedForRemoval;
140: }
141:
142: public int compareTo(Object o) {
143: return this .getTitle().toLowerCase().compareTo(
144: ((SectionDecorator) o).getTitle().toLowerCase());
145: }
146:
147: /**
148: * Compares SectionDecorators by the section's title.
149: *
150: * @param sortAscending
151: * @return
152: */
153: public static final Comparator<SectionDecorator> getTitleComparator(
154: final boolean sortAscending) {
155: return new Comparator<SectionDecorator>() {
156: public int compare(SectionDecorator section1,
157: SectionDecorator section2) {
158: int categoryNameComparison = section1.getCategory()
159: .compareTo(section2.getCategory());
160: if (categoryNameComparison == 0) {
161: int comparison = section1.getTitle().toLowerCase()
162: .compareTo(
163: section2.getTitle().toLowerCase());
164: return sortAscending ? comparison
165: : (-1 * comparison);
166: } else {
167: return categoryNameComparison;
168: }
169: }
170: };
171: }
172:
173: /**
174: * Compares SectionDecorators by the section's first meeting times.
175: *
176: * @param sortAscending
177: * @return
178: */
179: public static final Comparator<SectionDecorator> getTimeComparator(
180: final boolean sortAscending) {
181: return new Comparator<SectionDecorator>() {
182: public int compare(SectionDecorator section1,
183: SectionDecorator section2) {
184:
185: // First compare the category name, then compare the time
186: int categoryNameComparison = section1.getCategory()
187: .compareTo(section2.getCategory());
188: if (categoryNameComparison == 0) {
189: // These are in the same category, so compare by the first meeting time
190: List meetings1 = section1.getDecoratedMeetings();
191: List meetings2 = section2.getDecoratedMeetings();
192:
193: MeetingDecorator meeting1 = (MeetingDecorator) meetings1
194: .get(0);
195: MeetingDecorator meeting2 = (MeetingDecorator) meetings2
196: .get(0);
197:
198: Time startTime1 = meeting1.getStartTime();
199: Time startTime2 = meeting2.getStartTime();
200:
201: if (startTime1 == null && startTime2 != null) {
202: return sortAscending ? -1 : 1;
203: }
204: if (startTime2 == null && startTime1 != null) {
205: return sortAscending ? 1 : -1;
206: }
207:
208: if (startTime1 == null && startTime2 == null
209: || startTime1.equals(startTime2)) {
210: return getTitleComparator(sortAscending)
211: .compare(section1, section2);
212: }
213: return sortAscending ? startTime1
214: .compareTo(startTime2) : startTime2
215: .compareTo(startTime1);
216: } else {
217: return categoryNameComparison;
218: }
219: }
220: };
221: }
222:
223: /**
224: * Compares SectionDecorators by the section's first meeting days.
225: *
226: * @param sortAscending
227: * @return
228: */
229: public static final Comparator<SectionDecorator> getDayComparator(
230: final boolean sortAscending) {
231: return new Comparator<SectionDecorator>() {
232: public int compare(SectionDecorator section1,
233: SectionDecorator section2) {
234: // First compare the category name, then compare the time
235: int categoryNameComparison = section1.getCategory()
236: .compareTo(section2.getCategory());
237: if (categoryNameComparison == 0) {
238: // These are in the same category, so compare by the first meeting time
239: List<MeetingDecorator> meetings1 = section1
240: .getDecoratedMeetings();
241: List<MeetingDecorator> meetings2 = section2
242: .getDecoratedMeetings();
243:
244: String sortString1 = generateSortableDayString(meetings1
245: .get(0));
246: String sortString2 = generateSortableDayString(meetings2
247: .get(0));
248:
249: int diff = sortString1.compareTo(sortString2);
250:
251: if (diff == 0) {
252: return getTitleComparator(sortAscending)
253: .compare(section1, section2);
254: }
255: return sortAscending ? diff : -1 * diff;
256: } else {
257: return categoryNameComparison;
258: }
259: }
260: };
261: }
262:
263: /**
264: * Generate a string that contains information on the meeting days for a section
265: * meeting, and is sortable.
266: *
267: * @param meeting A meeting we're interested in sorting by day of the week.
268: * @return A string that sorts in the order of the meetings' days of the week.
269: */
270: private static final String generateSortableDayString(
271: MeetingDecorator meeting) {
272: StringBuffer sb = new StringBuffer();
273: if (meeting.isMonday()) {
274: sb.append("a");
275: }
276: if (meeting.isTuesday()) {
277: sb.append("b");
278: }
279: if (meeting.isWednesday()) {
280: sb.append("c");
281: }
282: if (meeting.isThursday()) {
283: sb.append("d");
284: }
285: if (meeting.isFriday()) {
286: sb.append("e");
287: }
288: if (meeting.isSaturday()) {
289: sb.append("f");
290: }
291: if (meeting.isSunday()) {
292: sb.append("g");
293: }
294: return sb.toString();
295: }
296:
297: /**
298: * Compares SectionDecorators by the section's first meeting location.
299: *
300: * @param sortAscending
301: * @return
302: */
303: public static final Comparator<SectionDecorator> getLocationComparator(
304: final boolean sortAscending) {
305: return new Comparator<SectionDecorator>() {
306: public int compare(SectionDecorator section1,
307: SectionDecorator section2) {
308:
309: // First compare the category name, then compare the time
310: int categoryNameComparison = section1.getCategory()
311: .compareTo(section2.getCategory());
312: if (categoryNameComparison == 0) {
313: // These are in the same category, so compare by the first meeting time
314: List meetings1 = section1.getDecoratedMeetings();
315: List meetings2 = section2.getDecoratedMeetings();
316:
317: MeetingDecorator meeting1 = (MeetingDecorator) meetings1
318: .get(0);
319: MeetingDecorator meeting2 = (MeetingDecorator) meetings2
320: .get(0);
321:
322: String location1 = meeting1.getLocation();
323: String location2 = meeting2.getLocation();
324:
325: if (location1 == null && location2 != null) {
326: return sortAscending ? -1 : 1;
327: }
328: if (location2 == null && location1 != null) {
329: return sortAscending ? 1 : -1;
330: }
331:
332: if (location1 == null && location2 == null
333: || location1.equals(location2)) {
334: return getTitleComparator(sortAscending)
335: .compare(section1, section2);
336: }
337: return sortAscending ? location1
338: .compareTo(location2) : location2
339: .compareTo(location1);
340: } else {
341: return categoryNameComparison;
342: }
343: }
344: };
345: }
346:
347: /**
348: * Compares SectionDecorators by the section's TA names.
349: *
350: * @param sortAscending
351: * @return
352: */
353: public static final Comparator<SectionDecorator> getManagersComparator(
354: final boolean sortAscending) {
355: return new Comparator<SectionDecorator>() {
356: public int compare(SectionDecorator section1,
357: SectionDecorator section2) {
358: // First compare the category name, then compare the time
359: int categoryNameComparison = section1.getCategory()
360: .compareTo(section2.getCategory());
361: if (categoryNameComparison == 0) {
362: // These are in the same category, so compare by the list of managers
363: List managers1 = section1.getInstructorNames();
364: List managers2 = section2.getInstructorNames();
365: if (managers1.isEmpty() && !managers2.isEmpty()) {
366: return sortAscending ? -1 : 1;
367: }
368: if (managers2.isEmpty() && !managers1.isEmpty()) {
369: return sortAscending ? 1 : -1;
370: }
371: if (managers1.isEmpty() && managers2.isEmpty()) {
372: return getTitleComparator(sortAscending)
373: .compare(section1, section2);
374: }
375: int managersComparison = managers1.get(0)
376: .toString().compareTo(
377: managers2.get(0).toString());
378: if (managersComparison == 0) {
379: return getTitleComparator(sortAscending)
380: .compare(section1, section2);
381: }
382: return sortAscending ? managersComparison
383: : (-1 * managersComparison);
384: }
385: // These are in different categories, so sort them by category name
386: return categoryNameComparison;
387: }
388: };
389: }
390:
391: /**
392: * Compares SectionDecorators by the section's enrollments.
393: *
394: * @param sortAscending Whether to sort ascending or descending
395: * @param useAvailable Whether to use the number of available enrollments, or the total number of enrollments to sort
396: * @return
397: */
398: public static final Comparator<SectionDecorator> getEnrollmentsComparator(
399: final boolean sortAscending, final boolean useAvailable) {
400: return new Comparator<SectionDecorator>() {
401: public int compare(SectionDecorator section1,
402: SectionDecorator section2) {
403: // First compare the category name, then compare available spots
404: int categoryNameComparison = section1.getCategory()
405: .compareTo(section2.getCategory());
406: if (categoryNameComparison == 0) {
407: // These are in the same category, so compare by total
408: Integer maxEnrollments1 = section1
409: .getMaxEnrollments();
410: Integer maxEnrollments2 = section2
411: .getMaxEnrollments();
412:
413: int total1 = section1.getTotalEnrollments();
414: int total2 = section2.getTotalEnrollments();
415:
416: int availEnrollmentComparison;
417:
418: if (useAvailable) {
419: if (maxEnrollments1 == null
420: && maxEnrollments2 != null) {
421: return sortAscending ? 1 : -1;
422: }
423: if (maxEnrollments2 == null
424: && maxEnrollments1 != null) {
425: return sortAscending ? -1 : 1;
426: }
427: if (maxEnrollments1 == null
428: && maxEnrollments2 == null) {
429: return getTitleComparator(sortAscending)
430: .compare(section1, section2);
431: }
432: availEnrollmentComparison = (maxEnrollments1
433: .intValue() - section1.totalEnrollments)
434: - (maxEnrollments2.intValue() - section2.totalEnrollments);
435: } else {
436: availEnrollmentComparison = total1 - total2;
437: }
438:
439: // If these are in the same category, and have the same number of enrollments, use the title to sort
440: if (availEnrollmentComparison == 0) {
441: return getTitleComparator(sortAscending)
442: .compare(section1, section2);
443: }
444: return sortAscending ? availEnrollmentComparison
445: : (-1 * availEnrollmentComparison);
446: }
447: // These are in different categories, so sort them by category name
448: return categoryNameComparison;
449: }
450: };
451: }
452:
453: public CourseSection getSection() {
454: return section;
455: }
456:
457: public List<MeetingDecorator> getDecoratedMeetings() {
458: return decoratedMeetings;
459: }
460:
461: // Decorator methods
462: public String getCategoryForDisplay() {
463: return categoryForDisplay;
464: }
465:
466: // Delegate methods
467:
468: public String getCategory() {
469: return section.getCategory();
470: }
471:
472: public Course getCourse() {
473: return section.getCourse();
474: }
475:
476: public Integer getMaxEnrollments() {
477: return section.getMaxEnrollments();
478: }
479:
480: public String getTitle() {
481: return section.getTitle();
482: }
483:
484: public String getUuid() {
485: return section.getUuid();
486: }
487:
488: public class MeetingDecorator implements Serializable {
489: private static final long serialVersionUID = 1L;
490: private Meeting meeting;
491:
492: public MeetingDecorator() {
493: // Needed for serialization
494: }
495:
496: public MeetingDecorator(Meeting meeting) {
497: this .meeting = meeting;
498: }
499:
500: private List<String> getDayList() {
501: List<String> list = new ArrayList<String>();
502: if (meeting.isMonday())
503: list.add("day_of_week_monday");
504: if (meeting.isTuesday())
505: list.add("day_of_week_tuesday");
506: if (meeting.isWednesday())
507: list.add("day_of_week_wednesday");
508: if (meeting.isThursday())
509: list.add("day_of_week_thursday");
510: if (meeting.isFriday())
511: list.add("day_of_week_friday");
512: if (meeting.isSaturday())
513: list.add("day_of_week_saturday");
514: if (meeting.isSunday())
515: list.add("day_of_week_sunday");
516: return list;
517: }
518:
519: private List<String> getAbbreviatedDayList() {
520: List<String> list = new ArrayList<String>();
521: if (meeting.isMonday())
522: list.add("day_of_week_monday_abbrev");
523: if (meeting.isTuesday())
524: list.add("day_of_week_tuesday_abbrev");
525: if (meeting.isWednesday())
526: list.add("day_of_week_wednesday_abbrev");
527: if (meeting.isThursday())
528: list.add("day_of_week_thursday_abbrev");
529: if (meeting.isFriday())
530: list.add("day_of_week_friday_abbrev");
531: if (meeting.isSaturday())
532: list.add("day_of_week_saturday_abbrev");
533: if (meeting.isSunday())
534: list.add("day_of_week_sunday_abbrev");
535: return list;
536: }
537:
538: public String getTimes() {
539: String timeSepChar = JsfUtil
540: .getLocalizedMessage("time_sep_char");
541:
542: StringBuffer sb = new StringBuffer();
543:
544: // Start time
545: DateFormat df = new SimpleDateFormat("h:mm a");
546: sb.append(" ");
547: if (meeting.getStartTime() != null) {
548: sb.append(df.format(
549: new Date(meeting.getStartTime().getTime()))
550: .toLowerCase());
551: }
552:
553: // End time
554: if (meeting.getStartTime() != null
555: && meeting.getEndTime() != null) {
556: sb.append(timeSepChar);
557: }
558:
559: if (meeting.getEndTime() != null) {
560: sb.append(df.format(
561: new Date(meeting.getEndTime().getTime()))
562: .toLowerCase());
563: }
564: if (log.isDebugEnabled())
565: log.debug("Meeting times = " + sb.toString());
566: return sb.toString();
567: }
568:
569: public String getAbbreviatedDays() {
570: String daySepChar = JsfUtil
571: .getLocalizedMessage("day_of_week_sep_char");
572:
573: StringBuffer sb = new StringBuffer();
574: for (Iterator iter = getAbbreviatedDayList().iterator(); iter
575: .hasNext();) {
576: String day = (String) iter.next();
577: sb.append(JsfUtil.getLocalizedMessage(day));
578: if (iter.hasNext()) {
579: sb.append(daySepChar);
580: }
581: }
582: if (log.isDebugEnabled())
583: log.debug("Meeting days = " + sb.toString());
584: return sb.toString();
585: }
586:
587: public String getDays() {
588: String daySepChar = JsfUtil
589: .getLocalizedMessage("day_of_week_sep_char");
590:
591: StringBuffer sb = new StringBuffer();
592: for (Iterator iter = getDayList().iterator(); iter
593: .hasNext();) {
594: String day = (String) iter.next();
595: sb.append(JsfUtil.getLocalizedMessage(day));
596: if (iter.hasNext()) {
597: sb.append(daySepChar);
598: }
599: }
600: if (log.isDebugEnabled())
601: log.debug("Meeting days = " + sb.toString());
602: return sb.toString();
603: }
604:
605: // Meeting delegate methods
606:
607: public Time getEndTime() {
608: return meeting.getEndTime();
609: }
610:
611: public String getLocation() {
612: return meeting.getLocation();
613: }
614:
615: public Time getStartTime() {
616: return meeting.getStartTime();
617: }
618:
619: public boolean isFriday() {
620: return meeting.isFriday();
621: }
622:
623: public boolean isMonday() {
624: return meeting.isMonday();
625: }
626:
627: public boolean isSaturday() {
628: return meeting.isSaturday();
629: }
630:
631: public boolean isSunday() {
632: return meeting.isSunday();
633: }
634:
635: public boolean isThursday() {
636: return meeting.isThursday();
637: }
638:
639: public boolean isTuesday() {
640: return meeting.isTuesday();
641: }
642:
643: public boolean isWednesday() {
644: return meeting.isWednesday();
645: }
646: }
647:
648: public String getRowGroupId() {
649: return section.getCategory();
650: }
651:
652: public String getRowGroupTitle() {
653: return categoryForDisplay;
654: }
655:
656: }
|