001/* 002 * Copyright 2019 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.odfpilotage.report.consistency.impl; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.ametys.cms.data.ContentValue; 026import org.ametys.cms.repository.Content; 027import org.ametys.odf.ProgramItem; 028import org.ametys.odf.course.Course; 029import org.ametys.odf.courselist.CourseList; 030import org.ametys.odf.courselist.CourseList.ChoiceType; 031import org.ametys.odf.enumeration.OdfReferenceTableEntry; 032import org.ametys.odf.program.AbstractProgram; 033import org.ametys.odf.program.Container; 034import org.ametys.odf.program.Program; 035import org.ametys.plugins.odfpilotage.report.consistency.AbstractConsistencyAnalysis; 036import org.ametys.plugins.odfpilotage.report.consistency.ConsistencyAnalysisResult; 037import org.ametys.plugins.odfpilotage.report.consistency.ConsistencyAnalysisStatus; 038import org.ametys.runtime.i18n.I18nizableText; 039 040/** 041 * Analysis on ECTS sums at each level of the {@link Program}. 042 */ 043public class ECTSSumAnalysis extends AbstractConsistencyAnalysis 044{ 045 private static final Pattern __ECTS_PATTERN = Pattern.compile("^.*?([0-9]+(?:[\\.,][0-9]+)?).*$"); 046 047 @Override 048 public ConsistencyAnalysisResult analyze(Program program) 049 { 050 ConsistencyAnalysisResult result = new ConsistencyAnalysisResult(); 051 052 // Introduction 053 result.setIntroText(new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_INTRO")); 054 055 // Columns 056 result.addColumn("parentName", new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_COLUMN_PARENT_NAME"), true); 057 result.addColumn("parentCode", new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_COLUMN_PARENT_CODE")); 058 result.addColumn("parentECTS", new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_COLUMN_PARENT_ECTS")); 059 result.addColumn("childrenECTS", new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_COLUMN_CHILDREN_ECTS")); 060 061 // Errors 062 List<Map<String, Object>> errors = _controlECTS(program, 0); 063 result.addLines(errors); 064 065 // Status 066 result.setStatus(errors.isEmpty() ? ConsistencyAnalysisStatus.OK : ConsistencyAnalysisStatus.KO); 067 068 return result; 069 } 070 071 /** 072 * Get the {@link I18nizableText} representing the error message to display (with parameters). 073 * @param programItem The program element which is inconsistency 074 * @param parentECTS Number of ECTS on the parent 075 * @param childrenECTS Number of ECTS on the children 076 * @param level the level of the line to retrieve (hierarchical level begins at 0 for the program) 077 * @return The error message 078 */ 079 protected Map<String, Object> _getErrorMessage(ProgramItem programItem, Double parentECTS, Double childrenECTS, int level) 080 { 081 Map<String, Object> error = new HashMap<>(); 082 error.put("parentName", ((Content) programItem).getTitle()); 083 error.put("parentCode", programItem.getCode()); 084 error.put("parentECTS", parentECTS); 085 error.put("childrenECTS", childrenECTS); 086 error.put(ConsistencyAnalysisResult.INDENTATION_LEVEL_DATA_NAME, level); 087 return error; 088 } 089 090 /** 091 * Control the ECTS consistency between the current content and its children. 092 * @param programItem The {@link ProgramItem} to check ECTS consistency 093 * @param level the level of the program item to control (hierarchical level begins at 0 for the program) 094 * @return The {@link List} of error messages 095 */ 096 protected List<Map<String, Object>> _controlECTS(ProgramItem programItem, int level) 097 { 098 List<Map<String, Object>> errors = new ArrayList<>(); 099 100 // Children ECTS control (if there are children) 101 List<ProgramItem> children = _odfHelper.getChildProgramItems(programItem); 102 if (!children.isEmpty()) 103 { 104 // Current content ECTS computation and control 105 Double parentECTS = _getECTS((Content) programItem); 106 // If there is no ECTS on the current content, there is no reason to control it. 107 if (parentECTS != null) 108 { 109 Double childrenECTS = _getChildrenECTSSum(programItem); 110 111 if (!parentECTS.equals(childrenECTS)) 112 { 113 errors.add(_getErrorMessage(programItem, parentECTS, childrenECTS, level)); 114 } 115 } 116 117 for (ProgramItem child : children) 118 { 119 errors.addAll(_controlECTS(child, level + 1)); 120 } 121 } 122 123 return errors; 124 } 125 126 /** 127 * Get the sum of the children of the current item. It calculates the coef if it's a {@link CourseList}. 128 * @param programItem The parent {@link ProgramItem}. 129 * @return The sum of children ECTS 130 */ 131 protected Double _getChildrenECTSSum(ProgramItem programItem) 132 { 133 // Ignore optional course list and avoid useless expensive calls 134 if (programItem instanceof CourseList && ChoiceType.OPTIONAL.equals(((CourseList) programItem).getType())) 135 { 136 return 0.0; 137 } 138 139 List<ProgramItem> children = _odfHelper.getChildProgramItems(programItem); 140 141 Double coef = 1.0; 142 Double childrenECTS = 0.0; 143 if (!children.isEmpty()) 144 { 145 if (programItem instanceof CourseList) 146 { 147 CourseList courseList = (CourseList) programItem; 148 switch (courseList.getType()) 149 { 150 case CHOICE: 151 // Apply the average of ECTS from children multiply by the minimum ELP to select 152 coef = ((double) courseList.getMinNumberOfCourses()) / children.size(); 153 break; 154 case MANDATORY: 155 default: 156 // Add all ECTS from children 157 break; 158 } 159 } 160 161 for (ProgramItem child : children) 162 { 163 Double childECTS = _getECTS((Content) child); 164 if (childECTS == null) 165 { 166 childECTS = _getChildrenECTSSum(child); 167 } 168 childrenECTS += childECTS; 169 } 170 } 171 172 return coef * childrenECTS; 173 } 174 175 /** 176 * Get the ECTS of the current {@link Content}. The method is different if it's a {@link Program} or a {@link Course} for example. 177 * @param content The content 178 * @return The value in the ECTS field 179 */ 180 protected Double _getECTS(Content content) 181 { 182 // ECTS : program, subProgram (enumeration) 183 // ECTS : container, course (enumeration) 184 // Nothing on courseList 185 186 if (!content.hasNonEmptyValue("ects")) 187 { 188 return null; 189 } 190 191 Object ects = content.getValue("ects"); 192 193 if (content instanceof Course || content instanceof Container) 194 { 195 return (Double) ects; 196 } 197 198 if (content instanceof AbstractProgram) 199 { 200 String code = new OdfReferenceTableEntry(((ContentValue) ects).getContent()).getCode(); 201 Matcher matcher = __ECTS_PATTERN.matcher(code); 202 if (matcher.matches()) 203 { 204 return Double.parseDouble(matcher.group(1).replace(",", ".")); 205 } 206 return null; 207 } 208 209 return null; 210 } 211}