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", defaultValue = "false") 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      SearchTypeValidator.validate(key, value);
077    }
078
079    this.key = key;
080    this.values = Collections.unmodifiableList(new ArrayList<>(values));
081  }
082
083  public S getKey() {
084    return key;
085  }
086
087  public Collection<String> getValues() {
088    return values;
089  }
090
091  /**
092   * This flag enables the use of case-sensitive matches and aggregations on certain search parameters.
093   * <p>
094   * Fields that support this feature are: occurrenceId, recordedBy, samplingProtocol, catalogNumber, collectionCode,
095   * institutionCode, eventId, parentEventId, waterBody, stateProvince, recordNumber, identifiedBy, organismId and locality.
096   * <p>
097   * This is an experimental feature and its implementation map change or be removed at any time.
098   */
099  @Experimental
100  public Boolean isMatchCase() {
101    return Optional.ofNullable(matchCase).orElse(Boolean.FALSE);
102  }
103
104  @Override
105  public boolean equals(Object o) {
106    if (this == o) {
107      return true;
108    }
109    if (o == null || getClass() != o.getClass()) {
110      return false;
111    }
112    InPredicate<S> that = (InPredicate<S>) o;
113    return key == that.key && Objects.equals(values, that.values) && matchCase == that.matchCase;
114  }
115
116  @Override
117  public int hashCode() {
118    return Objects.hash(key, values);
119  }
120
121  @Override
122  public String toString() {
123    return new StringJoiner(", ", InPredicate.class.getSimpleName() + "[", "]")
124      .add("key=" + key)
125      .add("values=" + values)
126      .add("matchCase=" + matchCase)
127      .toString();
128  }
129}