/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.api.instrumentation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
/**
* A source section filter represents an expression for a subset of tagged source sections that are
* used in an Truffle interpreter.
* <p>
* Start building event filters by calling {@link SourceSectionFilter#newBuilder()} and complete
* them by calling {@link Builder#build()}.
*
* @see SourceSectionFilter#newBuilder()
* @see Instrumenter#attachFactory(SourceSectionFilter, ExecutionEventNodeFactory)
* @see Instrumenter#attachListener(SourceSectionFilter, ExecutionEventListener)
* @since 0.12
*/
public final class SourceSectionFilter {
/**
* A filter that matches everything.
*
* @since 0.18
*/
public static final SourceSectionFilter ANY = newBuilder().build();
private final EventFilterExpression[] expressions;
private SourceSectionFilter(EventFilterExpression[] expressions) {
this.expressions = expressions;
}
/**
* Creates a new {@link SourceSectionFilter} expression using a {@link Builder builder} pattern.
* Individual builder statements are interpreted as conjunctions (AND) while multiple parameters
* for individual filter expressions are treated as disjunctions (OR). To create the final
* filter finalize the expression using {@link Builder#build()}.
*
* @see Builder#sourceIs(Source...)
* @see Builder#mimeTypeIs(String...)
* @see Builder#tagIs(Class...)
* @see Builder#tagIsNot(Class...)
* @see Builder#sourceSectionEquals(SourceSection...)
* @see Builder#indexIn(int, int)
* @see Builder#lineIn(int, int)
* @see Builder#lineIs(int)
* @see Builder#build()
*
* @return a new builder to create new {@link SourceSectionFilter} instances
* @since 0.12
*/
public static Builder newBuilder() {
return new SourceSectionFilter(null).new Builder();
}
/**
* @return the filter expressions in a human readable form for debugging.
* @since 0.12
*/
@Override
public String toString() {
StringBuilder b = new StringBuilder("SourceSectionFilter[");
String sep = "";
for (EventFilterExpression expression : expressions) {
b.append(sep);
b.append(expression.toString());
sep = " and ";
}
b.append("]");
return b.toString();
}
// implementation
Set<Class<?>> getReferencedTags() {
Set<Class<?>> usedTags = new HashSet<>();
for (EventFilterExpression expression : expressions) {
expression.collectReferencedTags(usedTags);
}
return usedTags;
}
boolean isSourceOnly() {
for (EventFilterExpression eventFilterExpression : expressions) {
if (!eventFilterExpression.isSourceOnly()) {
return false;
}
}
return true;
}
boolean isInstrumentedRoot(Set<Class<?>> providedTags, SourceSection rootSourceSection) {
for (EventFilterExpression exp : expressions) {
if (!exp.isRootIncluded(providedTags, rootSourceSection)) {
return false;
}
}
return true;
}
boolean isInstrumentedNode(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
if (sourceSection == null) {
return false;
}
for (EventFilterExpression exp : expressions) {
if (!exp.isIncluded(providedTags, instrumentedNode, sourceSection)) {
return false;
}
}
return true;
}
boolean isInstrumentedSource(Source source) {
if (source == null) {
return false;
}
for (EventFilterExpression exp : expressions) {
assert exp.isSourceOnly();
if (!exp.isSourceIncluded(source)) {
return false;
}
}
return true;
}
/**
* Configure your own {@link SourceSectionFilter} before creating its instance. Specify various
* parameters by calling individual {@link Builder} methods. When done, call {@link #build()}.
*
* @since 0.12
*/
public final class Builder {
private List<EventFilterExpression> expressions = new ArrayList<>();
private Builder() {
}
/**
* Add a filter for all source sections that reference one of the given sources.
*
* @since 0.12
*/
public Builder sourceIs(Source... source) {
verifyNotNull(source);
expressions.add(new EventFilterExpression.SourceIs(source));
return this;
}
/**
* Adds custom predicate to filter inclusion of {@link Source sources}. The predicate must
* always return the same result for a source instance otherwise the behavior is undefined.
* The predicate should be able run on multiple threads at the same time.
*
* @param predicate a test for inclusion
* @since 0.17
*/
public Builder sourceIs(SourcePredicate predicate) {
if (predicate == null) {
throw new IllegalArgumentException("SourcePredicate must not be null.");
}
expressions.add(new EventFilterExpression.SourceFilterIs(predicate));
return this;
}
/**
* Add a filter for all source sections that declare one of the given mime-types. Mime-types
* which are compared must match exactly one of the mime-types specified by the target guest
* language.
*
* @param mimeTypes matches one of the given mime types
* @return the builder to chain calls
* @since 0.12
*/
public Builder mimeTypeIs(String... mimeTypes) {
verifyNotNull(mimeTypes);
expressions.add(new EventFilterExpression.MimeTypeIs(mimeTypes));
return this;
}
/**
* Add a filter for all source sections that are tagged with one of the given String tags.
*
* @param tags matches one of the given tags
* @return the builder to chain calls
* @since 0.12
*/
public Builder tagIs(Class<?>... tags) {
verifyNotNull(tags);
expressions.add(new EventFilterExpression.TagIs(tags));
return this;
}
/**
* Add a filter for all sources sections that declare not one of the given String tags.
*
* @param tags matches not one of the given tags
* @return the builder to chain calls
* @since 0.12
*/
public Builder tagIsNot(Class<?>... tags) {
verifyNotNull(tags);
expressions.add(new Not(new EventFilterExpression.TagIs(tags)));
return this;
}
/**
* Add a filter for all sources sections that equal one of the given source sections.
*
* @param section matches one of the given source sections
* @return the builder to chain calls
* @since 0.12
*/
public Builder sourceSectionEquals(SourceSection... section) {
verifyNotNull(section);
expressions.add(new EventFilterExpression.SourceSectionEquals(section));
return this;
}
/**
* Add a filter for all root sources sections that equal one of the given source sections.
* All descendant source sections of a matching root source section are included in the
* filter. This can mean in the dynamic language domain that all nodes of a function for
* which the root source section matches the given source section is instrumented but its
* inner functions and its nodes are not instrumented.
*
* @param section matches one of the given root source sections
* @return the builder to chain calls
* @since 0.12
*/
public Builder rootSourceSectionEquals(SourceSection... section) {
verifyNotNull(section);
expressions.add(new EventFilterExpression.RootSourceSectionEquals(section));
return this;
}
/**
* Add a filter for all sources sections which indices are not contained in one of the given
* index ranges.
*
* @param ranges matches indices that are not contained one of the given index ranges
* @return the builder to chain calls
* @since 0.12
*/
public Builder indexNotIn(IndexRange... ranges) {
verifyNotNull(ranges);
expressions.add(new Not(new EventFilterExpression.IndexIn(ranges)));
return this;
}
/**
* Add a filter for all sources sections which indices are contained in one of the given
* index ranges.
*
* @param ranges matches indices that are contained one of the given index ranges
* @return the builder to chain calls
* @since 0.12
*/
public Builder indexIn(IndexRange... ranges) {
verifyNotNull(ranges);
expressions.add(new EventFilterExpression.IndexIn(ranges));
return this;
}
/**
* Add a filter for all sources sections where the index is inside a startIndex (inclusive)
* plus a given length (exclusive).
*
* @param startIndex the start index (inclusive)
* @param length the number of matched characters
* @return the builder to chain calls
* @since 0.12
*/
public Builder indexIn(int startIndex, int length) {
return indexIn(IndexRange.byLength(startIndex, length));
}
/**
* Add a filter for all sources sections which lines are contained in one of the given index
* ranges. Line indices must be greater or equal to <code>1</code>.
*
* @param ranges matches lines that are contained one of the given index ranges
* @return the builder to chain calls
* @since 0.12
*/
public Builder lineIn(IndexRange... ranges) {
verifyLineIndices(ranges);
expressions.add(new EventFilterExpression.LineIn(ranges));
return this;
}
/**
* Add a filter for all sources sections which lines are not contained in one of the given
* index ranges. Line indices must be greater or equal to <code>1</code>.
*
* @param ranges matches lines that are not contained one of the given index ranges
* @return the builder to chain calls
* @since 0.12
*/
public Builder lineNotIn(IndexRange... ranges) {
verifyLineIndices(ranges);
expressions.add(new Not(new EventFilterExpression.LineIn(ranges)));
return this;
}
/**
* Add a filter for all sources sections where the line is inside a startLine (first index
* inclusive) plus a given length (last index exclusive).
*
* @param startLine the start line (inclusive)
* @param length the number of matched lines
* @return the builder to chain calls
* @since 0.12
*/
public Builder lineIn(int startLine, int length) {
return lineIn(IndexRange.byLength(startLine, length));
}
/**
* Add a filter for all sources sections start in one of the given index ranges. Line
* indices must be greater or equal to <code>1</code>.
*
* @param ranges matches lines that start in one of the given index ranges
* @return the builder to chain calls
* @since 0.12
*/
public Builder lineStartsIn(IndexRange... ranges) {
verifyLineIndices(ranges);
expressions.add(new EventFilterExpression.LineStartsIn(ranges));
return this;
}
/**
* Add a filter for all sources sections end in one of the given index ranges. Line indices
* must be greater or equal to <code>1</code>.
*
* @param ranges matches lines that end in one of the given index ranges
* @return the builder to chain calls
* @since 0.12
*/
public Builder lineEndsIn(IndexRange... ranges) {
verifyLineIndices(ranges);
expressions.add(new EventFilterExpression.LineEndsIn(ranges));
return this;
}
private void verifyLineIndices(IndexRange... ranges) {
verifyNotNull(ranges);
for (IndexRange indexRange : ranges) {
if (indexRange.startIndex < 1) {
throw new IllegalArgumentException(String.format("Start line indices must be >= 1 but were %s.", indexRange.startIndex));
}
}
}
/**
* Add a filter for all sources sections where the line is exactly the given line. Line
* indices must be greater or equal to <code>1</code>. *
*
* @param line the line to be matched
* @return the builder to chain calls
* @since 0.12
*/
public Builder lineIs(int line) {
return lineIn(line, 1);
}
/**
* Finalizes and constructs the {@link SourceSectionFilter} instance.
*
* @return the built filter expression
* @since 0.12
*/
public SourceSectionFilter build() {
Collections.sort(expressions);
return new SourceSectionFilter(expressions.toArray(new EventFilterExpression[0]));
}
private void verifyNotNull(Object[] values) {
if (values == null) {
throw new IllegalArgumentException("Given arguments must not be null.");
}
for (int i = 0; i < values.length; i++) {
if (values[i] == null) {
throw new IllegalArgumentException("None of the given argument values must be null.");
}
}
}
}
/**
* Represents a predicate for source objects.
*
* @since 0.17
*/
public interface SourcePredicate {
/**
* Returns <code>true</code> if the given source should be tested positive and
* <code>false</code> if the sources should be filtered.
*
* @param source the source object to filter
* @since 0.17
*/
boolean test(Source source);
}
/**
* Represents a range between two indices within a {@link SourceSectionFilter source section
* filter}. Instances are immutable.
*
* @see SourceSectionFilter
* @see #between(int, int)
* @see #byLength(int, int)
* @since 0.12
*/
public static final class IndexRange {
final int startIndex;
final int endIndex;
private IndexRange(int startIndex, int endIndex) {
this.startIndex = startIndex;
this.endIndex = endIndex;
}
/**
* Constructs a new index range between one a first index inclusive and a second index
* exclusive. Parameters must comply <code>startIndex >= 0</code> and
* <code>startIndex <= endIndex</code>.
*
* @param startIndex the start index (inclusive)
* @param endIndex the end index (inclusive)
* @return a new index range
* @throws IllegalArgumentException if parameter invariants are violated
* @since 0.12
*/
public static IndexRange between(int startIndex, int endIndex) {
if (startIndex < 0) {
throw new IllegalArgumentException(String.format("The argument startIndex must be positive but is %s.", startIndex));
} else if (endIndex < startIndex) {
throw new IllegalArgumentException(String.format("Invalid range %s:%s.", startIndex, endIndex));
}
return new IndexRange(startIndex, endIndex);
}
/**
* Constructs a new index range with a given first index inclusive and a given length.
* Parameters must comply <code>startIndex >= 0</code> and <code>length >= 0</code>.
*
* @param startIndex the start index (inclusive)
* @param length the length of the range
* @return a new index range
* @throws IllegalArgumentException if parameter invariants are violated
* @since 0.12
*/
public static IndexRange byLength(int startIndex, int length) {
if (length < 0) {
throw new IllegalArgumentException(String.format("The argument length must be positive but is %s.", length));
} else if (startIndex < 0) {
throw new IllegalArgumentException(String.format("The argument startIndex must be positive but is %s.", startIndex));
}
return new IndexRange(startIndex, startIndex + length);
}
boolean contains(int otherStartIndex, int otherEndIndex) {
return startIndex <= otherEndIndex && otherStartIndex < endIndex;
}
/**
* @return a human readable version of the index range
* @since 0.12
*/
@Override
public String toString() {
return "[" + startIndex + "-" + endIndex + "[";
}
}
private abstract static class EventFilterExpression implements Comparable<EventFilterExpression> {
protected abstract int getOrder();
void collectReferencedTags(@SuppressWarnings("unused") Set<Class<?>> collectTags) {
// default implementation does nothing
}
boolean isSourceIncluded(@SuppressWarnings("unused") Source source) {
return false;
}
abstract boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection);
abstract boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSection);
boolean isSourceOnly() {
return false;
}
public final int compareTo(EventFilterExpression o) {
return o.getOrder() - getOrder();
}
static void appendRanges(StringBuilder builder, IndexRange[] ranges) {
String sep = "";
for (IndexRange range : ranges) {
builder.append(sep).append(range);
sep = " or ";
}
}
private static final class SourceFilterIs extends EventFilterExpression {
private final SourcePredicate predicate;
SourceFilterIs(SourcePredicate predicate) {
this.predicate = predicate;
}
@Override
boolean isSourceOnly() {
return true;
}
@Override
boolean isSourceIncluded(Source src) {
if (src == null) {
return false;
}
return predicate.test(src);
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSourceSection) {
if (rootSourceSection == null) {
return true;
}
return isSourceIncluded(rootSourceSection.getSource());
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
return isSourceIncluded(sourceSection.getSource());
}
@Override
protected int getOrder() {
return 1;
}
@Override
public String toString() {
return String.format("source is included by custom filter %s", predicate.toString());
}
}
private static final class SourceIs extends EventFilterExpression {
private final Source[] sources;
SourceIs(Source... source) {
this.sources = source;
}
@Override
boolean isSourceOnly() {
return true;
}
@Override
boolean isSourceIncluded(Source src) {
for (Source otherSource : sources) {
if (src == otherSource) {
return true;
}
}
return false;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSourceSection) {
if (rootSourceSection == null) {
return true;
}
return isSourceIncluded(rootSourceSection.getSource());
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
return isSourceIncluded(sourceSection.getSource());
}
@Override
protected int getOrder() {
return 1;
}
@Override
public String toString() {
return String.format("source is %s", Arrays.toString(sources));
}
}
private static final class MimeTypeIs extends EventFilterExpression {
private final String[] mimeTypes;
MimeTypeIs(String... mimeTypes) {
this.mimeTypes = mimeTypes;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSourceSection) {
if (rootSourceSection == null) {
return true;
}
return isSourceIncluded(rootSourceSection.getSource());
}
@Override
boolean isSourceOnly() {
return true;
}
@Override
boolean isSourceIncluded(Source source) {
String mimeType = source.getMimeType();
if (mimeType != null) {
for (String otherMimeType : mimeTypes) {
if (otherMimeType.equals(mimeType)) {
return true;
}
}
}
return false;
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
return isSourceIncluded(sourceSection.getSource());
}
@Override
protected int getOrder() {
return 2;
}
@Override
public String toString() {
return String.format("mime-type is one-of %s", Arrays.toString(mimeTypes));
}
}
private static Class<?>[] checkTags(Class<?>[] tags) {
for (int i = 0; i < tags.length; i++) {
if (tags[i] == null) {
throw new IllegalArgumentException("Tags must not be null.");
}
}
return tags;
}
private static final class TagIs extends EventFilterExpression {
private final Class<?>[] tags;
TagIs(Class<?>... tags) {
this.tags = checkTags(tags);
}
@Override
void collectReferencedTags(Set<Class<?>> collectTags) {
for (Class<?> tag : tags) {
collectTags.add(tag);
}
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
Class<?>[] filterTags = this.tags;
for (int i = 0; i < filterTags.length; i++) {
Class<?> tag = filterTags[i];
if (InstrumentationHandler.hasTagImpl(providedTags, instrumentedNode, tag)) {
return true;
}
}
return false;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSection) {
for (Class<?> tag : tags) {
if (providedTags.contains(tag)) {
return true;
}
}
return false;
}
@Override
protected int getOrder() {
return 4;
}
@Override
public String toString() {
return String.format("tag is one of %s", Arrays.toString(tags));
}
}
private static final class SourceSectionEquals extends EventFilterExpression {
private final SourceSection[] sourceSections;
SourceSectionEquals(SourceSection... sourceSection) {
this.sourceSections = sourceSection;
// clear tags
for (int i = 0; i < sourceSection.length; i++) {
sourceSections[i] = sourceSection[i];
}
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection s) {
for (SourceSection compareSection : sourceSections) {
if (s.equals(compareSection)) {
return true;
}
}
return false;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSection) {
if (rootSection == null) {
return true;
}
Source rootSource = rootSection.getSource();
if (rootSource != null) {
for (SourceSection compareSection : sourceSections) {
if (rootSource.equals(compareSection.getSource())) {
return true;
}
}
}
return false;
}
@Override
protected int getOrder() {
return 6;
}
@Override
public String toString() {
return String.format("source-section equals one-of %s", Arrays.toString(sourceSections));
}
}
private static final class RootSourceSectionEquals extends EventFilterExpression {
private final SourceSection[] sourceSections;
RootSourceSectionEquals(SourceSection... sourceSection) {
this.sourceSections = sourceSection;
// clear tags
for (int i = 0; i < sourceSection.length; i++) {
sourceSections[i] = sourceSection[i];
}
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection s) {
return true;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSection) {
if (rootSection == null) {
return false;
}
for (SourceSection compareSection : sourceSections) {
if (rootSection.equals(compareSection)) {
return true;
}
}
return false;
}
@Override
protected int getOrder() {
return 6;
}
@Override
public String toString() {
return String.format("source-section equals one-of %s", Arrays.toString(sourceSections));
}
}
private static final class IndexIn extends EventFilterExpression {
private final IndexRange[] ranges;
IndexIn(IndexRange[] ranges) {
this.ranges = ranges;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSourceSection) {
if (rootSourceSection == null) {
return true;
}
return isIncluded(null, null, rootSourceSection);
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
if (!sourceSection.isAvailable()) {
return false;
}
int otherStart = sourceSection.getCharIndex();
int otherEnd = otherStart + sourceSection.getCharLength();
for (IndexRange indexRange : ranges) {
if (indexRange.contains(otherStart, otherEnd)) {
return true;
}
}
return false;
}
@Override
protected int getOrder() {
return 8;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("(index-between ");
appendRanges(builder, ranges);
builder.append(")");
return builder.toString();
}
}
private static final class LineStartsIn extends EventFilterExpression {
private final IndexRange[] ranges;
LineStartsIn(IndexRange[] ranges) {
this.ranges = ranges;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSection) {
if (rootSection == null) {
return true;
}
if (!rootSection.isAvailable()) {
return false;
}
return LineIn.isLineIn(rootSection, ranges);
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
if (!sourceSection.isAvailable()) {
return false;
}
int otherStart = sourceSection.getStartLine();
for (IndexRange indexRange : ranges) {
if (indexRange.contains(otherStart, otherStart)) {
return true;
}
}
return false;
}
@Override
protected int getOrder() {
return 10;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("(line-starts-between ");
appendRanges(builder, ranges);
builder.append(")");
return builder.toString();
}
}
private static final class LineEndsIn extends EventFilterExpression {
private final IndexRange[] ranges;
LineEndsIn(IndexRange[] ranges) {
this.ranges = ranges;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSection) {
if (rootSection == null) {
return true;
}
if (!rootSection.isAvailable()) {
return false;
}
return LineIn.isLineIn(rootSection, ranges);
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
int otherStart = sourceSection.getStartLine();
int otherEnd;
if (sourceSection.getSource() == null) {
otherEnd = otherStart;
} else {
otherEnd = sourceSection.getEndLine();
}
for (IndexRange indexRange : ranges) {
if (indexRange.contains(otherEnd, otherEnd)) {
return true;
}
}
return false;
}
@Override
protected int getOrder() {
return 10;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("(line-ends-between ");
appendRanges(builder, ranges);
builder.append(")");
return builder.toString();
}
}
private static final class LineIn extends EventFilterExpression {
private final IndexRange[] ranges;
LineIn(IndexRange[] ranges) {
this.ranges = ranges;
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSourceSection) {
if (rootSourceSection == null) {
return true;
}
if (!rootSourceSection.isAvailable()) {
return false;
}
return isIncluded(null, null, rootSourceSection);
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
return isLineIn(sourceSection, ranges);
}
static boolean isLineIn(SourceSection sourceSection, IndexRange[] ranges) {
if (!sourceSection.isAvailable()) {
return false;
}
int otherStart = sourceSection.getStartLine();
int otherEnd;
if (sourceSection.getSource() == null) {
otherEnd = otherStart;
} else {
otherEnd = sourceSection.getEndLine();
}
for (IndexRange indexRange : ranges) {
if (indexRange.contains(otherStart, otherEnd)) {
return true;
}
}
return false;
}
@Override
protected int getOrder() {
return 10;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("(line-between ");
appendRanges(builder, ranges);
builder.append(")");
return builder.toString();
}
}
}
private static final class Not extends EventFilterExpression {
private final EventFilterExpression delegate;
Not(EventFilterExpression delegate) {
this.delegate = delegate;
}
@Override
boolean isSourceOnly() {
return delegate.isSourceOnly();
}
@Override
boolean isSourceIncluded(Source source) {
return !delegate.isSourceIncluded(source);
}
@Override
void collectReferencedTags(Set<Class<?>> collectTags) {
delegate.collectReferencedTags(collectTags);
}
@Override
boolean isRootIncluded(Set<Class<?>> providedTags, SourceSection rootSection) {
return true;
}
@Override
boolean isIncluded(Set<Class<?>> providedTags, Node instrumentedNode, SourceSection sourceSection) {
return !delegate.isIncluded(providedTags, instrumentedNode, sourceSection);
}
@Override
protected int getOrder() {
return delegate.getOrder();
}
@Override
public String toString() {
return "not(" + delegate.toString() + ")";
}
}
}