/*******************************************************************************
* Copyright (c) 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.gradebook.ui;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.service.gradebook.shared.GradebookService;
import org.sakaiproject.service.gradebook.shared.GradebookExternalAssessmentService;
import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException;
import org.sakaiproject.service.gradebook.shared.UnknownUserException;
import org.sakaiproject.tool.gradebook.Assignment;
import org.sakaiproject.tool.gradebook.AssignmentGradeRecord;
import org.sakaiproject.tool.gradebook.Category;
import org.sakaiproject.tool.gradebook.Comment;
import org.sakaiproject.tool.gradebook.CourseGrade;
import org.sakaiproject.tool.gradebook.CourseGradeRecord;
import org.sakaiproject.tool.gradebook.GradableObject;
import org.sakaiproject.tool.gradebook.Gradebook;
import org.sakaiproject.tool.gradebook.GradingEvent;
import org.sakaiproject.tool.gradebook.GradingEvents;
import org.sakaiproject.tool.gradebook.jsf.FacesUtil;
import org.sakaiproject.tool.gradebook.ui.AssignmentGradeRow;
import org.sakaiproject.component.cover.ServerConfigurationService;
/**
* Provides data for the student view of the gradebook. Is used by both the
* instructor and student views. Based upon original StudentViewBean
*
*/
public class ViewByStudentBean extends EnrollmentTableBean implements Serializable {
private static Log logger = LogFactory.getLog(ViewByStudentBean.class);
// View maintenance fields - serializable.
private String userDisplayName;
private boolean courseGradeReleased;
private CourseGradeRecord courseGrade;
private String courseGradeLetter;
private boolean assignmentsReleased;
private boolean anyNotCounted;
private boolean anyExternallyMaintained = false;
private boolean isAllItemsViewOnly = true;
private double totalPoints;
private double pointsEarned;
private boolean showCoursePoints;
private boolean sortAscending;
private String sortColumn;
private boolean isInstructorView = false;
private StringBuilder rowStyles;
private Map commentMap;
private List gradebookItems;
private String studentUid;
private Gradebook gradebook;
private static final Map columnSortMap;
private static final String SORT_BY_NAME = "name";
protected static final String SORT_BY_DATE = "dueDate";
protected static final String SORT_BY_POINTS_POSSIBLE = "pointsPossible";
protected static final String SORT_BY_POINTS_EARNED = "pointsEarned";
protected static final String SORT_BY_GRADE = "grade";
protected static final String SORT_BY_ITEM_VALUE = "itemValue";
protected static final String SORT_BY_SORTING = "sorting";
public static Comparator nameComparator;
public static Comparator dateComparator;
public static Comparator pointsPossibleComparator;
public static Comparator pointsEarnedComparator;
public static Comparator gradeAsPercentageComparator;
private static Comparator doubleOrNothingComparator;
private static Comparator itemValueComparator;
private static Comparator gradeEditorComparator;
public static Comparator sortingComparator;
static {
sortingComparator = new Comparator() {
public int compare(Object o1, Object o2) {
return GradableObject.sortingComparator.compare(((AssignmentGradeRow)o1).getAssociatedAssignment(), ((AssignmentGradeRow)o2).getAssociatedAssignment());
}
};
nameComparator = new Comparator() {
public int compare(Object o1, Object o2) {
return Assignment.nameComparator.compare(((AssignmentGradeRow)o1).getAssociatedAssignment(), ((AssignmentGradeRow)o2).getAssociatedAssignment());
}
};
dateComparator = new Comparator() {
public int compare(Object o1, Object o2) {
return Assignment.dateComparator.compare(((AssignmentGradeRow)o1).getAssociatedAssignment(), ((AssignmentGradeRow)o2).getAssociatedAssignment());
}
};
pointsPossibleComparator = new Comparator() {
public int compare(Object o1, Object o2) {
return Assignment.pointsComparator.compare(((AssignmentGradeRow)o1).getAssociatedAssignment(), ((AssignmentGradeRow)o2).getAssociatedAssignment());
}
};
doubleOrNothingComparator = new Comparator() {
public int compare(Object o1, Object o2) {
Double double1 = (Double)o1;
Double double2 = (Double)o2;
if(double1 == null && double2 == null) {
return 0;
} else if(double1 == null && double2 != null) {
return -1;
} else if(double1 != null && double2 == null) {
return 1;
} else {
return double1.compareTo(double2);
}
}
};
pointsEarnedComparator = new Comparator() {
public int compare(Object o1, Object o2) {
int comp = doubleOrNothingComparator.compare(((AssignmentGradeRow)o1).getPointsEarned(), ((AssignmentGradeRow)o2).getPointsEarned());
if (comp == 0) {
return nameComparator.compare(o1, o2);
} else {
return comp;
}
}
};
gradeAsPercentageComparator = new Comparator() {
public int compare(Object o1, Object o2) {
int comp = doubleOrNothingComparator.compare(((AssignmentGradeRow)o1).getGradeAsPercentage(), ((AssignmentGradeRow)o2).getGradeAsPercentage());
if (comp == 0) {
return nameComparator.compare(o1, o2);
} else {
return comp;
}
}
};
itemValueComparator = new Comparator() {
public int compare(Object o1, Object o2) {
int comp = doubleOrNothingComparator.compare(((AssignmentGradeRow)o1).getAssociatedAssignment().getPointsPossible(), ((AssignmentGradeRow)o2).getAssociatedAssignment().getPointsPossible());
if (comp == 0) {
return nameComparator.compare(o1, o2);
} else {
return comp;
}
}
};
gradeEditorComparator = new Comparator() {
public int compare(Object o1, Object o2) {
return Assignment.gradeEditorComparator.compare(((AssignmentGradeRow)o1).getAssociatedAssignment(), ((AssignmentGradeRow)o2).getAssociatedAssignment());
}
};
columnSortMap = new HashMap();
columnSortMap.put(SORT_BY_SORTING, ViewByStudentBean.sortingComparator);
columnSortMap.put(SORT_BY_NAME, ViewByStudentBean.nameComparator);
columnSortMap.put(SORT_BY_DATE, ViewByStudentBean.dateComparator);
columnSortMap.put(SORT_BY_POINTS_POSSIBLE, ViewByStudentBean.pointsPossibleComparator);
columnSortMap.put(SORT_BY_POINTS_EARNED, ViewByStudentBean.pointsEarnedComparator);
columnSortMap.put(SORT_BY_GRADE, ViewByStudentBean.gradeAsPercentageComparator);
columnSortMap.put(SORT_BY_ITEM_VALUE, ViewByStudentBean.itemValueComparator);
columnSortMap.put(Assignment.SORT_BY_EDITOR, ViewByStudentBean.gradeEditorComparator);
}
/**
* Since this bean does not use the session-scoped preferences bean to keep
* sort preferences, we need to define the defaults locally.
*/
public ViewByStudentBean() {
// SAK-15311 - setup so students view can use sorting if configured
boolean useSort = getGradebookBean().getConfigurationBean().getBooleanConfig("gradebook.students.use.sorting", false);
if (useSort) {
// use sorting order
sortAscending = true;
sortColumn = SORT_BY_SORTING;
} else {
// use old default
sortAscending = true;
sortColumn = SORT_BY_DATE;
}
}
/**
* @see org.sakaiproject.tool.gradebook.ui.InitializableBean#init()
*/
public void init() {
// Get the active gradebook
gradebook = getGradebook();
isAllItemsViewOnly = true;
// Set the display name
try {
userDisplayName = getUserDirectoryService().getUserDisplayName(studentUid);
} catch (UnknownUserException e) {
if(logger.isErrorEnabled())logger.error("User " + studentUid + " is unknown but referenced in gradebook " + gradebook.getUid());
userDisplayName = "";
}
courseGradeReleased = gradebook.isCourseGradeDisplayed();
assignmentsReleased = gradebook.isAssignmentsDisplayed();
// Reset the row styles
rowStyles = new StringBuilder();
// Display course grade if we've been instructed to.
CourseGradeRecord gradeRecord = getGradebookManager().getStudentCourseGradeRecord(gradebook, studentUid);
if (gradeRecord != null) {
if (courseGradeReleased || isInstructorView) {
courseGrade = gradeRecord;
courseGradeLetter = gradeRecord.getDisplayGrade();
}
if(gradeRecord.getPointsEarned() != null){
pointsEarned = gradeRecord.getPointsEarned();
}
}
List<AssignmentGradeRecord> studentGradeRecs = getGradebookManager().getStudentGradeRecords(gradebook.getId(), studentUid);
getGradebookManager().applyDropScores(studentGradeRecs);
List<Assignment> assignments = getGradebookManager().getAssignments(gradebook.getId());
List<Assignment> countedAssigns = new ArrayList<Assignment>();
// let's filter the passed assignments to make sure they are all counted
if (assignments != null) {
for (Assignment assign : assignments) {
if (assign.isIncludedInCalculations()) {
countedAssigns.add(assign);
}
}
}
totalPoints = getGradebookManager().getTotalPointsInternal(gradebook, getGradebookManager().getCategories(gradebook.getId()),
studentUid, studentGradeRecs, countedAssigns, true);
//getTotalPointsInternal(gradebook, categories, studentUid, studentGradeRecs, countedAssigns);
initializeStudentGradeData();
}
/**
* @return Returns the gradebookItems. Can include AssignmentGradeRows and Categories
*/
public List getGradebookItems() {
return gradebookItems;
}
/**
* @return Returns the CourseGradeRecord for this student
*/
public CourseGradeRecord getCourseGrade() {
return courseGrade;
}
/**
*
* @return letter representation of course grade
*/
public String getCourseGradeLetter() {
return courseGradeLetter;
}
/**
* @return Returns the courseGradeReleased.
*/
public boolean isCourseGradeReleased() {
return courseGradeReleased;
}
public boolean isAssignmentsReleased() {
return assignmentsReleased;
}
/**
* @return Returns the userDisplayName.
*/
public String getUserDisplayName() {
return userDisplayName;
}
/**
* Sets userDisplayName
* @param userDisplayName
*/
public void setUserDisplayName(String userDisplayName) {
this.userDisplayName = userDisplayName;
}
// Sorting
public boolean isSortAscending() {
return sortAscending;
}
public void setSortAscending(boolean sortAscending) {
this.sortAscending = sortAscending;
}
public String getSortColumn() {
return sortColumn;
}
public void setSortColumn(String sortColumn) {
this.sortColumn = sortColumn;
}
/**
* @return The comma-separated list of css styles to use in displaying the rows
*/
public String getRowStyles() {
if(rowStyles == null) {
return null;
} else {
return rowStyles.toString();
}
}
public String getEventsLogType() {
return getLocalizedString("inst_view_log_type");
}
/**
* @return True if the gradebook contains any assignments not counted toward
* the final course grade.
*/
public boolean isAnyNotCounted() {
return anyNotCounted;
}
/**
* if all items are "view-only", we need to disable the action buttons
* @return
*/
public boolean isAllItemsViewOnly() {
return isAllItemsViewOnly;
}
/**
*
* @return true if the gradebook contains any externally maintained assignments
*/
public boolean isAnyExternallyMaintained() {
return anyExternallyMaintained;
}
public void setStudentUid(String studentUid) {
this.studentUid = studentUid;
}
public String getStudentUid() {
return studentUid;
}
public double getTotalPoints() {
return totalPoints;
}
public double getPointsEarned() {
return pointsEarned;
}
public boolean getShowCoursePoints() {
String showCoursePoints = ServerConfigurationService.getString("gradebook.showCoursePoints", "false");
return Boolean.parseBoolean(showCoursePoints);
}
/**
* Instructor view will include some features that aren't appropriate
* for student view
* @param includeNotCountedInCategoryAvg
*/
public void setIsInstructorView(boolean isInstructorView) {
this.isInstructorView = isInstructorView;
}
/**
* Create the AssignmentGradeRows for the passed assignments list
* @param assignments
* @param gradeRecords
* @return
*/
private List retrieveGradeRows(List assignments, List gradeRecords) {
List gradeRows = new ArrayList();
// Don't display any assignments if they have not been released
if(!assignmentsReleased && !isInstructorView)
return gradeRows;
if (assignments == null)
return gradeRows;
if(logger.isDebugEnabled()) {
logger.debug(assignments.size() + " total assignments");
logger.debug(gradeRecords.size() +" grade records");
}
boolean userHasGraderPerms;
if (isUserAbleToGradeAll())
userHasGraderPerms = false;
else if (isUserHasGraderPermissions())
userHasGraderPerms = true;
else
userHasGraderPerms = false;
Map viewableAssignmentsMap = new HashMap();
if (userHasGraderPerms) {
viewableAssignmentsMap = getGradebookPermissionService().getAvailableItemsForStudent(gradebook.getId(), getUserUid(), studentUid, getAllSections());
}
// Create a map of assignments to assignment grade rows
Map asnMap = new HashMap();
for(Iterator iter = assignments.iterator(); iter.hasNext();) {
Assignment asn = (Assignment)iter.next();
if (userHasGraderPerms) {
String function = (String)viewableAssignmentsMap.get(asn.getId());
if (function != null) {
boolean userCanGrade = function.equalsIgnoreCase(GradebookService.gradePermission);
if (userCanGrade)
isAllItemsViewOnly = false;
asnMap.put(asn, new AssignmentGradeRow(asn, gradebook, userCanGrade));
}
} else {
asnMap.put(asn, new AssignmentGradeRow(asn, gradebook, true));
isAllItemsViewOnly = false;
}
}
assignments = new ArrayList(asnMap.keySet());
for(Iterator iter = gradeRecords.iterator(); iter.hasNext();) {
AssignmentGradeRecord asnGr = (AssignmentGradeRecord)iter.next();
if (asnGr != null) {
// Update the AssignmentGradeRow in the map
AssignmentGradeRow asnGradeRow = (AssignmentGradeRow)asnMap.get(asnGr.getAssignment());
if (asnGradeRow != null) {
Assignment asnGrAssignment = asnGr.getAssignment();
// if weighted gb and no category for assignment,
// it is not counted toward course grade
boolean counted = asnGrAssignment.isCounted();
if (counted && getWeightingEnabled()) {
Category assignCategory = asnGrAssignment.getCategory();
if (assignCategory == null)
counted = false;
}
asnGradeRow.setGradeRecord(asnGr);
if (getGradeEntryByPercent())
asnGradeRow.setScore(truncateScore(asnGr.getPercentEarned()));
else if(getGradeEntryByPoints())
asnGradeRow.setScore(truncateScore(asnGr.getPointsEarned()));
else if (getGradeEntryByLetter())
asnGradeRow.setLetterScore(asnGr.getLetterEarned());
}
}
}
Map goEventListMap = getGradebookManager().getGradingEventsForStudent(studentUid, assignments);
// NOTE: we are no longer converting the events b/c we are
// storing what the user entered, not just points
//iterate through the assignments and update the comments and grading events
Iterator assignmentIterator = assignments.iterator();
while(assignmentIterator.hasNext()){
Assignment assignment = (Assignment) assignmentIterator.next();
AssignmentGradeRow asnGradeRow = (AssignmentGradeRow)asnMap.get(assignment);
// Grading events
if (isInstructorView) {
List assignEventList = new ArrayList();
if (goEventListMap != null) {
assignEventList = (List) goEventListMap.get(assignment);
}
if (assignEventList != null && !assignEventList.isEmpty()) {
List eventRows = new ArrayList();
for (Iterator iter = assignEventList.iterator(); iter.hasNext();) {
GradingEvent gradingEvent = (GradingEvent)iter.next();
eventRows.add(new GradingEventRow(gradingEvent));
}
asnGradeRow.setEventRows(eventRows);
asnGradeRow.setEventsLogTitle(getLocalizedString("inst_view_log_title", new String[] {getUserDisplayName()}));
}
}
// Comments
try{
Comment comment = (Comment)commentMap.get(asnGradeRow.getAssociatedAssignment().getId());
if(comment.getCommentText().length() > 0)
asnGradeRow.setCommentText(comment.getCommentText());
}catch(NullPointerException npe){
if(logger.isDebugEnabled())
logger.debug("assignment has no associated comment");
}
}
gradeRows = new ArrayList(asnMap.values());
//remove assignments that are not released
Iterator i = gradeRows.iterator();
while(i.hasNext()){
AssignmentGradeRow assignmentGradeRow = (AssignmentGradeRow)i.next();
if(!assignmentGradeRow.getAssociatedAssignment().isReleased() && !isInstructorView)
i.remove();
}
i = gradeRows.iterator();
GradebookExternalAssessmentService gext = getGradebookExternalAssessmentService();
Map<String, String> externalAssignments = null;
if (isInstructorView) {
Map<String, List<String>> visible = gext.getVisibleExternalAssignments(gradebook.getUid(), Arrays.asList(studentUid));
if (visible.containsKey(studentUid)) {
externalAssignments = new HashMap<String, String>();
for (String externalId : visible.get(studentUid)) {
//FIXME: Take one of the following options for consistency:
// 1. Strip off the appKey from the single-user query
// 2. Add a layer to the all-user return to identify the appKey
externalAssignments.put(externalId, "");
}
}
} else {
externalAssignments = gext.getExternalAssignmentsForCurrentUser(gradebook.getUid());
}
while (i.hasNext()) {
Assignment assignment = ((AssignmentGradeRow)i.next()).getAssociatedAssignment();
if (assignment.isExternallyMaintained() && !externalAssignments.containsKey(assignment.getExternalId())) {
i.remove();
}
}
if (!sortColumn.equals(Category.SORT_BY_WEIGHT)) {
Collections.sort(gradeRows, (Comparator)columnSortMap.get(sortColumn));
if(!sortAscending) {
Collections.reverse(gradeRows);
}
}
return gradeRows;
}
/**
* Sets up the grade/category rows for student
* @param userUid
* @param gradebook
*/
private void initializeStudentGradeData() {
// do not retrieve assignments if not displayed for students
if (assignmentsReleased || isInstructorView) {
//get grade comments and load them into a map assignmentId->comment
commentMap = new HashMap();
List assignmentComments = getGradebookManager().getStudentAssignmentComments(studentUid, gradebook.getId());
logger.debug("number of comments "+assignmentComments.size());
Iterator iteration = assignmentComments.iterator();
while (iteration.hasNext()){
Comment comment = (Comment)iteration.next();
commentMap.put(comment.getGradableObject().getId(),comment);
}
// get the student grade records
List gradeRecords = getGradebookManager().getStudentGradeRecordsConverted(gradebook.getId(), studentUid);
getGradebookManager().applyDropScores(gradeRecords);
// The display may include categories and assignments, so we need a generic list
gradebookItems = new ArrayList();
if (getCategoriesEnabled()) {
// we will also have to determine the student's category avg - the category stats
// are for class avg
List categoryListWithCG = new ArrayList();
if (sortColumn.equals(Category.SORT_BY_WEIGHT))
categoryListWithCG = getGradebookManager().getCategoriesWithStats(getGradebookId(), Assignment.DEFAULT_SORT, true, sortColumn, sortAscending);
else
categoryListWithCG = getGradebookManager().getCategoriesWithStats(getGradebookId(), Assignment.DEFAULT_SORT, true, Category.SORT_BY_NAME, true);
List categoryList = new ArrayList();
// first, remove the CourseGrade from the Category list
for (Iterator catIter = categoryListWithCG.iterator(); catIter.hasNext();) {
Object catOrCourseGrade = catIter.next();
if (catOrCourseGrade instanceof Category) {
categoryList.add((Category)catOrCourseGrade);
}
}
if (!isUserAbleToGradeAll() && isUserHasGraderPermissions()) {
//SAK-19896, eduservice's can't share the same "Category" class, so just pass the ID's
List<Long> catIds = new ArrayList<Long>();
for (Category category : (List<Category>) categoryList) {
catIds.add(category.getId());
}
List<Long> viewableCats = getGradebookPermissionService().getCategoriesForUserForStudentView(getGradebookId(), getUserUid(), studentUid, catIds, getGradebook().getCategory_type(), getViewableSectionIds());
List<Category> tmpCatList = new ArrayList<Category>();
for (Category category : (List<Category>) categoryList) {
if(viewableCats.contains(category.getId())){
tmpCatList.add(category);
}
}
categoryList = tmpCatList;
}
// first, we deal with the categories and their associated assignments
if (categoryList != null && !categoryList.isEmpty()) {
Comparator catComparator = null;
if(SORT_BY_POINTS_EARNED.equals(sortColumn)){
//need to figure out the weight for the student before you can order by this:
Iterator catIter = categoryList.iterator();
while (catIter.hasNext()) {
Object catObject = catIter.next();
if (catObject instanceof Category) {
Category category = (Category) catObject;
List catAssign = category.getAssignmentList();
if (catAssign != null && !catAssign.isEmpty()) {
// we want to create the grade rows for these assignments
category.calculateStatisticsPerStudent(gradeRecords, studentUid);
}
}
}
catComparator = Category.averageScoreComparator;
}else if(Category.SORT_BY_WEIGHT.equals(sortColumn)){
catComparator = Category.weightComparator;
}else if(Category.SORT_BY_NAME.equals(sortColumn)){
catComparator = Category.nameComparator;
}
if(catComparator != null){
Collections.sort(categoryList, catComparator);
if(!sortAscending){
Collections.reverse(categoryList);
}
}
Iterator catIter = categoryList.iterator();
while (catIter.hasNext()) {
Object catObject = catIter.next();
if (catObject instanceof Category) {
Category category = (Category) catObject;
gradebookItems.add(category);
List catAssign = category.getAssignmentList();
if (catAssign != null && !catAssign.isEmpty()) {
// we want to create the grade rows for these assignments
List gradeRows = retrieveGradeRows(catAssign, gradeRecords);
category.calculateStatisticsPerStudent(gradeRecords, studentUid);
if (gradeRows != null && !gradeRows.isEmpty()) {
gradebookItems.addAll(gradeRows);
}
}
}
}
}
// now we need to grab all of the assignments w/o a category
if (!isUserAbleToGradeAll() && (isUserHasGraderPermissions() && !getGradebookPermissionService().getPermissionForUserForAllAssignmentForStudent(getGradebookId(), getUserUid(), studentUid, getViewableSectionIds()))) {
// user is not authorized to view/grade the unassigned category for the current student
} else {
List assignNoCat = getGradebookManager().getAssignmentsWithNoCategory(getGradebookId(), Assignment.DEFAULT_SORT, true);
if (assignNoCat != null && !assignNoCat.isEmpty()) {
Category unassignedCat = new Category();
unassignedCat.setGradebook(gradebook);
unassignedCat.setName(getLocalizedString("cat_unassigned"));
unassignedCat.setAssignmentList(assignNoCat);
//add this category to our list
gradebookItems.add(unassignedCat);
// now create grade rows for the unassigned assignments
List gradeRows = retrieveGradeRows(assignNoCat, gradeRecords);
/* we display N/A for the category avg for unassigned category,
* so don't need to calc this anymore
if (!getWeightingEnabled())
unassignedCat.calculateStatisticsPerStudent(gradeRecords, studentUid);*/
if (gradeRows != null && !gradeRows.isEmpty()) {
gradebookItems.addAll(gradeRows);
}
}
}
} else {
// there are no categories, so we will be returning a list of grade rows
List assignList = getGradebookManager().getAssignments(getGradebookId());
if (assignList != null && !assignList.isEmpty()) {
List gradeRows = retrieveGradeRows(assignList, gradeRecords);
if (gradeRows != null && !gradeRows.isEmpty()) {
gradebookItems.addAll(gradeRows);
}
}
}
for(Iterator iter = gradebookItems.iterator(); iter.hasNext();) {
Object gradebookItem = iter.next();
if (gradebookItem instanceof AssignmentGradeRow) {
AssignmentGradeRow gr = (AssignmentGradeRow)gradebookItem;
if(gr.getAssociatedAssignment().isExternallyMaintained()) {
rowStyles.append("external");
anyExternallyMaintained = true;
} else {
rowStyles.append("internal");
}
}
if(iter.hasNext()) {
rowStyles.append(",");
}
}
}
}
}