3 * Copyright (C) 2013 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.api
.utility
;
12 import java
.util
.Collection
;
13 import java
.util
.HashMap
;
14 import java
.util
.HashSet
;
18 import org
.apache
.log4j
.Logger
;
20 import eu
.etaxonomy
.cdm
.api
.service
.DistributionTree
;
21 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
22 import eu
.etaxonomy
.cdm
.model
.common
.Marker
;
23 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
24 import eu
.etaxonomy
.cdm
.model
.common
.OrderedTermBase
;
25 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
26 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
27 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
28 import eu
.etaxonomy
.cdm
.model
.location
.NamedAreaLevel
;
29 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.IDefinedTermDao
;
32 * @author a.kohlbecker
36 public class DescriptionUtility
{
38 private static final Logger logger
= Logger
.getLogger(DescriptionUtility
.class);
41 * <b>NOTE: To avoid LayzyLoadingExceptions this method must be used in a transactional context.</b>
43 * Filters the given set of {@link Distribution}s for publication purposes
44 * The following rules are respected during the filtering:
46 * <li><b>Marked area filter</b>: Skip distributions for areas having a {@code TRUE} {@link Marker}
47 * with one of the specified {@link MarkerType}s. Existing sub-areas of a marked area must also be marked
48 * with the same marker type, otherwise the marked area acts as a <b>fallback area</b> for the sub areas.
49 * An area is a <b>fallback area</b> if it is marked to be hidden and if it has at least one of
50 * sub area which is not marked to be hidden. The fallback area will be show if there is no {@link Distribution}
51 * for any of the non hidden sub-areas. For more detailed discussion on fallback areas see
52 * https://dev.e-taxonomy.eu/trac/ticket/4408</li>
53 * <li><b>Prefer computed rule</b>:Computed distributions are preferred over entered or imported elements.
54 * (Computed description elements are identified by the {@link
55 * MarkerType.COMPUTED()}). This means if a entered or imported status
56 * information exist for the same area for which computed data is available,
57 * the computed data has to be given preference over other data.
58 * see parameter <code>preferComputed</code></li>
59 * <li><b>Status order preference rule</b>: In case of multiple distribution
60 * status ({@link PresenceAbsenceTermBase}) for the same area the status
61 * with the highest order is preferred, see
62 * {@link OrderedTermBase#compareTo(OrderedTermBase)}. This rule is
63 * optional, see parameter <code>statusOrderPreference</code></li>
64 * <li><b>Sub area preference rule</b>: If there is an area with a <i>direct
65 * sub area</i> and both areas have the same status only the
66 * information on the sub area should be reported, whereas the super area
67 * should be ignored. This rule is optional, see parameter
68 * <code>subAreaPreference</code>. Can be run separately from the other filters.
69 * This rule affects any distribution,
70 * that is to computed and edited equally. For more details see
71 * {@link https://dev.e-taxonomy.eu/trac/ticket/5050})</li>
74 * @param distributions
75 * the distributions to filter
76 * @param hiddenAreaMarkerTypes
77 * distributions where the area has a {@link Marker} with one of the specified {@link MarkerType}s will
78 * be skipped or acts as fall back area. For more details see <b>Marked area filter</b> above.
79 * @param preferComputed
80 * Computed distributions for the same area will be preferred over edited distributions.
81 * <b>This parameter should always be set to <code>true</code>.</b>
82 * @param statusOrderPreference
83 * enables the <b>Status order preference rule</b> if set to true,
84 * This rule can be run separately from the other filters.
85 * @param subAreaPreference
86 * enables the <b>Sub area preference rule</b> if set to true
87 * @return the filtered collection of distribution elements.
89 public static Set
<Distribution
> filterDistributions(Collection
<Distribution
> distributions
,
90 Set
<MarkerType
> hiddenAreaMarkerTypes
, boolean preferComputed
, boolean statusOrderPreference
, boolean subAreaPreference
) {
92 Map
<NamedArea
, Set
<Distribution
>> filteredDistributions
= new HashMap
<NamedArea
, Set
<Distribution
>>(100); // start with a big map from the beginning!
94 // sort Distributions by the area
95 for(Distribution distribution
: distributions
){
96 NamedArea area
= distribution
.getArea();
98 logger
.debug("skipping distribution with NULL area");
102 if(!filteredDistributions
.containsKey(area
)){
103 filteredDistributions
.put(area
, new HashSet
<Distribution
>());
105 filteredDistributions
.get(area
).add(distribution
);
108 // -------------------------------------------------------------------
109 // 1) skip distributions having an area with markers matching hideMarkedAreas
110 // but keep distributions for fallback areas
111 if(hiddenAreaMarkerTypes
!= null && !hiddenAreaMarkerTypes
.isEmpty()) {
112 Set
<NamedArea
> areasHiddenByMarker
= new HashSet
<NamedArea
>();
113 for(NamedArea area
: filteredDistributions
.keySet()) {
114 if(checkAreaMarkedHidden(hiddenAreaMarkerTypes
, area
)) {
115 boolean showAsFallbackArea
= false;
116 // if at least one sub area is not hidden by a marker
117 // this area is a fall back area for this sub area
118 for(NamedArea subArea
: area
.getIncludes()) {
119 if (!areasHiddenByMarker
.contains(subArea
) && checkAreaMarkedHidden(hiddenAreaMarkerTypes
, subArea
)) {
120 if(filteredDistributions
.containsKey(subArea
)) {
121 areasHiddenByMarker
.add(subArea
);
124 // if this subarea is not marked to be hidden
125 // the parent area must be visible if there is no
126 // data for the subarea
127 boolean subAreaVisible
= filteredDistributions
.containsKey(subArea
) && !areasHiddenByMarker
.contains(subArea
);
128 showAsFallbackArea
= !subAreaVisible
|| showAsFallbackArea
;
130 if (!showAsFallbackArea
) {
131 // this area does not need to be shown as
132 // fallback for another area
133 // so it will be hidden.
134 areasHiddenByMarker
.add(area
);
138 for(NamedArea area
:areasHiddenByMarker
) {
139 filteredDistributions
.remove(area
);
142 // -------------------------------------------------------------------
145 // -------------------------------------------------------------------
146 // 2) remove not computed distributions for areas for which computed
147 // distributions exists
150 Map
<NamedArea
, Set
<Distribution
>> computedDistributions
= new HashMap
<NamedArea
, Set
<Distribution
>>(distributions
.size());
151 Map
<NamedArea
, Set
<Distribution
>> otherDistributions
= new HashMap
<NamedArea
, Set
<Distribution
>>(distributions
.size());
152 // separate computed and edited Distributions
153 for (NamedArea area
: filteredDistributions
.keySet()) {
154 for (Distribution distribution
: filteredDistributions
.get(area
)) {
155 // this is only required for rule 1
156 if(distribution
.hasMarker(MarkerType
.COMPUTED(), true)){
157 if(!computedDistributions
.containsKey(area
)){
158 computedDistributions
.put(area
, new HashSet
<Distribution
>());
160 computedDistributions
.get(area
).add(distribution
);
162 if(!otherDistributions
.containsKey(area
)){
163 otherDistributions
.put(area
, new HashSet
<Distribution
>());
165 otherDistributions
.get(area
).add(distribution
);
169 for(NamedArea keyComputed
: computedDistributions
.keySet()){
170 otherDistributions
.remove(keyComputed
);
172 // combine computed and non computed Distributions again
173 filteredDistributions
.clear();
174 for(NamedArea key
: computedDistributions
.keySet()){
175 if(!filteredDistributions
.containsKey(key
)) {
176 filteredDistributions
.put(key
, new HashSet
<Distribution
>());
178 filteredDistributions
.get(key
).addAll(computedDistributions
.get(key
));
180 for(NamedArea key
: otherDistributions
.keySet()){
181 if(!filteredDistributions
.containsKey(key
)) {
182 filteredDistributions
.put(key
, new HashSet
<Distribution
>());
184 filteredDistributions
.get(key
).addAll(otherDistributions
.get(key
));
187 // -------------------------------------------------------------------
190 // -------------------------------------------------------------------
191 // 3) statusOrderPreference
192 if (statusOrderPreference
) {
193 Map
<NamedArea
, Set
<Distribution
>> tmpMap
= new HashMap
<NamedArea
, Set
<Distribution
>>(filteredDistributions
.size());
194 for(NamedArea key
: filteredDistributions
.keySet()){
195 tmpMap
.put(key
, byHighestOrderPresenceAbsenceTerm(filteredDistributions
.get(key
)));
197 filteredDistributions
= tmpMap
;
199 // -------------------------------------------------------------------
202 // -------------------------------------------------------------------
203 // 4) Sub area preference rule
204 if(subAreaPreference
){
205 Set
<NamedArea
> removeCandidatesArea
= new HashSet
<NamedArea
>();
206 for(NamedArea key
: filteredDistributions
.keySet()){
207 if(removeCandidatesArea
.contains(key
)){
210 if(key
.getPartOf() != null && filteredDistributions
.containsKey(key
.getPartOf())){
211 removeCandidatesArea
.add(key
.getPartOf());
214 for(NamedArea removeKey
: removeCandidatesArea
){
215 filteredDistributions
.remove(removeKey
);
218 // -------------------------------------------------------------------
220 return valuesOfAllInnerSets(filteredDistributions
.values());
224 * @param hiddenAreaMarkerTypes
226 * @param isMarkedHidden
229 public static boolean checkAreaMarkedHidden(Set
<MarkerType
> hiddenAreaMarkerTypes
, NamedArea area
) {
230 if(hiddenAreaMarkerTypes
!= null) {
231 for(MarkerType markerType
: hiddenAreaMarkerTypes
){
232 if(area
.hasMarker(markerType
, true)){
241 * Orders the given Distribution elements in a hierarchical structure.
242 * This method will not filter out any of the Distribution elements.
245 * @param hiddenAreaMarkerTypes
246 * Areas not associated to a Distribution in the {@code distList} are detected as fall back area
247 * if they are having a {@link Marker} with one of the specified {@link MarkerType}s. Areas identified as such
248 * are omitted from the hierarchy and the sub areas are moving one level up.
249 * For more details on fall back areas see <b>Marked area filter</b> of
250 * {@link DescriptionUtility#filterDistributions(Collection, Set, boolean, boolean, boolean)}.
251 * @param distributionOrder
255 public static DistributionTree
orderDistributions(IDefinedTermDao termDao
,
256 Set
<NamedAreaLevel
> omitLevels
,
257 Collection
<Distribution
> distributions
,
258 Set
<MarkerType
> hiddenAreaMarkerTypes
,
259 DistributionOrder distributionOrder
) {
261 DistributionTree tree
= new DistributionTree(termDao
);
263 if (logger
.isDebugEnabled()){logger
.debug("order tree ...");}
265 tree
.orderAsTree(distributions
, omitLevels
, hiddenAreaMarkerTypes
);
266 tree
.recursiveSortChildren(distributionOrder
); // FIXME respect current locale for sorting
267 if (logger
.isDebugEnabled()){logger
.debug("create tree - DONE");}
272 * Implements the Status order preference filter for a given set to Distributions.
273 * The distributions should all be for the same area.
274 * The method returns a site of distributions since multiple Distributions
275 * with the same status are possible. For example if the same status has been
276 * published in more than one literature references.
278 * @param distributions
280 * @return the set of distributions with the highest status
282 private static Set
<Distribution
> byHighestOrderPresenceAbsenceTerm(Set
<Distribution
> distributions
){
284 Set
<Distribution
> preferred
= new HashSet
<Distribution
>();
285 PresenceAbsenceTerm highestStatus
= null; //we need to leave generics here as for some reason highestStatus.compareTo later jumps into the wrong class for calling compareTo
287 for (Distribution distribution
: distributions
) {
288 if(highestStatus
== null){
289 highestStatus
= distribution
.getStatus();
290 preferred
.add(distribution
);
292 if(distribution
.getStatus() == null){
295 compareResult
= highestStatus
.compareTo(distribution
.getStatus());
297 if(compareResult
< 0){
298 highestStatus
= distribution
.getStatus();
300 preferred
.add(distribution
);
301 } else if(compareResult
== 0) {
302 preferred
.add(distribution
);
310 private static <T
extends CdmBase
> Set
<T
> valuesOfAllInnerSets(Collection
<Set
<T
>> collectionOfSets
){
311 Set
<T
> allValues
= new HashSet
<T
>();
312 for(Set
<T
> set
: collectionOfSets
){
313 allValues
.addAll(set
);
319 * Provides a consistent string based key of the given NamedArea , see also
320 * {@link #areaKey(Distribution)}
323 * @return the string representation of the NamedArea.uuid
325 // private static String areaKey(NamedArea area){
326 // return String.valueOf(area.getUuid());
330 * Provides a consistent string based key of the given NamedArea contained
331 * in the given distribution, see also {@link #areaKey(Distribution)}
333 * @param distribution
334 * @return the string representation of the NamedArea.uuid or
335 * <code>"NULL"</code> in case the Distribution had no NamedArea
337 // private static String areaKey(Distribution distribution){
338 // StringBuilder keyBuilder = new StringBuilder();
340 // if(distribution.getArea() != null){
341 // keyBuilder.append(distribution.getArea().getUuid());
343 // keyBuilder.append("NULL");
346 // return keyBuilder.toString();