001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.api.model.collections.lookup;
015
016import java.util.Arrays;
017import java.util.Comparator;
018import java.util.HashSet;
019import java.util.Objects;
020import java.util.Set;
021import java.util.StringJoiner;
022
023/**
024 * Used in the {@link LookupResult} to express how good an entity matches. It also adds some remarks
025 * to help understand how the match was done.
026 *
027 * @param <T> it can be parameterized. To be used mainly with {@link
028 *     org.gbif.api.model.collections.Collection} or {@link
029 *     org.gbif.api.model.collections.Institution}
030 */
031public class Match<T extends EntityMatched> {
032
033  public static final Comparator<MatchType> MATCH_TYPE_COMPARATOR =
034      (t1, t2) -> {
035        if (t1 == null) {
036          return t2 == null ? 0 : 1;
037        } else if (t2 == null) {
038          return -1;
039        }
040
041        if (t1 == t2) {
042          return 0;
043        }
044        if (t1 == Match.MatchType.EXACT) {
045          return -1;
046        }
047        if (t2 == Match.MatchType.EXACT) {
048          return 1;
049        }
050        return t1.compareTo(t2);
051      };
052
053  private MatchType matchType;
054  private Status status;
055  private Set<Reason> reasons;
056  private T entityMatched;
057
058  public static <T extends EntityMatched> Match<T> exact(T entity, Reason... reasons) {
059    return exact(entity, new HashSet<>(Arrays.asList(reasons)));
060  }
061
062  public static <T extends EntityMatched> Match<T> exact(T entity, Set<Reason> reasons) {
063    Match<T> match = new Match<>();
064    match.setEntityMatched(entity);
065    match.setMatchType(MatchType.EXACT);
066    if (reasons != null) {
067      match.setReasons(reasons);
068    }
069    return match;
070  }
071
072  public static <T extends EntityMatched> Match<T> fuzzy(T entity, Reason... reasons) {
073    return fuzzy(entity, new HashSet<>(Arrays.asList(reasons)));
074  }
075
076  public static <T extends EntityMatched> Match<T> fuzzy(T entity, Set<Reason> reasons) {
077    Match<T> match = new Match<>();
078    match.setEntityMatched(entity);
079    match.setMatchType(MatchType.FUZZY);
080    if (reasons != null) {
081      match.setReasons(reasons);
082    }
083    return match;
084  }
085
086  public static <T extends EntityMatched> Match<T> none() {
087    Match<T> match = new Match<>();
088    match.setMatchType(MatchType.NONE);
089    return match;
090  }
091
092  public static <T extends EntityMatched> Match<T> none(Status status) {
093    Match<T> match = new Match<>();
094    match.setMatchType(MatchType.NONE);
095    match.setStatus(status);
096    return match;
097  }
098
099  public static <T extends EntityMatched> Match<T> explicitMapping(T entity, Reason... reasons) {
100    return explicitMapping(entity, new HashSet<>(Arrays.asList(reasons)));
101  }
102
103  public static <T extends EntityMatched> Match<T> explicitMapping(T entity, Set<Reason> reasons) {
104    Match<T> match = new Match<>();
105    match.setEntityMatched(entity);
106    match.setMatchType(MatchType.EXPLICIT_MAPPING);
107    if (reasons != null) {
108      match.setReasons(reasons);
109    }
110    return match;
111  }
112
113  public MatchType getMatchType() {
114    return matchType;
115  }
116
117  public void setMatchType(MatchType matchType) {
118    this.matchType = matchType;
119  }
120
121  public Status getStatus() {
122    return status;
123  }
124
125  public void setStatus(Status status) {
126    this.status = status;
127  }
128
129  public Set<Reason> getReasons() {
130    return reasons;
131  }
132
133  public void setReasons(Set<Reason> reasons) {
134    this.reasons = reasons;
135  }
136
137  public Match<T> addReason(Reason reason) {
138    if (reasons == null) {
139      reasons = new HashSet<>();
140    }
141    reasons.add(reason);
142    return this;
143  }
144
145  public T getEntityMatched() {
146    return entityMatched;
147  }
148
149  public void setEntityMatched(T entityMatched) {
150    this.entityMatched = entityMatched;
151  }
152
153  public enum MatchType {
154    EXACT,
155    FUZZY,
156    EXPLICIT_MAPPING,
157    NONE;
158  }
159
160  public enum Reason {
161    CODE_MATCH,
162    IDENTIFIER_MATCH,
163    ALTERNATIVE_CODE_MATCH,
164    NAME_MATCH,
165    KEY_MATCH,
166    DIFFERENT_OWNER,
167    BELONGS_TO_INSTITUTION_MATCHED,
168    INST_COLL_MISMATCH,
169    COUNTRY_MATCH
170  }
171
172  public enum Status {
173    ACCEPTED,
174    AMBIGUOUS,
175    AMBIGUOUS_EXPLICIT_MAPPINGS,
176    AMBIGUOUS_OWNER,
177    AMBIGUOUS_INSTITUTION_MISMATCH,
178    DOUBTFUL;
179  }
180
181  @Override
182  public boolean equals(Object o) {
183    if (this == o) {
184      return true;
185    }
186    if (o == null || getClass() != o.getClass()) {
187      return false;
188    }
189    Match<?> match = (Match<?>) o;
190    return matchType == match.matchType
191        && status == match.status
192        && Objects.equals(reasons, match.reasons)
193        && Objects.equals(entityMatched, match.entityMatched);
194  }
195
196  @Override
197  public int hashCode() {
198    return Objects.hash(matchType, status, reasons, entityMatched);
199  }
200
201  @Override
202  public String toString() {
203    return new StringJoiner(", ", Match.class.getSimpleName() + "[", "]")
204        .add("matchType=" + matchType)
205        .add("status=" + status)
206        .add("reasons=" + reasons)
207        .add("entityMatched=" + entityMatched)
208        .toString();
209  }
210}