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