Project

General

Profile

Download (10.6 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9

    
10
package eu.etaxonomy.cdm.model.taxon;
11

    
12
import java.util.Comparator;
13
import java.util.HashSet;
14
import java.util.Set;
15

    
16
import org.apache.log4j.Logger;
17

    
18
import eu.etaxonomy.cdm.model.name.NameRelationship;
19
import eu.etaxonomy.cdm.model.name.NameRelationshipType;
20
import eu.etaxonomy.cdm.model.name.TaxonName;
21

    
22
/**
23
 * This class orders synonyms of a homotypic group,
24
 * first by
25
 * <ul>
26
 *  <li>Basionym groups (the basionym and all names derived from this basionym)
27
 *      should be kept together in a subgroup</li>
28
 *  <li>The order of the subgroups is defined by the ordering of their
29
 *       basionyms (according to the following ordering)</li>
30
 *  <li>If a name is illegitimate or not does play a role for ordering</li>
31
 *  <li>Names with publication year should always come first</li>
32
 *  <li>Names with no publication year are sorted by rank</li>
33
 *  <li>Names with no publication year and equal rank are sorted alphabetically</li>
34
 *  <li>If 2 names have a replaced synonym relationship the replaced synonym comes first,
35
 *      the replacement name comes later as this reflects the order of publication</li>
36
 *  </ul>
37
 *
38
 * Details on ordering are explained at http://dev.e-taxonomy.eu/trac/ticket/3338<BR>
39
 *
40
 * @author a.mueller
41
 * @created 02.03.2016
42
 */
43
public class HomotypicGroupTaxonComparator extends TaxonComparator {
44
    private static final long serialVersionUID = -5088210641256430878L;
45
	private static final Logger logger = Logger.getLogger(HomotypicGroupTaxonComparator.class);
46

    
47
    private final TaxonBase<?> firstTaxonInGroup;
48
    private final TaxonName firstNameInGroup;
49
//    private final HomotypicalGroupComparator homotypicGroupComparator;
50

    
51
    /**
52
     * @param firstNameInGroup
53
     */
54
    public HomotypicGroupTaxonComparator(@SuppressWarnings("rawtypes") TaxonBase firstTaxonInGroup) {
55
        super(true);
56
        this.firstTaxonInGroup = firstTaxonInGroup;
57
        this.firstNameInGroup = firstTaxonInGroup == null ? null: firstTaxonInGroup.getName();
58
    }
59

    
60
    /**
61
     * @param firstNameInGroup
62
     */
63
    public HomotypicGroupTaxonComparator(@SuppressWarnings("rawtypes") TaxonBase firstTaxonInGroup, boolean includeRanks) {
64
        super(includeRanks);
65
        this.firstTaxonInGroup = firstTaxonInGroup;
66
        this.firstNameInGroup = firstTaxonInGroup == null ? null: firstTaxonInGroup.getName();
67
    }
68

    
69
    /**
70
     *
71
     * @see TaxonComparator#compare(TaxonBase, TaxonBase)
72
     * @see java.lang.String#compareTo(String)
73
     * @see	java.util.Comparator#compare(java.lang.Object, java.lang.Object)
74
     */
75
    @Override
76
    public int compare(
77
            @SuppressWarnings("rawtypes") TaxonBase taxonBase1,
78
            @SuppressWarnings("rawtypes") TaxonBase taxonBase2) {
79

    
80
        TaxonName name1 = taxonBase1.getName();
81
        TaxonName name2 = taxonBase2.getName();
82
        if (logger.isDebugEnabled()){logger.debug(name1.getTitleCache() +" : "+ name2.getTitleCache());}
83

    
84

    
85
        int compareStatus = compareStatus(name1, name2);
86
        if (compareStatus != 0){
87
            return compareStatus;
88
        }
89

    
90
        //not same homotypical group -
91
        //NOTE: this comparator should usually not be used
92
        //      for comparing names of different homotypical groups.
93
        //      The following is only to have a defined compare behavior
94
        //      which follows the contract of Comparator#compare.
95
        if (name1 == null ||
96
            name2 == null ||
97
            ! name1.getHomotypicalGroup().equals(name2.getHomotypicalGroup())){
98

    
99
            String compareString1 = name1 != null ?
100
                    name1.getHomotypicalGroup().getUuid().toString() :
101
                    taxonBase1.getUuid().toString();
102
            String compareString2 = name2 != null ?
103
                    name2.getHomotypicalGroup().getUuid().toString() :
104
                    taxonBase2.getUuid().toString();
105
            int result = compareString1.compareTo(compareString2);
106
            return result;
107
        }
108

    
109
        //same homotypical group ...
110
        //one taxon is first in group
111
        if (taxonBase1.equals(firstTaxonInGroup)){
112
            return -1;
113
        }else if (taxonBase2.equals(firstTaxonInGroup)){
114
            return 1;
115
        }
116

    
117
        //same name => compare on taxon level
118
        if (name1.equals(name2)){
119
            return super.compare(taxonBase1, taxonBase2);  //if name is the same compare on taxon level
120
        }
121

    
122
        TaxonName basionym1 = getPreferredInBasionymGroup(name1);
123
        TaxonName basionym2 = getPreferredInBasionymGroup(name2);
124

    
125
        int compareResult;
126
        if (basionym1.equals(basionym2)){
127
            //both names belong to same basionym sub-group
128
            compareResult = handleSameBasionym(basionym1, name1, name2);
129
        }else{
130
            compareResult = compareBasionyms(basionym1, basionym2);
131
        }
132

    
133
        if (compareResult != 0){
134
//            if (logger.isDebugEnabled()){logger.debug(": " + compareResult);}
135
            return compareResult;
136
        }else{
137
            //names are uncomparable on name level (except for uuid, id, etc.)
138
            int result = super.compare(taxonBase1, taxonBase2);
139
            if (logger.isDebugEnabled()){logger.debug(": = " + result);}
140
            return result;
141
        }
142
    }
143

    
144

    
145
    /**
146
     * Compare 2 names which have the same basionym.
147
     * The names must not be equal to each other but may be equal
148
     * to the basionym.
149
     * @param basionym the basionym
150
     * @param name1 first name to compare
151
     * @param name2 second name to compare
152
     * @return compare value according to the {@link Comparator#compare(Object, Object)} contract.
153
     */
154
    private int handleSameBasionym(TaxonName basionym,
155
            TaxonName name1,
156
            TaxonName name2) {
157

    
158
        if (basionym.equals(name1)){
159
            return -1;
160
        }else if (basionym.equals(name2)){
161
            return 1;
162
        }else{
163
            super.compare(name1, name2, false);
164
        }
165
        return 0;
166
    }
167

    
168
    /**
169
     * @param basionym1
170
     * @param basionym2
171
     * @return
172
     */
173
    private int compareBasionyms(TaxonName basionym1Orig, TaxonName basionym2Orig) {
174
        //one taxon is first in group
175
        TaxonName basionym1 = getFirstNameInGroup(basionym1Orig);
176
        TaxonName basionym2 = getFirstNameInGroup(basionym2Orig);
177

    
178
        //handle accepted taxon case
179
        if (basionym1.equals(firstNameInGroup)){
180
            return -1;
181
        }else if (basionym2.equals(firstNameInGroup)){
182
            return 1;
183
        }
184

    
185
        //handle replaced synonyms
186
        boolean basio2IsReplacedSynForBasio1 = getReplacedSynonymClosure(basionym1).contains(basionym2);
187
        boolean basio1IsReplacedSynForBasio2 = getReplacedSynonymClosure(basionym2).contains(basionym1);
188

    
189
        if (basio2IsReplacedSynForBasio1 && !basio1IsReplacedSynForBasio2){
190
            return 1;
191
        }else if (basio1IsReplacedSynForBasio2 && !basio2IsReplacedSynForBasio1){
192
            return -1;
193
        }
194

    
195
        //compare by date, nom. illeg., rank and alphabetically
196
        return super.compare(basionym1, basionym2, true);
197

    
198
    }
199

    
200
    /**
201
     * @param basionym
202
     * @return
203
     */
204
    private TaxonName getFirstNameInGroup(TaxonName<?,?> basionym) {
205
        for (NameRelationship nameRel : basionym.getRelationsFromThisName()){
206
            if (nameRel.getType() != null && nameRel.getType().equals(NameRelationshipType.BASIONYM())){
207
                if (nameRel.getToName().equals(firstNameInGroup)){
208
                    return firstNameInGroup;
209
                }
210
            }
211
        }
212
        return basionym;
213
    }
214

    
215
    /**
216
     * @param basionym1
217
     * @return
218
     */
219
    @SuppressWarnings("rawtypes")
220
    private Set<TaxonName> getReplacedSynonymClosure(TaxonName<?, ?> name) {
221
        Set<TaxonName> set = name.getReplacedSynonyms();
222
        if (set.isEmpty()){
223
            return set;
224
        }
225
        Set<TaxonName> result = new HashSet<>();
226
        for (TaxonName<?,?> replSyn : set){
227
            boolean notYetContained = result.add(replSyn);
228
            if (notYetContained){
229
                result.addAll(replSyn.getReplacedSynonyms());
230
            }
231
        }
232
        return result;
233
    }
234

    
235
    /**
236
     * @param name
237
     * @return
238
     */
239
    private TaxonName getPreferredInBasionymGroup(TaxonName<?,?> name) {
240
        Set<TaxonName<?,?>> candidates = new HashSet<>();
241
        //get all final basionyms, except for those being part of a basionym circle
242
        for (TaxonName<?,?> candidate : name.getBasionyms()){
243
            if (candidate != null
244
                    && candidate.getHomotypicalGroup().equals(name.getHomotypicalGroup())
245
                    && !hasBasionymCircle(candidate, null)){
246
                candidate = getPreferredInBasionymGroup(candidate);
247
                candidates.add(candidate);
248
            }
249
        }
250

    
251
        if (candidates.isEmpty()){
252
            return name;
253
        }else if (candidates.size() == 1){
254
            return candidates.iterator().next();
255
        }else{
256
            TaxonName result = candidates.iterator().next();
257
            candidates.remove(result);
258
            for (TaxonName<?,?> candidate : candidates){
259
                if (super.compare(result, candidate, false) > 0){
260
                    result = candidate;
261
                }
262
            }
263
            return result;
264
        }
265
    }
266

    
267
    /**
268
     * @param candidate
269
     * @return
270
     */
271
    private boolean hasBasionymCircle(TaxonName<?, ?> name, Set<TaxonName> existing) {
272
        if (existing == null){
273
            existing = new HashSet<>();
274
        }
275
        if (existing.contains(name)){
276
            return true;
277
        }else{
278
            Set<TaxonName> basionyms = name.getBasionyms();
279
            if (basionyms.isEmpty()){
280
                return false;
281
            }
282
            existing.add(name);
283
            for (TaxonName basionym : basionyms){
284
                if (hasBasionymCircle(basionym, existing)){
285
                    return true;
286
                }
287
            }
288
            return false;
289
        }
290
    }
291

    
292

    
293
//  /**
294
//   * @param homotypicalGroup
295
//   * @return
296
//   */
297
//  private TaxonBase<?> getFirstInHomotypicalGroup(HomotypicalGroup homotypicalGroup, Collection<TaxonBase<?>> existing) {
298
//      List<TaxonBase<?>> candidates =  new ArrayList<TaxonBase<?>>();
299
//      for (TaxonBase<?> candidate : existing){
300
//          if (homotypicalGroup.getTypifiedNames().contains(candidate.getName())){
301
//              candidates.add(candidate);
302
//          }
303
//      }
304
//      Collections.sort(candidates, this);
305
//      return candidates.isEmpty() ? null : candidates.get(0);
306
//  }
307

    
308
}
(3-3/20)