Java tutorial
/* * Copyright (c) 2015 Spotify AB. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.spotify.heroic.filter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.spotify.heroic.ObjectHasher; import com.spotify.heroic.common.Series; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Stream; import lombok.Data; import lombok.EqualsAndHashCode; import org.apache.commons.lang3.StringUtils; @Data @EqualsAndHashCode(of = { "OPERATOR", "filters" }, doNotUseGetters = true) @JsonTypeName("and") public class AndFilter implements Filter { public static final String OPERATOR = "and"; private final List<Filter> filters; @Override public boolean apply(Series series) { return filters.stream().allMatch(s -> s.apply(series)); } @Override public <T> T visit(final Visitor<T> visitor) { return visitor.visitAnd(this); } @Override public String toString() { final List<String> parts = new ArrayList<>(filters.size() + 1); parts.add(OPERATOR); for (final Filter statement : filters) { parts.add(statement.toString()); } return "[" + StringUtils.join(parts, ", ") + "]"; } @Override public Filter optimize() { return optimize(flatten(this.filters)); } static SortedSet<Filter> flatten(final Collection<Filter> filters) { final SortedSet<Filter> result = new TreeSet<>(); filters.stream().flatMap(f -> f.optimize().visit(new Filter.Visitor<Stream<Filter>>() { @Override public Stream<Filter> visitAnd(final AndFilter and) { return and.terms().stream().map(Filter::optimize); } @Override public Stream<Filter> visitNot(final NotFilter not) { // check for De Morgan's return not.getFilter().visit(new Filter.Visitor<Stream<Filter>>() { @Override public Stream<Filter> visitOr(final OrFilter or) { return or.terms().stream().map(f -> NotFilter.of(f).optimize()); } @Override public Stream<Filter> defaultAction(final Filter filter) { return Stream.of(not.optimize()); } }); } @Override public Stream<Filter> defaultAction(final Filter filter) { return Stream.of(filter.optimize()); } })).forEach(result::add); return result; } static Filter optimize(final SortedSet<Filter> filters) { final SortedSet<Filter> result = new TreeSet<>(); for (final Filter f : filters) { if (f instanceof NotFilter) { // Optimize away expressions which are always false. // Example: foo = bar and !(foo = bar) if (filters.contains(((NotFilter) f).getFilter())) { return FalseFilter.get(); } } else if (f instanceof StartsWithFilter) { // Optimize away prefixes which encompass each other. // Example: foo ^ hello and foo ^ helloworld -> foo ^ helloworld if (FilterUtils.containsPrefixedWith(filters, (StartsWithFilter) f, (inner, outer) -> FilterUtils.prefixedWith(inner.getValue(), outer.getValue()))) { continue; } } else if (f instanceof MatchTagFilter) { // Optimize matchTag expressions which are always false. // Example: foo = bar and foo = baz if (FilterUtils.containsConflictingMatchTag(filters, (MatchTagFilter) f)) { return FalseFilter.get(); } } else if (f instanceof MatchKeyFilter) { // Optimize matchTag expressions which are always false. // Example: $key = bar and $key = baz if (FilterUtils.containsConflictingMatchKey(filters, (MatchKeyFilter) f)) { return FalseFilter.get(); } } result.add(f); } if (result.isEmpty()) { return FalseFilter.get(); } if (result.size() == 1) { return result.iterator().next(); } return new AndFilter(ImmutableList.copyOf(result)); } @Override public String operator() { return OPERATOR; } public List<Filter> terms() { return filters; } @Override public int compareTo(Filter o) { if (!AndFilter.class.equals(o.getClass())) { return operator().compareTo(o.operator()); } final AndFilter other = (AndFilter) o; return FilterUtils.compareLists(terms(), other.terms()); } private final Joiner and = Joiner.on(" and "); @Override public String toDSL() { return "(" + and.join(filters.stream().map(Filter::toDSL).iterator()) + ")"; } @Override public void hashTo(final ObjectHasher hasher) { hasher.putObject(this.getClass(), () -> { hasher.putField("filters", filters, hasher.list(hasher.with(Filter::hashTo))); }); } public static Filter of(Filter... filters) { return new AndFilter(Arrays.asList(filters)); } }