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.predicate;
015
016import org.gbif.api.annotation.Experimental;
017import org.gbif.api.model.common.search.SearchParameter;
018import org.gbif.api.util.SearchTypeValidator;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Objects;
024import java.util.Optional;
025import java.util.StringJoiner;
026
027import javax.annotation.Nullable;
028import javax.validation.constraints.NotNull;
029import javax.validation.constraints.Size;
030
031import com.fasterxml.jackson.annotation.JsonCreator;
032import com.fasterxml.jackson.annotation.JsonProperty;
033
034import io.swagger.v3.oas.annotations.media.Schema;
035
036import static org.gbif.api.util.PreconditionUtils.checkArgument;
037
038/**
039 * This predicate checks if its {@code key} contains any of its {@code values}.
040 */
041@Schema(
042  description = "This predicate checks if its `key` is equal to any of its `values`."
043)
044public class InPredicate<S extends SearchParameter> implements Predicate {
045
046  @Schema(
047    description = "The search parameter to test."
048  )
049  @NotNull private final S key;
050
051  @Schema(
052    description = "The list of value to test for."
053  )
054  @NotNull
055  @Size(min = 1)
056  private final Collection<String> values;
057
058  @Schema(
059    description = "Whether to match letter case (UPPER or lower case) on string value comparisons."
060  )
061  @Experimental
062  @Nullable
063  private final Boolean matchCase;
064
065  @JsonCreator
066  public InPredicate(
067      @JsonProperty("key") S key,
068      @JsonProperty("values") Collection<String> values,
069      @JsonProperty(value = "matchCase") Boolean matchCase) {
070    this.matchCase = matchCase;
071    Objects.requireNonNull(key, "<key> may not be null");
072    Objects.requireNonNull(values, "<values> may not be null");
073    checkArgument(!values.isEmpty(), "<values> may not be empty");
074    // make sure the value is of the right type according to the key given
075    for (String value : values) {
076      if (value == null) {
077        throw new NullPointerException();
078      }
079      SearchTypeValidator.validate(key, value);
080    }
081
082    this.key = key;
083    this.values = Collections.unmodifiableList(new ArrayList<>(values));
084  }
085
086  public S getKey() {
087    return key;
088  }
089
090  public Collection<String> getValues() {
091    return values;
092  }
093
094  /**
095   * This flag enables the use of case-sensitive matches and aggregations on certain search parameters.
096   * <p>
097   * Fields that support this feature are: occurrenceId, recordedBy, samplingProtocol, catalogNumber, collectionCode,
098   * institutionCode, eventId, parentEventId, waterBody, stateProvince, recordNumber, identifiedBy, organismId and locality.
099   * <p>
100   * This is an experimental feature and its implementation map change or be removed at any time.
101   */
102  @Experimental
103  public Boolean isMatchCase() {
104    return Optional.ofNullable(matchCase).orElse(Boolean.FALSE);
105  }
106
107  @Override
108  public boolean equals(Object o) {
109    if (this == o) {
110      return true;
111    }
112    if (o == null || getClass() != o.getClass()) {
113      return false;
114    }
115    InPredicate<S> that = (InPredicate<S>) o;
116    return key == that.key && Objects.equals(values, that.values) && matchCase == that.matchCase;
117  }
118
119  @Override
120  public int hashCode() {
121    return Objects.hash(key, values, matchCase);
122  }
123
124  @Override
125  public String toString() {
126    return new StringJoiner(", ", InPredicate.class.getSimpleName() + "[", "]")
127      .add("key=" + key)
128      .add("values=" + values)
129      .add("matchCase=" + matchCase)
130      .toString();
131  }
132}