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 * Represents a filter that's filtering the metric {@link Record}s.
029 */
030@InterfaceAudience.Private
031public final class RecordFilter {
032
033  private enum Operator {
034    EQUAL("="),
035    DOUBLE_EQUALS("=="),
036    GREATER(">"),
037    GREATER_OR_EQUAL(">="),
038    LESS("<"),
039    LESS_OR_EQUAL("<=");
040
041    private final String operator;
042
043    Operator(String operator) {
044      this.operator = operator;
045    }
046
047    @Override
048    public String toString() {
049      return operator;
050    }
051  }
052
053  public static RecordFilter parse(String filterString, boolean ignoreCase) {
054    return parse(filterString, Arrays.asList(Field.values()), ignoreCase);
055  }
056
057  /*
058   * Parse a filter string and build a RecordFilter instance.
059   */
060  public static RecordFilter parse(String filterString, List<Field> fields, boolean ignoreCase) {
061    int index = 0;
062
063    boolean not = isNot(filterString);
064    if (not) {
065      index += 1;
066    }
067
068    StringBuilder fieldString = new StringBuilder();
069    while (
070      filterString.length() > index && filterString.charAt(index) != '<'
071        && filterString.charAt(index) != '>' && filterString.charAt(index) != '='
072    ) {
073      fieldString.append(filterString.charAt(index++));
074    }
075
076    if (fieldString.length() == 0 || filterString.length() == index) {
077      return null;
078    }
079
080    Field field = getField(fields, fieldString.toString());
081    if (field == null) {
082      return null;
083    }
084
085    StringBuilder operatorString = new StringBuilder();
086    while (
087      filterString.length() > index && (filterString.charAt(index) == '<'
088        || filterString.charAt(index) == '>' || filterString.charAt(index) == '=')
089    ) {
090      operatorString.append(filterString.charAt(index++));
091    }
092
093    Operator operator = getOperator(operatorString.toString());
094    if (operator == null) {
095      return null;
096    }
097
098    String value = filterString.substring(index);
099    FieldValue fieldValue = getFieldValue(field, value);
100    if (fieldValue == null) {
101      return null;
102    }
103
104    return new RecordFilter(ignoreCase, not, field, operator, fieldValue);
105  }
106
107  private static FieldValue getFieldValue(Field field, String value) {
108    try {
109      return field.newValue(value);
110    } catch (Exception e) {
111      return null;
112    }
113  }
114
115  private static boolean isNot(String filterString) {
116    return filterString.startsWith("!");
117  }
118
119  private static Field getField(List<Field> fields, String fieldString) {
120    for (Field f : fields) {
121      if (f.getHeader().equals(fieldString)) {
122        return f;
123      }
124    }
125    return null;
126  }
127
128  private static Operator getOperator(String operatorString) {
129    for (Operator o : Operator.values()) {
130      if (operatorString.equals(o.toString())) {
131        return o;
132      }
133    }
134    return null;
135  }
136
137  private final boolean ignoreCase;
138  private final boolean not;
139  private final Field field;
140  private final Operator operator;
141  private final FieldValue value;
142
143  private RecordFilter(boolean ignoreCase, boolean not, Field field, Operator operator,
144    FieldValue value) {
145    this.ignoreCase = ignoreCase;
146    this.not = not;
147    this.field = Objects.requireNonNull(field);
148    this.operator = Objects.requireNonNull(operator);
149    this.value = Objects.requireNonNull(value);
150  }
151
152  public Field getField() {
153    return field;
154  }
155
156  public boolean execute(Record record) {
157    FieldValue fieldValue = record.get(field);
158    if (fieldValue == null) {
159      return false;
160    }
161
162    if (operator == Operator.EQUAL) {
163      boolean ret;
164      if (ignoreCase) {
165        ret = fieldValue.asString().toLowerCase().contains(value.asString().toLowerCase());
166      } else {
167        ret = fieldValue.asString().contains(value.asString());
168      }
169      return not != ret;
170    }
171
172    int compare = ignoreCase ? fieldValue.compareToIgnoreCase(value) : fieldValue.compareTo(value);
173
174    boolean ret;
175    switch (operator) {
176      case DOUBLE_EQUALS:
177        ret = compare == 0;
178        break;
179
180      case GREATER:
181        ret = compare > 0;
182        break;
183
184      case GREATER_OR_EQUAL:
185        ret = compare >= 0;
186        break;
187
188      case LESS:
189        ret = compare < 0;
190        break;
191
192      case LESS_OR_EQUAL:
193        ret = compare <= 0;
194        break;
195
196      default:
197        throw new AssertionError();
198    }
199    return not != ret;
200  }
201
202  @Override
203  public String toString() {
204    return (not ? "!" : "") + field.getHeader() + operator + value.asString();
205  }
206
207  @Override
208  public boolean equals(Object o) {
209    if (this == o) {
210      return true;
211    }
212    if (!(o instanceof RecordFilter)) {
213      return false;
214    }
215    RecordFilter filter = (RecordFilter) o;
216    return ignoreCase == filter.ignoreCase && not == filter.not && field == filter.field
217      && operator == filter.operator && value.equals(filter.value);
218  }
219
220  @Override
221  public int hashCode() {
222    return Objects.hash(ignoreCase, not, field, operator, value);
223  }
224
225  /*
226   * For FilterBuilder
227   */
228  public static FilterBuilder newBuilder(Field field) {
229    return new FilterBuilder(field, false);
230  }
231
232  public static FilterBuilder newBuilder(Field field, boolean ignoreCase) {
233    return new FilterBuilder(field, ignoreCase);
234  }
235
236  public static final class FilterBuilder {
237    private final Field field;
238    private final boolean ignoreCase;
239
240    private FilterBuilder(Field field, boolean ignoreCase) {
241      this.field = Objects.requireNonNull(field);
242      this.ignoreCase = ignoreCase;
243    }
244
245    public RecordFilter equal(FieldValue value) {
246      return newFilter(false, Operator.EQUAL, value);
247    }
248
249    public RecordFilter equal(Object value) {
250      return equal(field.newValue(value));
251    }
252
253    public RecordFilter notEqual(FieldValue value) {
254      return newFilter(true, Operator.EQUAL, value);
255    }
256
257    public RecordFilter notEqual(Object value) {
258      return notEqual(field.newValue(value));
259    }
260
261    public RecordFilter doubleEquals(FieldValue value) {
262      return newFilter(false, Operator.DOUBLE_EQUALS, value);
263    }
264
265    public RecordFilter doubleEquals(Object value) {
266      return doubleEquals(field.newValue(value));
267    }
268
269    public RecordFilter notDoubleEquals(FieldValue value) {
270      return newFilter(true, Operator.DOUBLE_EQUALS, value);
271    }
272
273    public RecordFilter notDoubleEquals(Object value) {
274      return notDoubleEquals(field.newValue(value));
275    }
276
277    public RecordFilter greater(FieldValue value) {
278      return newFilter(false, Operator.GREATER, value);
279    }
280
281    public RecordFilter greater(Object value) {
282      return greater(field.newValue(value));
283    }
284
285    public RecordFilter notGreater(FieldValue value) {
286      return newFilter(true, Operator.GREATER, value);
287    }
288
289    public RecordFilter notGreater(Object value) {
290      return notGreater(field.newValue(value));
291    }
292
293    public RecordFilter greaterOrEqual(FieldValue value) {
294      return newFilter(false, Operator.GREATER_OR_EQUAL, value);
295    }
296
297    public RecordFilter greaterOrEqual(Object value) {
298      return greaterOrEqual(field.newValue(value));
299    }
300
301    public RecordFilter notGreaterOrEqual(FieldValue value) {
302      return newFilter(true, Operator.GREATER_OR_EQUAL, value);
303    }
304
305    public RecordFilter notGreaterOrEqual(Object value) {
306      return notGreaterOrEqual(field.newValue(value));
307    }
308
309    public RecordFilter less(FieldValue value) {
310      return newFilter(false, Operator.LESS, value);
311    }
312
313    public RecordFilter less(Object value) {
314      return less(field.newValue(value));
315    }
316
317    public RecordFilter notLess(FieldValue value) {
318      return newFilter(true, Operator.LESS, value);
319    }
320
321    public RecordFilter notLess(Object value) {
322      return notLess(field.newValue(value));
323    }
324
325    public RecordFilter lessOrEqual(FieldValue value) {
326      return newFilter(false, Operator.LESS_OR_EQUAL, value);
327    }
328
329    public RecordFilter lessOrEqual(Object value) {
330      return lessOrEqual(field.newValue(value));
331    }
332
333    public RecordFilter notLessOrEqual(FieldValue value) {
334      return newFilter(true, Operator.LESS_OR_EQUAL, value);
335    }
336
337    public RecordFilter notLessOrEqual(Object value) {
338      return notLessOrEqual(field.newValue(value));
339    }
340
341    private RecordFilter newFilter(boolean not, Operator operator, FieldValue value) {
342      return new RecordFilter(ignoreCase, not, field, operator, value);
343    }
344  }
345}