001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.hbtop;
019
020import java.util.Arrays;
021import java.util.List;
022import java.util.Objects;
023import org.apache.hadoop.hbase.hbtop.field.Field;
024import org.apache.hadoop.hbase.hbtop.field.FieldValue;
025import org.apache.yetus.audience.InterfaceAudience;
026
027
028/**
029 * Represents a filter that's filtering the metric {@link Record}s.
030 */
031@InterfaceAudience.Private
032public final class RecordFilter {
033
034  private enum Operator {
035    EQUAL("="),
036    DOUBLE_EQUALS("=="),
037    GREATER(">"),
038    GREATER_OR_EQUAL(">="),
039    LESS("<"),
040    LESS_OR_EQUAL("<=");
041
042    private final String operator;
043
044    Operator(String operator) {
045      this.operator = operator;
046    }
047
048    @Override
049    public String toString() {
050      return operator;
051    }
052  }
053
054  public static RecordFilter parse(String filterString, boolean ignoreCase) {
055    return parse(filterString, Arrays.asList(Field.values()), ignoreCase);
056  }
057
058  /*
059   * Parse a filter string and build a RecordFilter instance.
060   */
061  public static RecordFilter parse(String filterString, List<Field> fields, boolean ignoreCase) {
062    int index = 0;
063
064    boolean not = isNot(filterString);
065    if (not) {
066      index += 1;
067    }
068
069    StringBuilder fieldString = new StringBuilder();
070    while (filterString.length() > index && filterString.charAt(index) != '<'
071      && filterString.charAt(index) != '>' && filterString.charAt(index) != '=') {
072      fieldString.append(filterString.charAt(index++));
073    }
074
075    if (fieldString.length() == 0 || filterString.length() == index) {
076      return null;
077    }
078
079    Field field = getField(fields, fieldString.toString());
080    if (field == null) {
081      return null;
082    }
083
084    StringBuilder operatorString = new StringBuilder();
085    while (filterString.length() > index && (filterString.charAt(index) == '<' ||
086      filterString.charAt(index) == '>' || filterString.charAt(index) == '=')) {
087      operatorString.append(filterString.charAt(index++));
088    }
089
090    Operator operator = getOperator(operatorString.toString());
091    if (operator == null) {
092      return null;
093    }
094
095    String value = filterString.substring(index);
096    FieldValue fieldValue = getFieldValue(field, value);
097    if (fieldValue == null) {
098      return null;
099    }
100
101    return new RecordFilter(ignoreCase, not, field, operator, fieldValue);
102  }
103
104  private static FieldValue getFieldValue(Field field, String value) {
105    try {
106      return field.newValue(value);
107    } catch (Exception e) {
108      return null;
109    }
110  }
111
112  private static boolean isNot(String filterString) {
113    return filterString.startsWith("!");
114  }
115
116  private static Field getField(List<Field> fields, String fieldString) {
117    for (Field f : fields) {
118      if (f.getHeader().equals(fieldString)) {
119        return f;
120      }
121    }
122    return null;
123  }
124
125  private static Operator getOperator(String operatorString) {
126    for (Operator o : Operator.values()) {
127      if (operatorString.equals(o.toString())) {
128        return o;
129      }
130    }
131    return null;
132  }
133
134  private final boolean ignoreCase;
135  private final boolean not;
136  private final Field field;
137  private final Operator operator;
138  private final FieldValue value;
139
140  private RecordFilter(boolean ignoreCase, boolean not, Field field, Operator operator,
141    FieldValue value) {
142    this.ignoreCase = ignoreCase;
143    this.not = not;
144    this.field = Objects.requireNonNull(field);
145    this.operator = Objects.requireNonNull(operator);
146    this.value = Objects.requireNonNull(value);
147  }
148
149  public boolean execute(Record record) {
150    FieldValue fieldValue = record.get(field);
151    if (fieldValue == null) {
152      return false;
153    }
154
155    if (operator == Operator.EQUAL) {
156      boolean ret;
157      if (ignoreCase) {
158        ret = fieldValue.asString().toLowerCase().contains(value.asString().toLowerCase());
159      } else {
160        ret = fieldValue.asString().contains(value.asString());
161      }
162      return not != ret;
163    }
164
165    int compare = ignoreCase ?
166      fieldValue.compareToIgnoreCase(value) : fieldValue.compareTo(value);
167
168    boolean ret;
169    switch (operator) {
170      case DOUBLE_EQUALS:
171        ret = compare == 0;
172        break;
173
174      case GREATER:
175        ret = compare > 0;
176        break;
177
178      case GREATER_OR_EQUAL:
179        ret = compare >= 0;
180        break;
181
182      case LESS:
183        ret = compare < 0;
184        break;
185
186      case LESS_OR_EQUAL:
187        ret = compare <= 0;
188        break;
189
190      default:
191        throw new AssertionError();
192    }
193    return not != ret;
194  }
195
196  @Override
197  public String toString() {
198    return (not ? "!" : "") + field.getHeader() + operator + value.asString();
199  }
200
201  @Override
202  public boolean equals(Object o) {
203    if (this == o) {
204      return true;
205    }
206    if (!(o instanceof RecordFilter)) {
207      return false;
208    }
209    RecordFilter filter = (RecordFilter) o;
210    return ignoreCase == filter.ignoreCase && not == filter.not && field == filter.field
211      && operator == filter.operator && value.equals(filter.value);
212  }
213
214  @Override
215  public int hashCode() {
216    return Objects.hash(ignoreCase, not, field, operator, value);
217  }
218
219  /*
220   * For FilterBuilder
221   */
222  public static FilterBuilder newBuilder(Field field) {
223    return new FilterBuilder(field, false);
224  }
225
226  public static FilterBuilder newBuilder(Field field, boolean ignoreCase) {
227    return new FilterBuilder(field, ignoreCase);
228  }
229
230  public static final class FilterBuilder {
231    private final Field field;
232    private final boolean ignoreCase;
233
234    private FilterBuilder(Field field, boolean ignoreCase) {
235      this.field = Objects.requireNonNull(field);
236      this.ignoreCase = ignoreCase;
237    }
238
239    public RecordFilter equal(FieldValue value) {
240      return newFilter(false, Operator.EQUAL, value);
241    }
242
243    public RecordFilter equal(Object value) {
244      return equal(field.newValue(value));
245    }
246
247    public RecordFilter notEqual(FieldValue value) {
248      return newFilter(true, Operator.EQUAL, value);
249    }
250
251    public RecordFilter notEqual(Object value) {
252      return notEqual(field.newValue(value));
253    }
254
255    public RecordFilter doubleEquals(FieldValue value) {
256      return newFilter(false, Operator.DOUBLE_EQUALS, value);
257    }
258
259    public RecordFilter doubleEquals(Object value) {
260      return doubleEquals(field.newValue(value));
261    }
262
263    public RecordFilter notDoubleEquals(FieldValue value) {
264      return newFilter(true, Operator.DOUBLE_EQUALS, value);
265    }
266
267    public RecordFilter notDoubleEquals(Object value) {
268      return notDoubleEquals(field.newValue(value));
269    }
270
271    public RecordFilter greater(FieldValue value) {
272      return newFilter(false, Operator.GREATER, value);
273    }
274
275    public RecordFilter greater(Object value) {
276      return greater(field.newValue(value));
277    }
278
279    public RecordFilter notGreater(FieldValue value) {
280      return newFilter(true, Operator.GREATER, value);
281    }
282
283    public RecordFilter notGreater(Object value) {
284      return notGreater(field.newValue(value));
285    }
286
287    public RecordFilter greaterOrEqual(FieldValue value) {
288      return newFilter(false, Operator.GREATER_OR_EQUAL, value);
289    }
290
291    public RecordFilter greaterOrEqual(Object value) {
292      return greaterOrEqual(field.newValue(value));
293    }
294
295    public RecordFilter notGreaterOrEqual(FieldValue value) {
296      return newFilter(true, Operator.GREATER_OR_EQUAL, value);
297    }
298
299    public RecordFilter notGreaterOrEqual(Object value) {
300      return notGreaterOrEqual(field.newValue(value));
301    }
302
303    public RecordFilter less(FieldValue value) {
304      return newFilter(false, Operator.LESS, value);
305    }
306
307    public RecordFilter less(Object value) {
308      return less(field.newValue(value));
309    }
310
311    public RecordFilter notLess(FieldValue value) {
312      return newFilter(true, Operator.LESS, value);
313    }
314
315    public RecordFilter notLess(Object value) {
316      return notLess(field.newValue(value));
317    }
318
319    public RecordFilter lessOrEqual(FieldValue value) {
320      return newFilter(false, Operator.LESS_OR_EQUAL, value);
321    }
322
323    public RecordFilter lessOrEqual(Object value) {
324      return lessOrEqual(field.newValue(value));
325    }
326
327    public RecordFilter notLessOrEqual(FieldValue value) {
328      return newFilter(true, Operator.LESS_OR_EQUAL, value);
329    }
330
331    public RecordFilter notLessOrEqual(Object value) {
332      return notLessOrEqual(field.newValue(value));
333    }
334
335    private RecordFilter newFilter(boolean not, Operator operator, FieldValue value) {
336      return new RecordFilter(ignoreCase, not, field, operator, value);
337    }
338  }
339}