package sk.stuba.fiit.perconik.core.java.dom;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import org.eclipse.jdt.core.dom.ASTNode;
import static java.util.Arrays.asList;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Sets.newHashSet;
public abstract class NodeClassFilter<N extends ASTNode, R extends ASTNode> implements Predicate<N> {
static final Mode defaultMode = Mode.INCLUDE;
static final Strategy defaultStrategy = Strategy.INSTANCE_OF;
final Mode mode;
final Strategy strategy;
NodeClassFilter(final Mode mode, final Strategy strategy) {
this.mode = checkNotNull(mode);
this.strategy = checkNotNull(strategy);
}
private static <N extends ASTNode, R extends ASTNode> Single<N, R> newSingle(final Class<? extends R> implementation) {
return new Single<>(defaultMode, defaultStrategy, implementation);
}
private static <N extends ASTNode, R extends ASTNode> Multi<N, R> newMulti(final Set<Class<? extends R>> implementations) {
return new Multi<>(defaultMode, defaultStrategy, implementations);
}
public static <N extends ASTNode, R extends ASTNode> NodeClassFilter<N, R> of(final Class<? extends R> implementation) {
return newSingle(checkNotNull(implementation));
}
public static <N extends ASTNode, R extends ASTNode> NodeClassFilter<N, R> of(final Class<? extends R> a, final Class<? extends R> b) {
return newMulti(ImmutableSet.of(a, b));
}
public static <N extends ASTNode, R extends ASTNode> NodeClassFilter<N, R> of(final Class<? extends R> a, final Class<? extends R> b, final Class<? extends R> c) {
return newMulti(ImmutableSet.of(a, b, c));
}
public static <N extends ASTNode, R extends ASTNode> NodeClassFilter<N, R> of(final Class<? extends R> a, final Class<? extends R> b, final Class<? extends R> c, final Class<? extends R> d) {
return newMulti(ImmutableSet.of(a, b, c, d));
}
@SafeVarargs
public static <N extends ASTNode, R extends ASTNode> NodeClassFilter<N, R> of(final Class<? extends R> implementation, final Class<? extends R> ... rest) {
return newMulti(ImmutableSet.<Class<? extends R>>builder().add(implementation).addAll(asList(rest)).build());
}
public static <N extends ASTNode, R extends ASTNode> NodeClassFilter<N, R> of(final Iterable<Class<? extends R>> implementations) {
return newMulti(ImmutableSet.copyOf(implementations));
}
public static <N extends ASTNode, R extends ASTNode> NodeClassFilter<N, R> of(final Iterator<Class<? extends R>> implementations) {
return newMulti(ImmutableSet.copyOf(implementations));
}
public static class Builder<N extends ASTNode, R extends ASTNode> {
private Mode mode;
private Strategy strategy;
private final Set<Class<? extends R>> types;
public Builder() {
this.types = newHashSet();
}
public final Builder<N, R> include() {
checkState(this.mode == null);
this.mode = Mode.INCLUDE;
return this;
}
public final Builder<N, R> exclude() {
checkState(this.mode == null);
this.mode = Mode.EXCLUDE;
return this;
}
public final Builder<N, R> instanceOf() {
checkState(this.strategy == null);
this.strategy = Strategy.INSTANCE_OF;
return this;
}
public final Builder<N, R> matchedBy() {
checkState(this.strategy == null);
this.strategy = Strategy.MATCHED_BY;
return this;
}
public final Builder<N, R> type(final Class<? extends R> type) {
this.types.add(type);
return this;
}
public final Builder<N, R> types(final Collection<Class<? extends R>> types) {
this.types.addAll(types);
return this;
}
public final NodeClassFilter<N, R> build() {
Mode mode = firstNonNull(this.mode, defaultMode);
Strategy strategy = firstNonNull(this.strategy, defaultStrategy);
Set<Class<? extends R>> types = ImmutableSet.copyOf(this.types);
switch (types.size()) {
case 0:
throw new IllegalStateException();
case 1:
return new Single<>(mode, strategy, types.iterator().next());
default:
return new Multi<>(mode, strategy, types);
}
}
}
public static <N extends ASTNode, R extends ASTNode> Builder<N, R> builder() {
return new Builder<>();
}
private enum Mode {
INCLUDE {
@Override
boolean apply(final boolean result) {
return result;
}
},
EXCLUDE {
@Override
boolean apply(final boolean result) {
return !result;
}
};
abstract boolean apply(final boolean result);
}
private enum Strategy {
INSTANCE_OF {
@Override
boolean compute(final Class<? extends ASTNode> implementation, @Nullable final ASTNode node) {
return node != null && implementation.isInstance(node);
}
},
MATCHED_BY {
@Override
boolean compute(final Class<? extends ASTNode> implementation, @Nullable final ASTNode node) {
return node != null && implementation == node.getClass();
}
};
abstract boolean compute(Class<? extends ASTNode> implementation, @Nullable ASTNode node);
}
private static final class Single<N extends ASTNode, R extends ASTNode> extends NodeClassFilter<N, R> {
private final Class<? extends R> implementation;
Single(final Mode mode, final Strategy strategy, final Class<? extends R> implementation) {
super(mode, strategy);
this.implementation = implementation;
}
@Override
public boolean apply(@Nullable final N node) {
return this.mode.apply(this.strategy.compute(this.implementation, node));
}
@Override
public Set<Class<? extends R>> getNodeClasses() {
return ImmutableSet.<Class<? extends R>>of(this.implementation);
}
}
private static final class Multi<N extends ASTNode, R extends ASTNode> extends NodeClassFilter<N, R> {
private final Set<Class<? extends R>> implementations;
Multi(final Mode mode, final Strategy strategy, final Set<Class<? extends R>> implementations) {
super(mode, strategy);
this.implementations = implementations;
}
@Override
public boolean apply(@Nullable final N node) {
for (Class<? extends R> implementation: this.implementations) {
if (this.mode.apply(this.strategy.compute(implementation, node))) {
return true;
}
}
return false;
}
@Override
public Set<Class<? extends R>> getNodeClasses() {
return this.implementations;
}
}
@Override
public final boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NodeClassFilter)) {
return false;
}
NodeClassFilter<?, ?> other = (NodeClassFilter<?, ?>) o;
return this.mode == other.mode && this.strategy == other.strategy && this.getNodeClasses().equals(other.getNodeClasses());
}
@Override
public final int hashCode() {
return Objects.hashCode(this.mode, this.strategy, this.getNodeClasses());
}
public abstract Set<Class<? extends R>> getNodeClasses();
@Override
public final String toString() {
return "filter(" + Joiner.on(',').join(this.getNodeClasses()) + ")";
}
}