/** * Copyright © 2002 Instituto Superior Técnico * * This file is part of FenixEdu Academic. * * FenixEdu Academic is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * FenixEdu Academic is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with FenixEdu Academic. If not, see <http://www.gnu.org/licenses/>. */ package org.fenixedu.academic.domain.degreeStructure; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.apache.commons.collections.Predicate; import org.fenixedu.academic.domain.CurricularCourse; import org.fenixedu.academic.domain.Degree; import org.fenixedu.academic.domain.DegreeCurricularPlan; import org.fenixedu.academic.domain.EquivalencePlan; import org.fenixedu.academic.domain.EquivalencePlanEntry; import org.fenixedu.academic.domain.ExecutionDegree; import org.fenixedu.academic.domain.ExecutionInterval; import org.fenixedu.academic.domain.ExecutionSemester; import org.fenixedu.academic.domain.ExecutionYear; import org.fenixedu.academic.domain.curricularRules.CreditsLimit; import org.fenixedu.academic.domain.curricularRules.CurricularRule; import org.fenixedu.academic.domain.curricularRules.CurricularRuleType; import org.fenixedu.academic.domain.curricularRules.DegreeModulesSelectionLimit; import org.fenixedu.academic.domain.curricularRules.Exclusiveness; import org.fenixedu.academic.domain.curricularRules.ICurricularRule; import org.fenixedu.academic.domain.degree.DegreeType; import org.fenixedu.academic.domain.exceptions.DomainException; import org.fenixedu.academic.util.MultiLanguageString; import org.fenixedu.bennu.core.domain.Bennu; abstract public class DegreeModule extends DegreeModule_Base { static final public Comparator<DegreeModule> COMPARATOR_BY_NAME = Comparator.<DegreeModule, MultiLanguageString> comparing( DegreeModule::getNameI18N).thenComparing(DegreeModule::getExternalId); public static class ComparatorByMinEcts implements Comparator<DegreeModule> { private final ExecutionSemester executionSemester; public ComparatorByMinEcts(final ExecutionSemester executionSemester) { this.executionSemester = executionSemester; } @Override public int compare(DegreeModule leftDegreeModule, DegreeModule rightDegreeModule) { int comparationResult = leftDegreeModule.getMinEctsCredits(this.executionSemester).compareTo( rightDegreeModule.getMinEctsCredits(this.executionSemester)); return (comparationResult == 0) ? leftDegreeModule.getExternalId().compareTo(rightDegreeModule.getExternalId()) : comparationResult; } } public DegreeModule() { super(); setRootDomainObject(Bennu.getInstance()); } /** * We need a method to return a full name of a course group - from it's * parent course group to the degree curricular plan's root course group. * * Given this is impossible, for there are many routes from the root course * group to one particular course group, we choose (for now...) to get one * possible full name, always visiting the first element of every list of * contexts on our way to the root course group. * * @param result builder for with one possible full name of this course group * @param executionSemester */ protected void getOneFullName(final StringBuilder result, final ExecutionSemester executionSemester) { final String selfName = getNameI18N(executionSemester).getContent(); if (isRoot()) { result.append(selfName); } else { Collection<Context> parentContextsByExecutionPeriod = getParentContextsByExecutionSemester(executionSemester); if (parentContextsByExecutionPeriod.isEmpty()) { // if not existing, just return all (as previous implementation // of method parentContextsByExecutionPeriod = getParentContextsSet(); } final CourseGroup parentCourseGroup = parentContextsByExecutionPeriod.iterator().next().getParentCourseGroup(); parentCourseGroup.getOneFullName(result, executionSemester); result.append(FULL_NAME_SEPARATOR); result.append(selfName); } } private static final String FULL_NAME_SEPARATOR = " > "; public String getOneFullName(final ExecutionSemester executionSemester) { final StringBuilder result = new StringBuilder(); getOneFullName(result, executionSemester); return result.toString(); } public String getOneFullName() { return getOneFullName(null); } public String getOneFullNameI18N(final Locale language) { return getOneFullNameI18N(null, language); } public String getOneFullNameI18N(final ExecutionSemester executionSemester, Locale language) { final StringBuilder result = new StringBuilder(); getOneFullNameI18N(result, executionSemester, language); return result.toString(); } protected void getOneFullNameI18N(final StringBuilder result, final ExecutionSemester executionSemester, final Locale language) { final String selfName = getNameI18N(executionSemester).getContent(language); if (isRoot()) { result.append(selfName); } else { Collection<Context> parentContextsByExecutionPeriod = getParentContextsByExecutionSemester(executionSemester); if (parentContextsByExecutionPeriod.isEmpty()) { // if not existing, just return all (as previous implementation // of method parentContextsByExecutionPeriod = getParentContextsSet(); } final CourseGroup parentCourseGroup = parentContextsByExecutionPeriod.iterator().next().getParentCourseGroup(); parentCourseGroup.getOneFullNameI18N(result, executionSemester, language); result.append(FULL_NAME_SEPARATOR); result.append(selfName); } } public MultiLanguageString getNameI18N(final ExecutionSemester executionSemester) { MultiLanguageString multiLanguageString = new MultiLanguageString(); String name = getName(executionSemester); if (name != null && name.length() > 0) { multiLanguageString = multiLanguageString.with(MultiLanguageString.pt, name); } String nameEn = getNameEn(executionSemester); if (nameEn != null && nameEn.length() > 0) { multiLanguageString = multiLanguageString.with(MultiLanguageString.en, nameEn); } return multiLanguageString; } public MultiLanguageString getNameI18N(final ExecutionYear executionYear) { return getNameI18N((executionYear == null) ? null : executionYear.getLastExecutionPeriod()); } public MultiLanguageString getNameI18N() { return getNameI18N((ExecutionSemester) null); } protected String getName(final ExecutionSemester executionSemester) { return getName(); } protected String getNameEn(final ExecutionSemester executionSemester) { return getNameEn(); } public MultiLanguageString getNameI18N(ExecutionInterval executionInterval) { if (executionInterval instanceof ExecutionSemester) { return getNameI18N((ExecutionSemester) executionInterval); } if (executionInterval instanceof ExecutionYear) { return getNameI18N((ExecutionYear) executionInterval); } if (executionInterval == null) { return getNameI18N((ExecutionSemester) null); } throw new DomainException("error.DegreeModule.getNameI18N.does.not.support.provided.executionInterval.type", executionInterval.getClass().getName()); } public void delete() { if (getCanBeDeleted()) { for (; !getParentContextsSet().isEmpty(); getParentContextsSet().iterator().next().delete()) { ; } for (; !getCurricularRulesSet().isEmpty(); getCurricularRulesSet().iterator().next().delete()) { ; } for (; !getParticipatingPrecedenceCurricularRulesSet().isEmpty(); getParticipatingPrecedenceCurricularRulesSet() .iterator().next().delete()) { ; } for (; !getParticipatingExclusivenessCurricularRulesSet().isEmpty(); getParticipatingExclusivenessCurricularRulesSet() .iterator().next().delete()) { ; } } else { throw new DomainException("courseGroup.notEmptyCurriculumModules"); } } protected Boolean getCanBeDeleted() { return getCurriculumModulesSet().isEmpty(); } public void deleteContext(Context context) { if (getParentContextsSet().contains(context)) { context.delete(); } if (getParentContextsSet().isEmpty()) { delete(); } } public Set<CourseGroup> getAllParentCourseGroups() { Set<CourseGroup> result = new HashSet<CourseGroup>(); collectParentCourseGroups(result, this); return result; } private void collectParentCourseGroups(Set<CourseGroup> result, DegreeModule module) { for (Context parent : module.getParentContextsSet()) { if (!parent.getParentCourseGroup().isRoot()) { result.add(parent.getParentCourseGroup()); collectParentCourseGroups(result, parent.getParentCourseGroup()); } } } public List<CurricularRule> getParticipatingCurricularRules() { final List<CurricularRule> result = new ArrayList<CurricularRule>(); result.addAll(getParticipatingPrecedenceCurricularRulesSet()); result.addAll(getParticipatingExclusivenessCurricularRulesSet()); return result; } public List<CurricularRule> getCurricularRules(final ExecutionYear executionYear) { final List<CurricularRule> result = new ArrayList<CurricularRule>(); for (final CurricularRule curricularRule : this.getCurricularRulesSet()) { if (isCurricularRuleValid(curricularRule, executionYear)) { result.add(curricularRule); } } return result; } public List<CurricularRule> getCurricularRules(final ExecutionSemester executionSemester) { final List<CurricularRule> result = new ArrayList<CurricularRule>(); for (final CurricularRule curricularRule : this.getCurricularRulesSet()) { if (isCurricularRuleValid(curricularRule, executionSemester)) { result.add(curricularRule); } } return result; } public List<CurricularRule> getVisibleCurricularRules(final ExecutionYear executionYear) { final List<CurricularRule> result = new ArrayList<CurricularRule>(); for (final CurricularRule curricularRule : this.getCurricularRulesSet()) { if (curricularRule.isVisible() && (executionYear == null || curricularRule.isValid(executionYear))) { result.add(curricularRule); } } return result; } public List<CurricularRule> getVisibleCurricularRules(final ExecutionSemester executionSemester) { final List<CurricularRule> result = new ArrayList<CurricularRule>(); for (final CurricularRule curricularRule : this.getCurricularRulesSet()) { if (curricularRule.isVisible() && isCurricularRuleValid(curricularRule, executionSemester)) { result.add(curricularRule); } } return result; } public List<CurricularRule> getCurricularRules(final Context context, final ExecutionSemester executionSemester) { final List<CurricularRule> result = new ArrayList<CurricularRule>(); for (final CurricularRule curricularRule : getCurricularRulesSet()) { if (isCurricularRuleValid(curricularRule, executionSemester) && curricularRule.appliesToContext(context)) { result.add(curricularRule); } } return result; } private boolean isCurricularRuleValid(final ICurricularRule curricularRule, final ExecutionSemester executionSemester) { return executionSemester == null || curricularRule.isValid(executionSemester); } private boolean isCurricularRuleValid(final ICurricularRule curricularRule, final ExecutionYear executionYear) { return executionYear == null || curricularRule.isValid(executionYear); } public List<Context> getParentContextsByExecutionYear(final ExecutionYear executionYear) { final List<Context> result = new ArrayList<Context>(); for (final Context context : getParentContextsSet()) { if (executionYear == null || context.isValid(executionYear)) { result.add(context); } } return result; } public List<Context> getParentContextsByExecutionSemester(final ExecutionSemester executionSemester) { final List<Context> result = new ArrayList<Context>(); for (final Context context : getParentContextsSet()) { if (executionSemester == null || context.isValid(executionSemester)) { result.add(context); } } return result; } public List<Context> getParentContextsBy(final ExecutionSemester executionSemester, final CourseGroup parentCourseGroup) { final List<Context> result = new ArrayList<Context>(); for (final Context context : getParentContextsSet()) { if (context.isValid(executionSemester) && context.getParentCourseGroup() == parentCourseGroup) { result.add(context); } } return result; } public boolean hasAnyParentContexts(final ExecutionSemester executionSemester) { for (final Context context : getParentContextsSet()) { if (executionSemester == null || context.isValid(executionSemester)) { return true; } } return false; } public boolean hasAnyOpenParentContexts(final ExecutionSemester executionSemester) { for (final Context context : getParentContextsSet()) { if (executionSemester == null || context.isOpen(executionSemester)) { return true; } } return false; } public boolean isBolonhaDegree() { return getParentDegreeCurricularPlan().isBolonhaDegree(); } public boolean isOptional() { return false; } public boolean isBranchCourseGroup() { return false; } public boolean isCycleCourseGroup() { return false; } public boolean isCurricularCourse() { return false; } public boolean isCourseGroup() { return false; } public Degree getDegree() { return getParentDegreeCurricularPlan().getDegree(); } public DegreeType getDegreeType() { return getDegree().getDegreeType(); } public boolean hasOnlyOneParentCourseGroup() { return hasOnlyOneParentCourseGroup(null); } public boolean hasOnlyOneParentCourseGroup(final ExecutionSemester executionSemester) { DegreeModule degreeModule = null; for (final Context context : getParentContextsByExecutionSemester(executionSemester)) { if (degreeModule == null) { degreeModule = context.getParentCourseGroup(); } else if (degreeModule != context.getParentCourseGroup()) { return false; } } return true; } public List<? extends ICurricularRule> getCurricularRules(final CurricularRuleType ruleType, final ExecutionSemester executionSemester) { final List<ICurricularRule> result = new ArrayList<ICurricularRule>(); for (final ICurricularRule curricularRule : getCurricularRulesSet()) { if (curricularRule.hasCurricularRuleType(ruleType) && isCurricularRuleValid(curricularRule, executionSemester)) { result.add(curricularRule); } } return result; } public boolean hasAnyCurricularRules(final CurricularRuleType ruleType, final ExecutionSemester executionSemester) { for (final ICurricularRule curricularRule : getCurricularRulesSet()) { if (curricularRule.hasCurricularRuleType(ruleType) && isCurricularRuleValid(curricularRule, executionSemester)) { return true; } } return false; } public List<? extends ICurricularRule> getCurricularRules(final CurricularRuleType ruleType, final ExecutionYear executionYear) { final List<ICurricularRule> result = new ArrayList<ICurricularRule>(); for (final ICurricularRule curricularRule : getCurricularRulesSet()) { if (curricularRule.hasCurricularRuleType(ruleType) && isCurricularRuleValid(curricularRule, executionYear)) { result.add(curricularRule); } } return result; } public List<? extends ICurricularRule> getCurricularRules(final CurricularRuleType ruleType, final CourseGroup parentCourseGroup, final ExecutionYear executionYear) { final List<ICurricularRule> result = new ArrayList<ICurricularRule>(); for (final ICurricularRule curricularRule : getCurricularRulesSet()) { if (curricularRule.hasCurricularRuleType(ruleType) && isCurricularRuleValid(curricularRule, executionYear) && curricularRule.appliesToCourseGroup(parentCourseGroup)) { result.add(curricularRule); } } return result; } public List<? extends ICurricularRule> getCurricularRules(final CurricularRuleType ruleType, final CourseGroup parentCourseGroup, final ExecutionSemester executionSemester) { final List<ICurricularRule> result = new ArrayList<ICurricularRule>(); for (final ICurricularRule curricularRule : getCurricularRulesSet()) { if (curricularRule.hasCurricularRuleType(ruleType) && isCurricularRuleValid(curricularRule, executionSemester) && curricularRule.appliesToCourseGroup(parentCourseGroup)) { result.add(curricularRule); } } return result; } public ICurricularRule getMostRecentActiveCurricularRule(final CurricularRuleType ruleType, final CourseGroup parentCourseGroup, final ExecutionYear executionYear) { final List<ICurricularRule> curricularRules = new ArrayList<ICurricularRule>(getCurricularRules(ruleType, parentCourseGroup, (ExecutionYear) null)); Collections.sort(curricularRules, ICurricularRule.COMPARATOR_BY_BEGIN); if (curricularRules.isEmpty()) { return null; } if (executionYear == null) { final ListIterator<ICurricularRule> iter = curricularRules.listIterator(curricularRules.size()); while (iter.hasPrevious()) { final ICurricularRule curricularRule = iter.previous(); if (curricularRule.isActive()) { return curricularRule; } } return null; } ICurricularRule result = null; for (final ICurricularRule curricularRule : curricularRules) { if (curricularRule.isValid(executionYear)) { if (result != null) { // TODO: remove this throw when curricular rule ensures // that it can be only one active for execution period // and replace by: return curricularRule throw new DomainException("error.degree.module.has.more.than.one.credits.limit.for.executionYear", getName()); } result = curricularRule; } } return result; } public ICurricularRule getMostRecentActiveCurricularRule(final CurricularRuleType ruleType, final CourseGroup parentCourseGroup, final ExecutionSemester executionSemester) { final List<ICurricularRule> curricularRules = new ArrayList<ICurricularRule>(getCurricularRules(ruleType, parentCourseGroup, executionSemester)); if (curricularRules.isEmpty()) { return null; } ICurricularRule result = null; for (final ICurricularRule curricularRule : curricularRules) { if (curricularRule.isValid(executionSemester)) { if (result != null) { // TODO: remove this throw when curricular rule ensures // that it can be only one active for execution period // and replace by: return curricularRule throw new DomainException("error.degree.module.has.more.than.one.credits.limit.for.executionPeriod", getName()); } result = curricularRule; } } return result; } public Double getMaxEctsCredits() { return getMaxEctsCredits(ExecutionSemester.readActualExecutionSemester()); } public Double getMinEctsCredits() { return getMinEctsCredits(ExecutionSemester.readActualExecutionSemester()); } public boolean hasDegreeModule(final DegreeModule degreeModule) { return this.equals(degreeModule); } public ExecutionSemester getMinimumExecutionPeriod() { if (isRoot()) { return isBolonhaDegree() ? getBeginBolonhaExecutionPeriod() : getFirstExecutionPeriodOfFirstExecutionDegree(); } final SortedSet<ExecutionSemester> executionSemesters = new TreeSet<ExecutionSemester>(); for (final Context context : getParentContextsSet()) { executionSemesters.add(context.getBeginExecutionPeriod()); } return executionSemesters.first(); } public ExecutionSemester getBeginBolonhaExecutionPeriod() { return ExecutionSemester.readFirstBolonhaExecutionPeriod(); } private ExecutionSemester getFirstExecutionPeriodOfFirstExecutionDegree() { final ExecutionDegree executionDegree = getParentDegreeCurricularPlan().getFirstExecutionDegree(); return executionDegree != null ? executionDegree.getExecutionYear().getFirstExecutionPeriod() : getBeginBolonhaExecutionPeriod(); } public DegreeModulesSelectionLimit getDegreeModulesSelectionLimitRule(final ExecutionSemester executionSemester) { final List<DegreeModulesSelectionLimit> result = (List<DegreeModulesSelectionLimit>) getCurricularRules(CurricularRuleType.DEGREE_MODULES_SELECTION_LIMIT, executionSemester); return result.isEmpty() ? null : (DegreeModulesSelectionLimit) result.iterator().next(); } public CreditsLimit getCreditsLimitRule(final ExecutionSemester executionSemester) { final List<? extends ICurricularRule> result = getCurricularRules(CurricularRuleType.CREDITS_LIMIT, executionSemester); return result.isEmpty() ? null : (CreditsLimit) result.iterator().next(); } public List<Exclusiveness> getExclusivenessRules(final ExecutionSemester executionSemester) { return (List<Exclusiveness>) getCurricularRules(CurricularRuleType.EXCLUSIVENESS, executionSemester); } public Set<EquivalencePlanEntry> getNewDegreeModuleEquivalencePlanEntries(final EquivalencePlan equivalencePlan) { final Set<EquivalencePlanEntry> equivalencePlanEntries = new TreeSet<EquivalencePlanEntry>(EquivalencePlanEntry.COMPARATOR); for (final EquivalencePlanEntry equivalencePlanEntry : getNewEquivalencePlanEntriesSet()) { if (equivalencePlanEntry.getEquivalencePlan() == equivalencePlan) { equivalencePlanEntries.add(equivalencePlanEntry); } } return equivalencePlanEntries; } public Collection<CycleCourseGroup> getParentCycleCourseGroups() { final Collection<CycleCourseGroup> res = new HashSet<CycleCourseGroup>(); getParentContextsSet().forEach(c -> res.addAll(c.getParentCourseGroup().getParentCycleCourseGroups())); return res; } /** * @deprecated use getParentContextsSet instead */ @Deprecated public Set<CourseGroup> getParentCourseGroups() { Set<CourseGroup> res = new HashSet<CourseGroup>(); for (Context context : getParentContextsSet()) { res.add(context.getParentCourseGroup()); } return res; } public boolean isDissertation() { return false; } abstract public DegreeCurricularPlan getParentDegreeCurricularPlan(); abstract public void print(StringBuilder stringBuffer, String tabs, Context previousContext); abstract public boolean isLeaf(); abstract public boolean isRoot(); abstract public Double getMaxEctsCredits(final ExecutionSemester executionSemester); abstract public Double getMinEctsCredits(final ExecutionSemester executionSemester); abstract public void getAllDegreeModules(final Collection<DegreeModule> degreeModules); abstract public Set<CurricularCourse> getAllCurricularCourses(final ExecutionSemester executionSemester); abstract public Set<CurricularCourse> getAllCurricularCourses(); abstract public void doForAllCurricularCourses(final CurricularCourseFunctor curricularCourseFunctor); abstract public void applyToCurricularCourses(final ExecutionYear executionYear, final Predicate predicate); public boolean isOptionalCourseGroup() { return false; } }