This is an automated email from the git hooks/post-receive script. New commit to branch feature/5923 in repository wao. See http://git.codelutin.com/wao.git commit 1322a83b593828cef55008860037c3b311392711 Author: Brendan Le Ny <bleny@codelutin.com> Date: Fri Feb 20 11:07:23 2015 +0100 Remaniement de la génération du graphique du planifié vs réalisé, on introduit un pattern Template Method et trois implémentations (une par programme) --- .../fr/ifremer/wao/entity/ContactTopiaDao.java | 32 +- .../wao/services/service/SynthesisService.java | 411 +++++++++++++++++---- 2 files changed, 342 insertions(+), 101 deletions(-) diff --git a/wao-persistence/src/main/java/fr/ifremer/wao/entity/ContactTopiaDao.java b/wao-persistence/src/main/java/fr/ifremer/wao/entity/ContactTopiaDao.java index efc04b7..ba9d427 100644 --- a/wao-persistence/src/main/java/fr/ifremer/wao/entity/ContactTopiaDao.java +++ b/wao-persistence/src/main/java/fr/ifremer/wao/entity/ContactTopiaDao.java @@ -21,7 +21,6 @@ package fr.ifremer.wao.entity; * #L% */ -import com.google.common.base.Function; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; @@ -29,7 +28,6 @@ import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; import fr.ifremer.wao.ContactsFilter; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.Range; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -46,7 +44,6 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.SortedMap; import java.util.TreeMap; public class ContactTopiaDao extends AbstractContactTopiaDao<Contact> { @@ -207,35 +204,10 @@ public class ContactTopiaDao extends AbstractContactTopiaDao<Contact> { return query.findFirstOrNull(); } - public SortedMap<Date, Integer> getActualObservationsByMonths(Date periodFromMonth, Date periodToMonth, ContactsFilter filter, Function<Date, Date> truncateToTimePeriodFunction) { - + public List<Contact> getContactsWithObservationDoneState(ContactsFilter filter) { HqlAndParametersBuilder<Contact> query = toContactHqlAndParametersBuilder(filter); query.addEquals(Contact.PROPERTY_CONTACT_STATE, ContactState.OBSERVATION_DONE); - List<Contact> contacts = findAll(query.getHql(), query.getHqlParameters()); - - SortedMap<Date, Integer> actualObservationsByPeriods = new TreeMap<>(); - Range<Date> range = Range.between(periodFromMonth, periodToMonth); - - for (Contact contact : contacts) { - Date period = truncateToTimePeriodFunction.apply(contact.getObservationBeginDate()); - if (range.contains(period)) { - Integer count = actualObservationsByPeriods.get(period); - if (count == null) { - count = 0; - } - if (filter.getSampleRowFilter().getObsProgram().isSclerochronology()) { - // le réalisé est le nombre d'individus échantilonnés - count += contact.getSampleSize(); - } else { - // le réalisé est le nombre d'observation, on compte 1 observation pour 1 navire donc 1 pour chaque contact - count += 1; - } - actualObservationsByPeriods.put(period, count); - } - } - - return actualObservationsByPeriods; - + return findAll(query.getHql(), query.getHqlParameters()); } public Map<Boat, LinkedHashMap<String, List<Date>>> getBoardingBoats(ContactsFilter filter) { diff --git a/wao-services/src/main/java/fr/ifremer/wao/services/service/SynthesisService.java b/wao-services/src/main/java/fr/ifremer/wao/services/service/SynthesisService.java index 71fd2fe..c37c12b 100644 --- a/wao-services/src/main/java/fr/ifremer/wao/services/service/SynthesisService.java +++ b/wao-services/src/main/java/fr/ifremer/wao/services/service/SynthesisService.java @@ -21,20 +21,25 @@ package fr.ifremer.wao.services.service; * #L% */ -import com.google.common.base.Function; +import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.cache.Cache; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import fr.ifremer.wao.ContactsFilter; +import fr.ifremer.wao.SampleRowsFilter; import fr.ifremer.wao.WaoUtils; import fr.ifremer.wao.entity.Boat; import fr.ifremer.wao.entity.Company; +import fr.ifremer.wao.entity.Contact; import fr.ifremer.wao.entity.ContactState; import fr.ifremer.wao.entity.ContactTopiaDao; import fr.ifremer.wao.entity.ObsProgram; -import fr.ifremer.wao.entity.SampleRowTopiaDao; +import fr.ifremer.wao.entity.SampleMonth; +import fr.ifremer.wao.entity.SampleRow; import fr.ifremer.wao.entity.SynthesisId; import fr.ifremer.wao.services.AuthenticatedWaoUser; +import org.apache.commons.lang3.Range; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -214,80 +219,49 @@ public class SynthesisService extends WaoServiceSupport { Preconditions.checkArgument(filter.isFilterOnObservationBeginDate()); - Locale locale = serviceContext.getLocale(); - - ObsProgram obsProgram = filter.getSampleRowFilter().getObsProgram(); + SampleRowsFilter sampleRowFilter = filter.getSampleRowFilter(); + ObsProgram obsProgram = sampleRowFilter.getObsProgram(); - Function<Date, Date> truncateToTimePeriodFunction; - Function<Date, String> formatPeriodFunction; - if (obsProgram.isObsMer() || obsProgram.isObsVente()) { - truncateToTimePeriodFunction = WaoUtils.truncateToMonthFunction(); - formatPeriodFunction = WaoUtils.formatMonthFunction(locale); + Locale locale = serviceContext.getLocale(); + Date periodFrom = filter.getPeriodFrom(); + Date periodTo = filter.getPeriodTo(); + Collection<SampleRow> sampleRows = getSampleRowDao().findAll(sampleRowFilter); + Collection<Contact> contacts = getContactDao().getContactsWithObservationDoneState(filter); + boolean realVsEstimated = filter.getRealVsEstimated(); + + // selon le programme, on applique la ou les bons templates de génération du graphe + ExpectedVsActualChartTemplateMethod method; + if (obsProgram.isObsMer()) { + method = ExpectedVsActualChartTemplateMethod.forObsMerInObservations( + locale, + periodFrom, periodTo, + sampleRows, + contacts, + realVsEstimated); + synthesis.setExpectedVsActualObservationsByMonthsChart(method.getChart()); + } else if (obsProgram.isObsVente()) { + method = ExpectedVsActualChartTemplateMethod.forObsVenteInObservations( + locale, + periodFrom, periodTo, + sampleRows, + contacts, + realVsEstimated); + synthesis.setExpectedVsActualObservationsByMonthsChart(method.getChart()); } else if (obsProgram.isSclerochronology()) { - truncateToTimePeriodFunction = WaoUtils.truncateToTrimesterFunction(); - formatPeriodFunction = WaoUtils.formatTrimesterFunction(locale); + method = ExpectedVsActualChartTemplateMethod.forSclerochronologyInObservations( + locale, + periodFrom, periodTo, + sampleRows, + contacts, + realVsEstimated); + synthesis.setExpectedVsActualObservationsByMonthsChart(method.getChart()); } else { - throw new UnsupportedOperationException("unsupported " + obsProgram); + throw new UnsupportedOperationException("obsProgram = " + obsProgram); } - // définition de la fenêtre - Date periodFromMonth = truncateToTimePeriodFunction.apply(filter.getPeriodFrom()); - Date periodToMonth = truncateToTimePeriodFunction.apply(filter.getPeriodTo()); - - // Calcul du programmé - SampleRowTopiaDao dao = getSampleRowDao(); - SortedMap<Date, Integer> expectedObservationsByMonths = - dao.getExpectedObservationsByMonths(periodFromMonth, periodToMonth, filter.getSampleRowFilter(), truncateToTimePeriodFunction); - - // Calcul du réalisé - ContactTopiaDao contactDao = getContactDao(); - SortedMap<Date, Integer> actualObservationsMyMonths = - contactDao.getActualObservationsByMonths(periodFromMonth, periodToMonth, filter, truncateToTimePeriodFunction); - - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - - for (Map.Entry<Date, Integer> entry : expectedObservationsByMonths.entrySet()) { - Date period = entry.getKey(); - Integer expected = entry.getValue(); - dataset.setValue(expected, I18n.l(locale, "wao.synthesis.planned"), formatPeriodFunction.apply(period)); - } - String rowKey; - if (filter.getRealVsEstimated()) { - rowKey = I18n.l(locale, "wao.ui.samplingPlan.Actual"); - } else { - rowKey = I18n.l(locale, "wao.synthesis.estimated"); - } - for (Map.Entry<Date, Integer> entry : actualObservationsMyMonths.entrySet()) { - Date period = entry.getKey(); - Integer actual = entry.getValue(); - dataset.setValue(actual, rowKey, formatPeriodFunction.apply(period)); - } - - // Axises - CategoryAxis categoryAxis = new CategoryAxis(""); - - String valueAxisLabel; - if (obsProgram.isSclerochronology()) { - valueAxisLabel = I18n.l(locale, "wao.synthesis.observationsCount.sclerochronology"); - } else { - valueAxisLabel = I18n.l(locale, "wao.synthesis.observationsCount"); - } - ValueAxis valueAxis = new NumberAxis(valueAxisLabel); - valueAxis.setUpperMargin(0.15); - - // Renderer for Category - AbstractCategoryItemRenderer renderer = new BarRenderer(); - // Show labels on each element - renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator()); - renderer.setBaseItemLabelsVisible(Boolean.TRUE); - - CategoryPlot plot = new CategoryPlot(dataset, categoryAxis, valueAxis, renderer); - plot.setOrientation(PlotOrientation.VERTICAL); - plot.setAxisOffset(RectangleInsets.ZERO_INSETS); - - JFreeChart expectedVsActualObservationsByMonthsChart = - new JFreeChart(I18n.l(locale, SynthesisId.GRAPH_SAMPLING.getI18nKey()), JFreeChart.DEFAULT_TITLE_FONT, plot, true); - synthesis.setExpectedVsActualObservationsByMonthsChart(expectedVsActualObservationsByMonthsChart); + Preconditions.checkNotNull( + synthesis.getExpectedVsActualObservationsByMonthsChart(), + "all synthesis must provide expectedVsActualObservationsByMonthsChart"); } @@ -426,4 +400,299 @@ public class SynthesisService extends WaoServiceSupport { synthesis.setBoardingBoatsChart(boardingBoatsChart); } + + /** + * Représente l'algorithme de génération d'un graphique qui permet de comparer le planifié + * au réalisé. + * + * On utilise le patron de conception Template Method car on doit pouvoir décomposer en mois + * ou en trimestres, varier les libellés et les modes de calculs selon le {@link fr.ifremer.wao.entity.ObsProgram} + */ + protected abstract static class ExpectedVsActualChartTemplateMethod { + + /** + * La locale, utile pour les libellés + */ + protected Locale locale; + + protected Date periodFrom; + + protected Date periodTo; + + protected ImmutableSet<SampleRow> sampleRows; + + /** + * Les contacts réalisés ({@link fr.ifremer.wao.entity.ContactState#OBSERVATION_DONE}) pour + * les lignes {@link #sampleRows} + */ + protected ImmutableSet<Contact> contacts; + + protected boolean realVsEstimated; + + public static ExpectedVsActualChartTemplateMethod forObsMerInObservations(Locale locale, Date periodFrom, Date periodTo, Collection<SampleRow> sampleRows, Collection<Contact> contacts, boolean realVsEstimated) { + ExpectedVsActualChartTemplateMethod method = new ExpectedVsActualObsMerObservationsChartMethod(); + return buildMethod(method, locale, periodFrom, periodTo, sampleRows, contacts, realVsEstimated); + } + + public static ExpectedVsActualChartTemplateMethod forObsVenteInObservations(Locale locale, Date periodFrom, Date periodTo, Collection<SampleRow> sampleRows, Collection<Contact> contacts, boolean realVsEstimated) { + ExpectedVsActualChartTemplateMethod method = new ExpectedVsActualObsVenteObservationsChartMethod(); + return buildMethod(method, locale, periodFrom, periodTo, sampleRows, contacts, realVsEstimated); + } + + public static ExpectedVsActualChartTemplateMethod forSclerochronologyInObservations(Locale locale, Date periodFrom, Date periodTo, Collection<SampleRow> sampleRows, Collection<Contact> contacts, boolean realVsEstimated) { + ExpectedVsActualChartTemplateMethod method = new ExpectedVsActualSclerochronologyObservationsChartMethod(); + return buildMethod(method, locale, periodFrom, periodTo, sampleRows, contacts, realVsEstimated); + } + + protected static ExpectedVsActualChartTemplateMethod buildMethod(ExpectedVsActualChartTemplateMethod method, Locale locale, Date periodFrom, Date periodTo, Collection<SampleRow> sampleRows, Collection<Contact> contacts, boolean realVsEstimated) { + method.locale = locale; + method.periodFrom = periodFrom; + method.periodTo = periodTo; + method.sampleRows = ImmutableSet.copyOf(sampleRows); + method.contacts = ImmutableSet.copyOf(contacts); + method.realVsEstimated = realVsEstimated; + return method; + } + + public JFreeChart getChart() { + + // définition de la fenêtre + Date periodFromMonth = truncateToTimePeriodFunction(periodFrom); + Date periodToMonth = truncateToTimePeriodFunction(periodTo); + + Range<Date> periodRange = Range.between(periodFromMonth, periodToMonth); + + // Calcul du programmé + SortedMap<Date, Integer> expectedEffortByPeriods = new TreeMap<>(); + for (SampleRow sampleRow : sampleRows) { + for (SampleMonth sampleMonth : sampleRow.getSampleMonth()) { + Date month = sampleMonth.getPeriodDate(); + Date period = truncateToTimePeriodFunction(month); + if (periodRange.contains(period)) { + Integer expected = MoreObjects.firstNonNull( + expectedEffortByPeriods.get(period), + 0); + expected += getExpected(sampleMonth); + expectedEffortByPeriods.put(period, expected); + } + } + } + + if (log.isDebugEnabled()) { + log.debug("expected effort by periods is " + expectedEffortByPeriods); + } + + // Calcul du réalisé + SortedMap<Date, Integer> actualObservationsByPeriods = new TreeMap<>(); + for (Contact contact : contacts) { + Preconditions.checkState(sampleRows.contains(contact.getSampleRow())); + Date period = truncateToTimePeriodFunction(contact.getObservationBeginDate()); + if (periodRange.contains(period)) { + Integer count = MoreObjects.firstNonNull( + actualObservationsByPeriods.get(period), + 0); + count += getActual(contact); + actualObservationsByPeriods.put(period, count); + } + } + + if (log.isDebugEnabled()) { + log.debug("actual effort by periods is " + actualObservationsByPeriods); + } + + // Création du graphique + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + + for (Map.Entry<Date, Integer> entry : expectedEffortByPeriods.entrySet()) { + Date period = entry.getKey(); + Integer expected = entry.getValue(); + dataset.setValue(expected, I18n.l(locale, "wao.synthesis.planned"), formatPeriod(period)); + } + String rowKey; + if (realVsEstimated) { + rowKey = I18n.l(locale, "wao.ui.samplingPlan.Actual"); + } else { + rowKey = I18n.l(locale, "wao.synthesis.estimated"); + } + for (Map.Entry<Date, Integer> entry : actualObservationsByPeriods.entrySet()) { + Date period = entry.getKey(); + Integer actual = entry.getValue(); + dataset.setValue(actual, rowKey, formatPeriod(period)); + } + + // Axises + CategoryAxis categoryAxis = new CategoryAxis(""); + + String valueAxisLabel = getValueAxisLabel(); + ValueAxis valueAxis = new NumberAxis(valueAxisLabel); + valueAxis.setUpperMargin(0.15); + + // Renderer for Category + AbstractCategoryItemRenderer renderer = new BarRenderer(); + // Show labels on each element + renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator()); + renderer.setBaseItemLabelsVisible(Boolean.TRUE); + + CategoryPlot plot = new CategoryPlot(dataset, categoryAxis, valueAxis, renderer); + plot.setOrientation(PlotOrientation.VERTICAL); + plot.setAxisOffset(RectangleInsets.ZERO_INSETS); + + JFreeChart chart = new JFreeChart( + I18n.l(locale, SynthesisId.GRAPH_SAMPLING.getI18nKey()), + JFreeChart.DEFAULT_TITLE_FONT, plot, true); + + return chart; + + } + + /** + * Combien il faut compter de réalisé pour ce contact. + */ + protected abstract int getActual(Contact contact); + + protected abstract String getValueAxisLabel(); + + /** + * Combien il faut compter d'effort planifié pour ce mois. + */ + protected abstract int getExpected(SampleMonth sampleMonth); + + /** + * Pour une date, indique dans quelle période de temps elle se trouve (mois ? trimestre ?) + */ + protected abstract Date truncateToTimePeriodFunction(Date period); + + protected abstract String formatPeriod(Date period); + + /** + * Pour ObsMer en nombre d'observation. + */ + protected static class ExpectedVsActualObsMerObservationsChartMethod extends ExpectedVsActualChartTemplateMethod { + + /** + * On est sur un découpage mensuel. + */ + @Override + protected String formatPeriod(Date period) { + return WaoUtils.formatMonth(locale, period); + } + + /** + * On est sur un découpage mensuel. + */ + @Override + protected Date truncateToTimePeriodFunction(Date period) { + return WaoUtils.truncateToMonth(period); + } + + /** + * Le nombre de sorties en mer. + */ + @Override + protected int getExpected(SampleMonth sampleMonth) { + return sampleMonth.getExpectedTidesValue(); + } + + @Override + protected String getValueAxisLabel() { + return I18n.l(locale, "wao.synthesis.observationsCount"); + } + + /** + * Le réalisé est le nombre d'observation, on compte 1 observation pour 1 navire donc 1 pour chaque contact. + */ + @Override + protected int getActual(Contact contact) { + return 1; + } + } + + /** + * Pour ObsVente en nombre d'observation. + */ + protected static class ExpectedVsActualObsVenteObservationsChartMethod extends ExpectedVsActualChartTemplateMethod { + + /** + * On est sur un découpage mensuel. + */ + @Override + protected String formatPeriod(Date period) { + return WaoUtils.formatMonth(locale, period); + } + + /** + * On est sur un découpage mensuel. + */ + @Override + protected Date truncateToTimePeriodFunction(Date period) { + return WaoUtils.truncateToMonth(period); + } + + /** + * Pour ObsVente, le nombre de sortie × le nombre de navires qu'il est demandé d'observer par sortie. + */ + @Override + protected int getExpected(SampleMonth sampleMonth) { + int averageObservationsCount = sampleMonth.getSampleRow().getAverageObservationsCount(); + return sampleMonth.getExpectedTidesValue() * averageObservationsCount; + } + + @Override + protected String getValueAxisLabel() { + return I18n.l(locale, "wao.synthesis.observationsCount"); + } + + /** + * Le réalisé est le nombre d'observation, on compte 1 observation pour 1 navire donc 1 pour chaque contact. + */ + @Override + protected int getActual(Contact contact) { + return 1; + } + } + + /** + * Pour Sclerochronologie en nombre d'observation. + */ + protected static class ExpectedVsActualSclerochronologyObservationsChartMethod extends ExpectedVsActualChartTemplateMethod { + + /** + * On est sur un découpage en trimestres. + */ + @Override + protected String formatPeriod(Date period) { + return WaoUtils.formatTrimester(locale, period); + } + + /** + * On est sur un découpage en trimestres. + */ + @Override + protected Date truncateToTimePeriodFunction(Date period) { + return WaoUtils.truncateToTrimester(period); + } + + /** + * Le nombre d'individu à observer. + */ + @Override + protected int getExpected(SampleMonth sampleMonth) { + return sampleMonth.getExpectedTidesValue(); + } + + @Override + protected String getValueAxisLabel() { + return I18n.l(locale, "wao.synthesis.observationsCount.sclerochronology"); + } + + /** + * Le réalisé est le nombre d'individus échantilonnés. + */ + @Override + protected int getActual(Contact contact) { + return contact.getSampleSize(); + } + } + + } } -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.