Project

General

Profile

Download (5.53 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2018 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
package eu.etaxonomy.cdm.model.metadata;
10

    
11
import java.util.ArrayList;
12
import java.util.List;
13

    
14
import eu.etaxonomy.cdm.common.CdmUtils;
15
import eu.etaxonomy.cdm.model.metadata.CdmPreference.PrefKey;
16

    
17
/**
18
 * @author a.mueller
19
 * @since 28.11.2018
20
 */
21
public class PreferenceResolver {
22

    
23
    protected static final String MULTI_BEST_MATCHING = "There are 2 best matching preferences with equal key but differing values";
24

    
25
    /**
26
     * Returns the best matching {@link CdmPreference preference} for the
27
     * given preference list and for the given {@link PrefKey preference key}
28
     * or <code>null</code> if none is matching.<BR>
29
     * A preference is matching if the preference key is matching the given key.
30
     * Keys are matching if they {@link PreferencePredicate predicates} are
31
     * equal and if the given {@link PreferenceSubject subject} matches
32
     * the preferences subject.<BR>
33
     * A subject A matches another subject B if all parts of A can also be found
34
     * in B in the same order. However, A may have parts that can not be found in B
35
     * but still it matches. But B must not have parts that can not be found in A
36
     * and all parts of B must be in the same order as in A otherwise it does not match.<BR>
37
     * The <b>best</b> key match is computed from the back recursively.
38
     * If the last part matches it matches better then matching only the second but last.
39
     * If !=1 keys  match the last part the best matching is computed on
40
     * the first n-1 parts of the key.<BR>
41
     *
42
     * If key or one of its parts is <code>null</code>, <code>null</code> is returned.
43
     *
44
     * @return the best matching preference
45
     * @throws IllegalArgumentException if the given preferences list contains 2 preference
46
     *  with completely equal best matching keys but with different values.
47
     */
48
    public static CdmPreference resolve(List<CdmPreference> preferences, PrefKey key) throws IllegalArgumentException{
49
        if (key == null ||key.getPredicate() == null || key.getSubject() == null){
50
            return null;
51
        }
52

    
53
        List<CdmPreference> matchingPreferences = new ArrayList<>();
54
        for (CdmPreference preference : preferences){
55
            if (preference == null|| preference.getKey()== null){
56
                continue;
57
            }
58
            if (key.getPredicate().equals(preference.getKey().getPredicate())){
59
                if (subjectMatches(PreferenceSubject.NewInstance(key.getSubject()), PreferenceSubject.NewInstance(preference.getKey().getSubject()))){
60
                    matchingPreferences.add(preference);
61
                }
62
            }
63
        }
64
        CdmPreference bestMatching = null;
65
        boolean multipleBestMatching = false;
66
        for (CdmPreference preference : matchingPreferences){
67
            if (bestMatching == null){
68
                bestMatching = preference;
69
                continue;
70
            }else{
71
                int c = compare(PreferenceSubject.fromPreference(preference), PreferenceSubject.fromPreference(bestMatching), PreferenceSubject.fromKey(key) );
72
                if (c < 0){
73
                    bestMatching = preference;
74
                    multipleBestMatching = false;
75
                }else if (c == 0){
76
                    if(!CdmUtils.nullSafeEqual(preference.getValue(), bestMatching.getValue())||
77
                            preference.isAllowOverride()!= bestMatching.isAllowOverride()){
78
                        multipleBestMatching = true;
79
                    }
80
                }else{
81
                    multipleBestMatching = false;
82
                }
83
            }
84
        }
85
        if (multipleBestMatching){
86
            throw new IllegalArgumentException(MULTI_BEST_MATCHING);
87
        }
88
        return bestMatching;
89
    }
90

    
91

    
92
    /**
93
     * Compares 2 subjects. Returns a value < 0, if subject1 is better matching,
94
     * returns a value >0 if subject2 is better matching.
95
     * Returns 0 if both subjects are equal.
96
     * @param subject1
97
     * @param subject2
98
     * @return
99
     */
100
    private static int compare(PreferenceSubject subject1, PreferenceSubject subject2,
101
            PreferenceSubject compareAgainst) {
102

    
103
        String last = compareAgainst.getLastPart();
104
        String last1 = subject1.getLastPart();
105
        String last2 = subject2.getLastPart();
106
        if (compareAgainst.isRoot()){
107
            return 0;
108
        }
109
        if (last.equals(last1)){
110
            if (!last.equals(last2)){
111
                return -1;
112
            }else{
113
                return compare(subject1.getNextHigher(), subject2.getNextHigher(), compareAgainst.getNextHigher());
114
            }
115
        }else if(last.equals(last2)){
116
            return 1;
117
        }else{
118
            return compare(subject1, subject2, compareAgainst.getNextHigher());
119
        }
120
    }
121

    
122
    /**
123
     * @param subject
124
     * @param subject2
125
     * @return
126
     */
127
    private static boolean subjectMatches(PreferenceSubject subjectA, PreferenceSubject subjectB) {
128
        List<String> partsA = subjectA.getParts();
129
        List<String> partsB = subjectB.getParts();
130
        for (int a = partsA.size()-1 ; a>=0 ; a--){
131
            String lastA = partsA.get(a);
132
            String lastB = partsB.get(partsB.size()-1);
133
            if (lastA.equals(lastB) && !partsB.isEmpty()){
134
                partsB = partsB.subList(0, partsB.size()-1);
135
            }
136
        }
137
        return partsB.isEmpty();
138
    }
139
}
(9-9/18)