package net.sourceforge.fenixedu.domain.degreeStructure; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import net.sourceforge.fenixedu.domain.CurricularCourse; import net.sourceforge.fenixedu.domain.DegreeCurricularPlan; import net.sourceforge.fenixedu.domain.ExecutionSemester; import net.sourceforge.fenixedu.domain.ExecutionYear; import net.sourceforge.fenixedu.domain.RootDomainObject; import net.sourceforge.fenixedu.domain.curricularPeriod.CurricularPeriod; import net.sourceforge.fenixedu.domain.curricularRules.CreditsLimit; import net.sourceforge.fenixedu.domain.curricularRules.CurricularRule; import net.sourceforge.fenixedu.domain.curricularRules.CurricularRuleType; import net.sourceforge.fenixedu.domain.curricularRules.DegreeModulesSelectionLimit; import net.sourceforge.fenixedu.domain.exceptions.DomainException; import net.sourceforge.fenixedu.util.StringFormatter; import org.apache.commons.collections.comparators.ReverseComparator; import pt.ist.fenixWebFramework.security.accessControl.Checked; public class CourseGroup extends CourseGroup_Base { static public List readCourseGroups() { final List result = new ArrayList(); for (final DegreeModule degreeModule : RootDomainObject.getInstance().getDegreeModules()) { if (degreeModule instanceof CourseGroup) { result.add((CourseGroup) degreeModule); } } return result; } protected CourseGroup() { super(); } protected CourseGroup(final String name, final String nameEn) { this(); init(name, nameEn); } protected void init(final String name, final String nameEn) { super.setName(StringFormatter.prettyPrint(name)); super.setNameEn(StringFormatter.prettyPrint(nameEn)); } public CourseGroup(final CourseGroup parentCourseGroup, final String name, final String nameEn, final ExecutionSemester begin, final ExecutionSemester end) { init(parentCourseGroup, name, nameEn, begin, end); } protected void init(CourseGroup parentCourseGroup, String name, String nameEn, ExecutionSemester begin, ExecutionSemester end) { init(name, nameEn); if (parentCourseGroup == null) { throw new DomainException("error.degreeStructure.CourseGroup.parentCourseGroup.cannot.be.null"); } parentCourseGroup.checkDuplicateChildNames(name, nameEn); new Context(parentCourseGroup, this, null, begin, end); } public boolean isLeaf() { return false; } public void edit(String name, String nameEn, Context context, ExecutionSemester beginExecutionPeriod, ExecutionSemester endExecutionPeriod) { // override, assure that root's name equals degree curricular plan name if (this.isRoot()) { setName(getParentDegreeCurricularPlan().getName()); setNameEn(getParentDegreeCurricularPlan().getName()); } else { setName(StringFormatter.prettyPrint(name)); setNameEn(StringFormatter.prettyPrint(nameEn)); } this.checkDuplicateBrotherNames(name, nameEn); if (!this.isRoot() && context != null) { context.edit(beginExecutionPeriod, endExecutionPeriod); } } public Boolean getCanBeDeleted() { return super.getCanBeDeleted() && !hasAnyChildContexts() && !hasAnyOldCourseGroupChangeRequests() && !hasAnyNewCourseGroupChangeRequests(); } public void delete() { if (getCanBeDeleted()) { super.delete(); for (; !getParticipatingContextCurricularRules().isEmpty(); getParticipatingContextCurricularRules().get(0).delete()) ; removeRootDomainObject(); super.deleteDomainObject(); } else { throw new DomainException("courseGroup.notEmptyCourseGroupContexts"); } } public void print(StringBuilder dcp, String tabs, Context previousContext) { String tab = tabs + "\t"; dcp.append(tab); dcp.append("[CG ").append(this.getIdInternal()).append("] ").append(this.getName()).append("\n"); for (Context context : this.getSortedChildContextsWithCurricularCourses()) { context.getChildDegreeModule().print(dcp, tab, context); } for (Context context : this.getSortedChildContextsWithCourseGroups()) { context.getChildDegreeModule().print(dcp, tab, context); } } public boolean isRoot() { return false; } @Override public DegreeCurricularPlan getParentDegreeCurricularPlan() { return hasAnyParentContexts() ? getParentContexts().get(0).getParentCourseGroup().getParentDegreeCurricularPlan() : null; } public List getChildContexts(Class clazz) { return getValidChildContexts(clazz, (ExecutionYear) null); } public List getValidChildContexts(final ExecutionYear executionYear) { return getValidChildContexts(null, executionYear); } public List getValidChildContexts(final ExecutionSemester executionSemester) { return getValidChildContexts(null, executionSemester); } // Valid means that is open to execution year, and if is // CurricularCourse // the context must have same semester of any ExecutionPeriod of // ExecutionYear public List getValidChildContexts(final Class clazz, final ExecutionYear executionYear) { final List result = new ArrayList(); for (final Context context : this.getChildContexts()) { if (hasClass(clazz, context.getChildDegreeModule()) && ((executionYear == null || context.isValid(executionYear)))) { result.add(context); } } return result; } // Valid means that is open to execution period, and if is // CurricularCourse // the context must have same semester than executionPeriod public List getValidChildContexts(final Class clazz, final ExecutionSemester executionSemester) { final List result = new ArrayList(); for (Context context : this.getChildContexts()) { if (hasClass(clazz, context.getChildDegreeModule()) && ((executionSemester == null || context.isValid(executionSemester)))) { result.add(context); } } return result; } public List getSortedOpenChildContextsWithCurricularCourses(final ExecutionYear executionYear) { final List result = getOpenChildContexts(CurricularCourse.class, executionYear); Collections.sort(result); return result; } public List getSortedOpenChildContextsWithCourseGroups(final ExecutionYear executionYear) { final List result = this.getOpenChildContexts(CourseGroup.class, executionYear); Collections.sort(result); return result; } public List getSortedOpenChildContextsWithCourseGroups(final ExecutionSemester executionSemester) { final List result = this.getOpenChildContexts(CourseGroup.class, executionSemester); Collections.sort(result); return result; } public List getOpenChildContexts(final Class clazz, final ExecutionSemester executionSemester) { final List result = new ArrayList(); for (final Context context : getChildContexts()) { if (hasClass(clazz, context.getChildDegreeModule()) && ((executionSemester == null || context.isOpen(executionSemester)))) { result.add(context); } } return result; } public List getOpenChildContexts(final Class clazz, final ExecutionYear executionYear) { final List result = new ArrayList(); for (final Context context : getChildContexts()) { if (hasClass(clazz, context.getChildDegreeModule()) && ((executionYear == null || context.isOpen(executionYear)))) { result.add(context); } } return result; } private boolean hasClass(final Class clazz, final DegreeModule degreeModule) { return clazz == null || clazz.isAssignableFrom(degreeModule.getClass()); } public List getSortedChildContextsWithCurricularCourses() { List result = this.getChildContexts(CurricularCourse.class); Collections.sort(result); return result; } public List getSortedChildContextsWithCurricularCoursesByExecutionYear(ExecutionYear executionYear) { List result = this.getValidChildContexts(CurricularCourse.class, executionYear); Collections.sort(result); return result; } public List getSortedChildContextsWithCourseGroups() { List result = new ArrayList(this.getChildContexts(CourseGroup.class)); Collections.sort(result); return result; } public List getSortedChildContextsWithCourseGroupsByExecutionYear(ExecutionYear executionYear) { List result = this.getValidChildContexts(CourseGroup.class, executionYear); Collections.sort(result); return result; } @Override public List getParticipatingCurricularRules() { final List result = new ArrayList(); result.addAll(super.getParticipatingCurricularRules()); result.addAll(getParticipatingContextCurricularRules()); return result; } @Override @Checked("CourseGroupPredicates.curricularPlanMemberWritePredicate") public void setName(String name) { super.setName(name); } @Override @Checked("CourseGroupPredicates.curricularPlanMemberWritePredicate") public void setNameEn(String nameEn) { super.setNameEn(nameEn); } public void checkDuplicateChildNames(final String name, final String nameEn) { String normalizedName = StringFormatter.normalize(name); String normalizedNameEn = StringFormatter.normalize(nameEn); if (!verifyNames(normalizedName, normalizedNameEn)) { throw new DomainException("error.existingCourseGroupWithSameName"); } } public void checkDuplicateBrotherNames(final String name, final String nameEn) { String normalizedName = StringFormatter.normalize(name); String normalizedNameEn = StringFormatter.normalize(nameEn); for (Context parentContext : getParentContexts()) { CourseGroup parentCourseGroup = parentContext.getParentCourseGroup(); if (!parentCourseGroup.verifyNames(normalizedName, normalizedNameEn, this)) { throw new DomainException("error.existingCourseGroupWithSameName"); } } } private boolean verifyNames(String normalizedName, String normalizedNameEn) { return verifyNames(normalizedName, normalizedNameEn, this); } private boolean verifyNames(String normalizedName, String normalizedNameEn, DegreeModule excludedModule) { for (Context context : getChildContexts()) { DegreeModule degreeModule = context.getChildDegreeModule(); if (degreeModule != excludedModule) { if (degreeModule.getName() != null && StringFormatter.normalize(degreeModule.getName()).equals(normalizedName)) { return false; } if (degreeModule.getNameEn() != null && StringFormatter.normalize(degreeModule.getNameEn()).equals(normalizedNameEn)) { return false; } } } return true; } public void orderChild(Context contextToOrder, int position) { List newSort = null; if (contextToOrder.getChildDegreeModule() instanceof CurricularCourse) { newSort = this.getSortedChildContextsWithCurricularCourses(); } else { newSort = this.getSortedChildContextsWithCourseGroups(); } if (newSort.size() <= 1 || position < 0 || position > newSort.size()) { return; } newSort.remove(contextToOrder); newSort.add(position, contextToOrder); for (int newOrder = 0; newOrder < newSort.size(); newOrder++) { Context context = newSort.get(newOrder); if (context == contextToOrder && newOrder != position) { throw new DomainException("wrong.order.algorithm"); } context.setChildOrder(newOrder); } } public Set collectAllChildDegreeModules(final Class clazz, final ExecutionYear executionYear) { final Set result = new HashSet(); for (final Context context : this.getValidChildContexts(executionYear)) { final DegreeModule degreeModule = context.getChildDegreeModule(); if (clazz.isAssignableFrom(degreeModule.getClass())) { result.add(degreeModule); } if (!degreeModule.isLeaf()) { final CourseGroup courseGroup = (CourseGroup) degreeModule; result.addAll(courseGroup.collectAllChildDegreeModules(clazz, executionYear)); } } return result; } public Set collectAllChildDegreeModules(final Class clazz, final ExecutionSemester executionSemester) { final Set result = new HashSet(); for (final Context context : getValidChildContexts(executionSemester)) { final DegreeModule degreeModule = context.getChildDegreeModule(); if (clazz.isAssignableFrom(degreeModule.getClass())) { result.add(degreeModule); } if (!degreeModule.isLeaf()) { final CourseGroup courseGroup = (CourseGroup) degreeModule; result.addAll(courseGroup.collectAllChildDegreeModules(clazz, executionSemester)); } } return result; } public void collectChildDegreeModulesIncludingFullPath(Class clazz, List> result, List previousDegreeModulesPath, ExecutionYear executionYear) { final List currentDegreeModulesPath = previousDegreeModulesPath; for (final Context context : this.getValidChildContexts(executionYear)) { List newDegreeModulesPath = null; if (clazz.isAssignableFrom(context.getChildDegreeModule().getClass())) { newDegreeModulesPath = initNewDegreeModulesPath(newDegreeModulesPath, currentDegreeModulesPath, context .getChildDegreeModule()); result.add(newDegreeModulesPath); } if (!context.getChildDegreeModule().isLeaf()) { newDegreeModulesPath = initNewDegreeModulesPath(newDegreeModulesPath, currentDegreeModulesPath, context .getChildDegreeModule()); ((CourseGroup) context.getChildDegreeModule()).collectChildDegreeModulesIncludingFullPath(clazz, result, newDegreeModulesPath, executionYear); } } } private List initNewDegreeModulesPath(List newDegreeModulesPath, final List currentDegreeModulesPath, final DegreeModule degreeModule) { if (newDegreeModulesPath == null) { newDegreeModulesPath = new ArrayList(currentDegreeModulesPath); newDegreeModulesPath.add(degreeModule); } return newDegreeModulesPath; } public Collection getNotOptionalChildCourseGroups(final ExecutionSemester executionSemester) { final Collection degreeModules = getDegreeModulesByExecutionPeriod(executionSemester); final Collection curricularRules = getCurricularRulesByExecutionPeriod(executionSemester); final DegreeModulesSelectionLimit degreeModulesSelectionLimit = getDegreeModulesSelectionLimitRule(curricularRules); if (degreeModulesSelectionLimit != null) { if (degreeModulesSelectionLimit.getMinimumLimit().equals(degreeModulesSelectionLimit.getMaximumLimit()) && degreeModulesSelectionLimit.getMaximumLimit().equals(degreeModules.size())) { return filterCourseGroups(degreeModules); } else { return Collections.EMPTY_LIST; } } return filterCourseGroups(degreeModules); } private Collection filterCourseGroups(final Collection degreeModules) { final Collection result = new HashSet(); for (final DegreeModule degreeModule : degreeModules) { if (!degreeModule.isLeaf()) { result.add((CourseGroup) degreeModule); } } return result; } private DegreeModulesSelectionLimit getDegreeModulesSelectionLimitRule(final Collection curricularRules) { for (final CurricularRule curricularRule : curricularRules) { if (curricularRule.getCurricularRuleType() == CurricularRuleType.DEGREE_MODULES_SELECTION_LIMIT) { return (DegreeModulesSelectionLimit) curricularRule; } } return null; } private Collection getCurricularRulesByExecutionPeriod(final ExecutionSemester executionSemester) { final Collection result = new HashSet(); for (final CurricularRule curricularRule : this.getCurricularRulesSet()) { if (curricularRule.isValid(executionSemester)) { result.add(curricularRule); } } return result; } private Collection getDegreeModulesByExecutionPeriod(final ExecutionSemester executionSemester) { final Collection result = new HashSet(); for (final Context context : this.getChildContexts()) { if (context.isValid(executionSemester)) { result.add(context.getChildDegreeModule()); } } return result; } public boolean validate(CurricularCourse curricularCourse) { for (final Context context : this.getChildContextsSet()) { if (context.getChildDegreeModule() instanceof CurricularCourse) { CurricularCourse childCurricularCourse = (CurricularCourse) context.getChildDegreeModule(); if (childCurricularCourse.isEquivalent(curricularCourse)) { return true; } } } return false; } public Collection getContextsWithCurricularCourseByCurricularPeriod(final CurricularPeriod curricularPeriod, final ExecutionSemester executionSemester) { final Collection result = new HashSet(); for (final Context context : this.getChildContextsSet()) { if (context.getChildDegreeModule().isLeaf() && context.hasCurricularPeriod() && context.getCurricularPeriod().equals(curricularPeriod) && context.isValid(executionSemester)) { result.add(context); } } return result; } public Set getOpenChildDegreeModulesByExecutionPeriod(final ExecutionSemester executionSemester) { final Set result = new HashSet(); for (final Context context : getChildContexts()) { if (context.isOpen(executionSemester)) { result.add(context.getChildDegreeModule()); } } return result; } public Set getParentCourseGroups() { final Set result = new HashSet(); for (final Context context : getParentContexts()) { result.add(context.getParentCourseGroup()); } return result; } @Override public Double getMaxEctsCredits(final ExecutionSemester executionSemester) { final List creditsLimitRules = (List) getCurricularRules(CurricularRuleType.CREDITS_LIMIT, executionSemester); if (!creditsLimitRules.isEmpty()) { for (final CreditsLimit creditsLimit : creditsLimitRules) { if (getParentCourseGroups().contains(creditsLimit.getContextCourseGroup())) { return creditsLimit.getMaximumCredits(); } } return creditsLimitRules.get(0).getMaximumCredits(); } final Collection modulesByExecutionPeriod = getOpenChildDegreeModulesByExecutionPeriod(executionSemester); final DegreeModulesSelectionLimit modulesSelectionLimit = getDegreeModulesSelectionLimitRule(executionSemester); if (modulesSelectionLimit != null) { return countMaxEctsCredits(modulesByExecutionPeriod, executionSemester, modulesSelectionLimit.getMaximumLimit()); } return countMaxEctsCredits(modulesByExecutionPeriod, executionSemester, modulesByExecutionPeriod.size()); } private Double countMaxEctsCredits(final Collection modulesByExecutionPeriod, final ExecutionSemester executionSemester, final Integer maximumLimit) { final List ectsCredits = new ArrayList(); for (final DegreeModule degreeModule : modulesByExecutionPeriod) { ectsCredits.add(degreeModule.getMaxEctsCredits(executionSemester)); } Collections.sort(ectsCredits, new ReverseComparator()); return sumEctsCredits(ectsCredits, maximumLimit.intValue()); } @Override public Double getMinEctsCredits(final ExecutionSemester executionSemester) { final List creditsLimitRules = (List) getCurricularRules(CurricularRuleType.CREDITS_LIMIT, executionSemester); if (!creditsLimitRules.isEmpty()) { for (final CreditsLimit creditsLimit : creditsLimitRules) { if (getParentCourseGroups().contains(creditsLimit.getContextCourseGroup())) { return creditsLimit.getMinimumCredits(); } } return creditsLimitRules.get(0).getMinimumCredits(); } final Collection modulesByExecutionPeriod = getOpenChildDegreeModulesByExecutionPeriod(executionSemester); final DegreeModulesSelectionLimit modulesSelectionLimit = getDegreeModulesSelectionLimitRule(executionSemester); if (modulesSelectionLimit != null) { return countMinEctsCredits(modulesByExecutionPeriod, executionSemester, modulesSelectionLimit.getMinimumLimit()); } return countMinEctsCredits(modulesByExecutionPeriod, executionSemester, modulesByExecutionPeriod.size()); } private Double countMinEctsCredits(final Collection modulesByExecutionPeriod, final ExecutionSemester executionSemester, final Integer minimumLimit) { final List ectsCredits = new ArrayList(); for (final DegreeModule degreeModule : modulesByExecutionPeriod) { ectsCredits.add(degreeModule.getMinEctsCredits(executionSemester)); } Collections.sort(ectsCredits); return sumEctsCredits(ectsCredits, minimumLimit.intValue()); } private Double sumEctsCredits(final List ectsCredits, int limit) { double result = 0d; final Iterator ectsCreditsIter = ectsCredits.iterator(); for (; ectsCreditsIter.hasNext() && limit > 0; limit--) { result += ectsCreditsIter.next().doubleValue(); } return Double.valueOf(result); } @Override public boolean hasDegreeModule(final DegreeModule degreeModule) { if (super.hasDegreeModule(degreeModule)) { return true; } for (final Context context : getChildContexts()) { if (context.getChildDegreeModule().hasDegreeModule(degreeModule)) { return true; } } return false; } public Context addCurricularCourse(final CurricularCourse curricularCourse, final CurricularPeriod curricularPeriod, final ExecutionSemester begin, final ExecutionSemester end) { return addContext(curricularCourse, curricularPeriod, begin, end); } public Context addContext(final DegreeModule degreeModule, final CurricularPeriod curricularPeriod, final ExecutionSemester begin, final ExecutionSemester end) { if (!allowChildWith(begin)) { throw new DomainException("degreeModule.cannot.add.context.with.begin.execution.period", getName(), begin.getName(), begin.getExecutionYear().getYear()); } return new Context(this, degreeModule, curricularPeriod, begin, end); } @Override public void getAllDegreeModules(final Collection degreeModules) { degreeModules.add(this); for (Context context : getChildContexts()) { context.getAllDegreeModules(degreeModules); } } public void getAllCoursesGroupse(final Set courseGroups) { for (final Context context : getChildContextsSet()) { context.addAllCourseGroups(courseGroups); } } public boolean allowChildWith(final ExecutionSemester executionSemester) { return getMinimumExecutionPeriod().isBeforeOrEquals(executionSemester); } public Set getChildContextsSortedByDegreeModuleName() { final Set contexts = new TreeSet(Context.COMPARATOR_BY_DEGREE_MODULE_NAME); contexts.addAll(getChildContextsSet()); return contexts; } public Set getChildDegreeModules() { final Set result = new HashSet(); for (final Context context : getChildContexts()) { result.add(context.getChildDegreeModule()); } return result; } public Set getChildDegreeModulesValidOn(final ExecutionSemester executionSemester) { final Set result = new HashSet(); for (final Context context : getValidChildContexts(executionSemester)) { result.add(context.getChildDegreeModule()); } return result; } public Set getChildDegreeModulesValidOn(final ExecutionYear executionYear) { final Set result = new HashSet(); for (final Context context : getValidChildContexts(executionYear)) { result.add(context.getChildDegreeModule()); } return result; } public Set getActiveChildContexts() { final Set result = new HashSet(); for (final Context context : getChildContexts()) { if (context.isOpen()) { result.add(context); } } return result; } public Set getActiveChildContextsWithMax(final ExecutionSemester executionSemester) { final Map maxContextsByDegreeModule = new HashMap(); for (final Context context : getActiveChildContexts()) { if (maxContextsByDegreeModule.containsKey(context.getChildDegreeModule())) { final Context existingContext = maxContextsByDegreeModule.get(context.getChildDegreeModule()); if (existingContext.getCurricularPeriod().getChildOrder().intValue() != executionSemester.getSemester() .intValue() && context.getCurricularPeriod().getChildOrder().intValue() == executionSemester.getSemester().intValue()) { maxContextsByDegreeModule.put(context.getChildDegreeModule(), context); } } else { maxContextsByDegreeModule.put(context.getChildDegreeModule(), context); } } return new HashSet(maxContextsByDegreeModule.values()); } public Map> getActiveChildCurricularContextsWithMaxByCurricularPeriod( final ExecutionSemester executionSemester) { final Map> result = new HashMap>(); for (final Context context : getActiveChildContextsWithMax(executionSemester)) { if (context.getChildDegreeModule().isCurricularCourse()) { if (!result.containsKey(context.getCurricularPeriod())) { result.put(context.getCurricularPeriod(), new HashSet()); } result.get(context.getCurricularPeriod()).add(context); } } return result; } public Set getChildCurricularCoursesValidOn(final ExecutionSemester executionSemester) { final Set result = new HashSet(); for (final Context context : getValidChildContexts(executionSemester)) { if (context.getChildDegreeModule().isCurricularCourse()) { result.add((CurricularCourse) context.getChildDegreeModule()); } } return result; } public List getChildContextsForCurricularCourses(final ExecutionSemester executionSemester) { final List result = new ArrayList(); for (final Context context : getChildContexts(CurricularCourse.class)) { if (context.isValid(executionSemester)) { result.add(context); } } return result; } public Set getActiveChildContextsWithMaxCurricularPeriodForCurricularCourses( final ExecutionSemester executionSemester) { final Set result = new HashSet(); for (final Context context : getActiveChildContextsWithMax(executionSemester)) { if (context.getChildDegreeModule().isCurricularCourse()) { result.add(context); } } return result; } public boolean hasDegreeModuleOnChilds(final DegreeModule degreeModuleToSearch) { for (final Context context : getChildContexts()) { if (context.getChildDegreeModule() == degreeModuleToSearch) { return true; } } return false; } public boolean hasAnyChildContextWithCurricularCourse() { for (final Context context : getChildContextsSet()) { if (context.getChildDegreeModule().isCurricularCourse()) { return true; } } return false; } @Override public boolean isCourseGroup() { return true; } @Override public Set getAllCurricularCourses(final ExecutionSemester executionSemester) { final Set result = new HashSet(); for (final Context context : getChildContexts()) { if (executionSemester == null || context.isOpen(executionSemester)) { result.addAll(context.getChildDegreeModule().getAllCurricularCourses(executionSemester)); } } return result; } @Override public Set getAllCurricularCourses() { return getAllCurricularCourses(null); } public Set getAllOpenCurricularCourses() { return getAllCurricularCourses(ExecutionSemester.readActualExecutionSemester()); } public Set getBeginContextExecutionYears() { final Set result = new HashSet(); for (final Context context : getChildContexts(CourseGroup.class)) { result.add(context.getBeginExecutionPeriod().getExecutionYear()); result.addAll(((CourseGroup) context.getChildDegreeModule()).getBeginContextExecutionYears()); } return result; } @Override public void doForAllCurricularCourses(final CurricularCourseFunctor curricularCourseFunctor) { for (final Context context : getChildContexts()) { final DegreeModule degreeModule = context.getChildDegreeModule(); degreeModule.doForAllCurricularCourses(curricularCourseFunctor); if (!curricularCourseFunctor.keepDoing()) { return; } } } }