Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.query.change; 16 : 17 : import static com.google.common.base.Preconditions.checkState; 18 : import static java.util.Objects.requireNonNull; 19 : 20 : import com.google.gerrit.common.Nullable; 21 : import com.google.gerrit.entities.Change; 22 : import com.google.gerrit.entities.Change.Status; 23 : import com.google.gerrit.index.query.HasCardinality; 24 : import com.google.gerrit.index.query.Predicate; 25 : import com.google.gerrit.index.query.QueryParseException; 26 : import com.google.gerrit.server.index.change.ChangeField; 27 : import java.util.ArrayList; 28 : import java.util.List; 29 : import java.util.Map; 30 : import java.util.NavigableMap; 31 : import java.util.Objects; 32 : import java.util.TreeMap; 33 : 34 : /** 35 : * Predicate for a {@link Status}. 36 : * 37 : * <p>The actual name of this operator can differ, it usually comes as {@code status:} but may also 38 : * be {@code is:} to help do-what-i-meanery for end-users searching for changes. Either operator 39 : * name has the same meaning. 40 : * 41 : * <p>Status names are looked up by prefix case-insensitively. 42 : */ 43 : public final class ChangeStatusPredicate extends ChangeIndexPredicate implements HasCardinality { 44 : private static final String INVALID_STATUS = "__invalid__"; 45 113 : static final Predicate<ChangeData> NONE = new ChangeStatusPredicate(null); 46 : 47 : private static final TreeMap<String, Predicate<ChangeData>> PREDICATES; 48 : private static final Predicate<ChangeData> CLOSED; 49 : private static final Predicate<ChangeData> OPEN; 50 : 51 : static { 52 113 : PREDICATES = new TreeMap<>(); 53 113 : List<Predicate<ChangeData>> open = new ArrayList<>(); 54 113 : List<Predicate<ChangeData>> closed = new ArrayList<>(); 55 : 56 113 : for (Change.Status s : Change.Status.values()) { 57 113 : ChangeStatusPredicate p = forStatus(s); 58 113 : String str = canonicalize(s); 59 113 : checkState( 60 113 : !INVALID_STATUS.equals(str), 61 : "invalid status sentinel %s cannot match canonicalized status string %s", 62 : INVALID_STATUS, 63 : str); 64 113 : PREDICATES.put(str, p); 65 113 : (s.isOpen() ? open : closed).add(p); 66 : } 67 : 68 113 : CLOSED = Predicate.or(closed); 69 113 : OPEN = Predicate.or(open); 70 : 71 113 : PREDICATES.put("closed", CLOSED); 72 113 : PREDICATES.put("open", OPEN); 73 113 : PREDICATES.put("pending", OPEN); 74 113 : } 75 : 76 : public static String canonicalize(Change.Status status) { 77 113 : return status.name().toLowerCase(); 78 : } 79 : 80 : public static Predicate<ChangeData> parse(String value) throws QueryParseException { 81 18 : String lower = value.toLowerCase(); 82 18 : NavigableMap<String, Predicate<ChangeData>> head = PREDICATES.tailMap(lower, true); 83 18 : if (!head.isEmpty()) { 84 : // Assume no statuses share a common prefix so we can only walk one entry. 85 18 : Map.Entry<String, Predicate<ChangeData>> e = head.entrySet().iterator().next(); 86 18 : if (e.getKey().startsWith(lower)) { 87 17 : return e.getValue(); 88 : } 89 : } 90 5 : throw new QueryParseException("Unrecognized value: " + value); 91 : } 92 : 93 : public static Predicate<ChangeData> open() { 94 105 : return OPEN; 95 : } 96 : 97 : public static Predicate<ChangeData> closed() { 98 7 : return CLOSED; 99 : } 100 : 101 : public static ChangeStatusPredicate forStatus(Change.Status status) { 102 113 : return new ChangeStatusPredicate(requireNonNull(status)); 103 : } 104 : 105 : @Nullable private final Change.Status status; 106 : 107 : private ChangeStatusPredicate(@Nullable Change.Status status) { 108 113 : super(ChangeField.STATUS_SPEC, status != null ? canonicalize(status) : INVALID_STATUS); 109 113 : this.status = status; 110 113 : } 111 : 112 : /** 113 : * Get the status for this predicate. 114 : * 115 : * @return the status, or null if this predicate is intended to never match any changes. 116 : */ 117 : @Nullable 118 : public Change.Status getStatus() { 119 9 : return status; 120 : } 121 : 122 : @Override 123 : public boolean match(ChangeData object) { 124 81 : Change change = object.change(); 125 81 : return change != null && Objects.equals(status, change.getStatus()); 126 : } 127 : 128 : @Override 129 : public int hashCode() { 130 0 : return Objects.hashCode(status); 131 : } 132 : 133 : @Override 134 : public boolean equals(Object other) { 135 1 : return (other instanceof ChangeStatusPredicate) 136 1 : && Objects.equals(status, ((ChangeStatusPredicate) other).status); 137 : } 138 : 139 : @Override 140 : public String toString() { 141 8 : return getOperator() + ":" + getValue(); 142 : } 143 : 144 : @Override 145 : public int getCardinality() { 146 8 : if (getStatus() == null) { 147 2 : return 0; 148 : } 149 8 : switch (getStatus()) { 150 : case MERGED: 151 7 : return 50_000; 152 : case ABANDONED: 153 7 : return 50_000; 154 : case NEW: 155 : default: 156 8 : return 2000; 157 : } 158 : } 159 : }