refcodes-matcher: Build custom matching logic and match path patterns

README

The REFCODES.ORG codes represent a group of artifacts consolidating parts of my work in the past years. Several topics are covered which I consider useful for you, programmers, developers and software engineers.

What is this repository for?

This artifact allows the construction of matchers for building arbitrary complex matching functionality. It is used to test whether matchee instances match certain criteria. Matcher instances may get nested in a hierarchy (using e.g. the OrMatcher, the AndMatcher or the NotMatcher types), having different matchers in combination will do custom matching. Some examples for Matcher implementations are the EqualWithMatcher, the PathMatcher,the LessThanMatcher or the RegExpMatcher. Use the MatcherSugar syntactic sugar mixin for expressively building your matchers.

How do I get set up?

To get up and running, include the following dependency (without the three dots “…”) in your pom.xml:

1
2
3
4
5
6
7
8
9
<dependencies>
	...
	<dependency>
		<artifactId>refcodes-matcher</artifactId>
		<groupId>org.refcodes</groupId>
		<version>3.3.5</version>
	</dependency>
	...
</dependencies>

The artifact is hosted directly at Maven Central. Jump straight to the source codes at Bitbucket. Read the artifact’s javadoc at javadoc.io.

Snippets of interest

Below find some code snippets which demonstrate the various aspects of using the refcodes-matcher artifact (and , if applicable, its offsprings). See also the example source codes of this artifact for further information on the usage of this artifact.

Syntactic sugar

The below snippets make use of the MatcherSugar class which’s methods are imported statically:

1
2
3
...
import static org.refcodes.matcher.MatcherSugar.*;
...

Match x₁ ≤ x ≤ x₂

The code snippet below test whether a number x is between the range x₁ ≤ x ≤ x₂ with x₁ = -10 (included) and x₂ = 10 (included):

1
2
3
4
5
6
...
Matcher<Integer> theMatcher = and( greaterOrEqualThan( -10 ), lessOrEqualThan( 10 ) ); // "-10 ≤ x ≤ 10
for ( int i = -12; i <= 12; i++ ) {
	System.out.println( "-10 ≤ " + i + " ≤ 10 --> " + theMatcher.isMatching( i ) );
}
...

Note that we combine a GreaterOrEqualThanMatcher instance and a LessOrEqualThanMatcher instance within a AndMatcher instance (further nesting is possible, only limited by your ideas).

-10 ≤ -12 ≤ 10 --> false
-10 ≤ -11 ≤ 10 --> false
-10 ≤ -10 ≤ 10 --> true
-10 ≤ -9 ≤ 10 --> true
-10 ≤ -8 ≤ 10 --> true
-10 ≤ -7 ≤ 10 --> true
-10 ≤ -6 ≤ 10 --> true
-10 ≤ -5 ≤ 10 --> true
-10 ≤ -4 ≤ 10 --> true
-10 ≤ -3 ≤ 10 --> true
-10 ≤ -2 ≤ 10 --> true
-10 ≤ -1 ≤ 10 --> true
-10 ≤ 0 ≤ 10 --> true
-10 ≤ 1 ≤ 10 --> true
-10 ≤ 2 ≤ 10 --> true
-10 ≤ 3 ≤ 10 --> true
-10 ≤ 4 ≤ 10 --> true
-10 ≤ 5 ≤ 10 --> true
-10 ≤ 6 ≤ 10 --> true
-10 ≤ 7 ≤ 10 --> true
-10 ≤ 8 ≤ 10 --> true
-10 ≤ 9 ≤ 10 --> true
-10 ≤ 10 ≤ 10 --> true
-10 ≤ 11 ≤ 10 --> false
-10 ≤ 12 ≤ 10 --> false

Match x₁ < x < x₂

The code snippet below test whether a number x is between (excluding) the range x₁ ≤ x ≤ x₂ with x₁ = -10 (excluded) and x₂ = 10 (excluded):

1
2
3
4
5
6
...
Matcher<Integer> theMatcher = and( greaterThan( -10 ), lessThan( 10 ) ); // -10 < x < 10
for ( int i = -12; i <= 12; i++ ) {
	System.out.println( "-10 ≤ " + i + " ≤ 10 --> " + theMatcher.isMatching( i ) );
}
...

Note that we combine a GreaterOrEqualThanMatcher instance and a LessThanMatcher instance within a AndMatcher instance (further nesting is possible, only limited by your ideas).

-10 < -12 < 10 --> false
-10 < -11 < 10 --> false
-10 < -10 < 10 --> false
-10 < -9 < 10 --> true
-10 < -8 < 10 --> true
-10 < -7 < 10 --> true
-10 < -6 < 10 --> true
-10 < -5 < 10 --> true
-10 < -4 < 10 --> true
-10 < -3 < 10 --> true
-10 < -2 < 10 --> true
-10 < -1 < 10 --> true
-10 < 0 < 10 --> true
-10 < 1 < 10 --> true
-10 < 2 < 10 --> true
-10 < 3 < 10 --> true
-10 < 4 < 10 --> true
-10 < 5 < 10 --> true
-10 < 6 < 10 --> true
-10 < 7 < 10 --> true
-10 < 8 < 10 --> true
-10 < 9 < 10 --> true
-10 < 10 < 10 --> false
-10 < 11 < 10 --> false
-10 < 12 < 10 --> false

Match x ≤ x₁ or x ≥ x₂

The code snippet below test whether a number x is outside the range x₁ ≤ x ≤ x₂ with x₁ = -10 (included) and x₂ = 10 (included):

1
2
3
4
5
6
...
Matcher<Integer> theMatcher = or( lessOrEqualThan( -10 ), greaterOrEqualThan( 10 ) ); // -10 ≰ x ≰ 10
for ( int i = -12; i <= 12; i++ ) {
	System.out.println( "-10 ≤ " + i + " ≤ 10 --> " + theMatcher.isMatching( i ) );
}
...

Note that we combine a GreaterOrEqualThanMatcher instance and a LessOrEqualThanMatcher instance within a OrMatcher instance (further nesting is possible, only limited by your ideas).

-10 ≰ -12 ≰ 10 --> true
-10 ≰ -11 ≰ 10 --> true
-10 ≰ -10 ≰ 10 --> true
-10 ≰ -9 ≰ 10 --> false
-10 ≰ -8 ≰ 10 --> false
-10 ≰ -7 ≰ 10 --> false
-10 ≰ -6 ≰ 10 --> false
-10 ≰ -5 ≰ 10 --> false
-10 ≰ -4 ≰ 10 --> false
-10 ≰ -3 ≰ 10 --> false
-10 ≰ -2 ≰ 10 --> false
-10 ≰ -1 ≰ 10 --> false
-10 ≰ 0 ≰ 10 --> false
-10 ≰ 1 ≰ 10 --> false
-10 ≰ 2 ≰ 10 --> false
-10 ≰ 3 ≰ 10 --> false
-10 ≰ 4 ≰ 10 --> false
-10 ≰ 5 ≰ 10 --> false
-10 ≰ 6 ≰ 10 --> false
-10 ≰ 7 ≰ 10 --> false
-10 ≰ 8 ≰ 10 --> false
-10 ≰ 9 ≰ 10 --> false
-10 ≰ 10 ≰ 10 --> true
-10 ≰ 11 ≰ 10 --> true
-10 ≰ 12 ≰ 10 --> true

Match x < x₁ or x > x₂

The code snippet below test whether a number x is outside the range x₁ < x < x₂ with x₁ = -10 (excluded) and x₂ = 10 (excluded):

1
2
3
4
5
6
...
Matcher<Integer> theMatcher = or( lessThan( -10 ), greaterThan( 10 ) ); // -10 ≮ x ≮ 10
for ( int i = -12; i <= 12; i++ ) {
	System.out.println( "-10 ≤ " + i + " ≤ 10 --> " + theMatcher.isMatching( i ) );
}
...

Note that we combine a GreaterThanMatcher instance and a LessThanMatcher instance within a OrMatcher instance (further nesting is possible, only limited by your ideas).

-10 ≮ -12 ≮ 10 --> true
-10 ≮ -11 ≮ 10 --> true
-10 ≮ -10 ≮ 10 --> false
-10 ≮ -9 ≮ 10 --> false
-10 ≮ -8 ≮ 10 --> false
-10 ≮ -7 ≮ 10 --> false
-10 ≮ -6 ≮ 10 --> false
-10 ≮ -5 ≮ 10 --> false
-10 ≮ -4 ≮ 10 --> false
-10 ≮ -3 ≮ 10 --> false
-10 ≮ -2 ≮ 10 --> false
-10 ≮ -1 ≮ 10 --> false
-10 ≮ 0 ≮ 10 --> false
-10 ≮ 1 ≮ 10 --> false
-10 ≮ 2 ≮ 10 --> false
-10 ≮ 3 ≮ 10 --> false
-10 ≮ 4 ≮ 10 --> false
-10 ≮ 5 ≮ 10 --> false
-10 ≮ 6 ≮ 10 --> false
-10 ≮ 7 ≮ 10 --> false
-10 ≮ 8 ≮ 10 --> false
-10 ≮ 9 ≮ 10 --> false
-10 ≮ 10 ≮ 10 --> false
-10 ≮ 11 ≮ 10 --> true
-10 ≮ 12 ≮ 10 --> true

Match a path with * and **

The code snippet below test whether path matches the pattern “/*/acme/**” where * is a wildcard for one path element and ** is a wildcard for multiple path elements:

1
2
3
4
5
6
7
8
9
...
Matcher<String> theMatcher = new PathMatcher( "/*/acme/**" );
String thePath = "/foo/acme/atari/bar";
System.out.println( "\"" + thePath + "\" -?-> \"/*/acme/**\" --> " + theMatcher.isMatching( thePath ) );
thePath = "/bar/acme/commodore/bar";
System.out.println( "\"" + thePath + "\" -?-> \"/*/acme/**\" --> " + theMatcher.isMatching( thePath ) );
thePath = "/foo/evil/bad/bar";
System.out.println( "\"" + thePath + "\" -?-> \"/*/acme/**\" --> " + theMatcher.isMatching( thePath ) );
...

Note that we directly use a PathMatcher instance (further nesting is possible, only limited by your ideas).

"/foo/acme/atari/bar" -?-> "/*/acme/**" --> true
"/bar/acme/commodore/bar" -?-> "/*/acme/**" --> true
"/foo/evil/bad/bar" -?-> "/*/acme/**" --> false

Get path variables with ${...}

The code snippet below matches the pattern “/acme/**/${tail}” against a path where ${tail} is a path variable which we want to determine:

1
2
3
4
5
6
7
8
9
10
11
12
13
PathMatcher theMatcher = new PathMatcher( "/acme/**/${tail}" );
String thePath = "/acme/foo/atari";
String theVar = theMatcher.toWildcardReplacement( thePath, "tail" );
System.out.println( "\"" + thePath + "\" -?-> \"/acme/**/${tail}\" --> " + theVar );
thePath = "/acme/foo/commodore";
theVar = theMatcher.toWildcardReplacement( thePath, "tail" );
System.out.println( "\"" + thePath + "\" -?-> \"/acme/**/${tail}\" --> " + theVar );
thePath = "/acme/foo/what/ever/path/sinclair";
theVar = theMatcher.toWildcardReplacement( thePath, "tail" );
System.out.println( "\"" + thePath + "\" -?-> \"/acme/**/${tail}\" --> " + theVar );
thePath = "/foo/what/ever/path/ghost";
theVar = theMatcher.toWildcardReplacement( thePath, "tail" );
System.out.println( "\"" + thePath + "\" -?-> \"/acme/**/${tail}\" --> " + theVar );

Note that the path variable ${tail} used here can be of any name (such as ${head}, ${var}, ${...}) and you may use multiple path variables and the path variables can be placed anywhere in the path pattern as path element.

"/acme/foo/atari" -?-> "/acme/**/${tail}" --> atari
"/acme/foo/commodore" -?-> "/acme/**/${tail}" --> commodore
"/acme/foo/what/ever/path/sinclair" -?-> "/acme/**/${tail}" --> sinclair
"/foo/what/ever/path/ghost" -?-> "/acme/**/${tail}" --> null

Matcher schema

To introspect your event matcher syntax, the Matcher type provides means to generate a MatcherSchema object, recursively including all the tree node’s MatcherSchema objects. The MatcherSchema gives insights on the construction and the current state of all the nodes building up your matcher syntax. Creating a MatcherSchema is a helpful tool to analyze more complex command line syntax constructions:

1
2
3
4
...
Matcher<String> theSportsMatcher = or( equalWith( "Soccer" ), equalWith( "Football" ) );
System.out.println( theSportsMatcher.toSchema() );
...

The above snippet defines a syntax for anything published for channels Soccer or Football (line 1) and prints out the according MatcherSchema (line 2). The result of the printable version of the MatcherSchema then looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OrMatcher: {
  ALIAS: "OR",
  DESCRIPTION: "At least one nested matcher must match (OR).",
  TYPE: "org.refcodes.matcher.OrMatcher",
  EqualWithMatcher: {
    ALIAS: "EQUAL_WITH",
    DESCRIPTION: "Compares the matchees for equality (EQUAL WITH).",
    TYPE: "org.refcodes.matcher.EqualWithMatcher",
    VALUE: "Soccer"
  },
  EqualWithMatcher: {
    ALIAS: "EQUAL_WITH",
    DESCRIPTION: "Compares the matchees for equality (EQUAL WITH).",
    TYPE: "org.refcodes.matcher.EqualWithMatcher",
    VALUE: "Football"
  }
}

As you can see, the text output format uses a JSON alike notation for easy further processing.

Examples

See the example source codes of this artifact for further information on the usage of this artifact.

Contribution guidelines

  • Report issues
  • Finding bugs
  • Helping fixing bugs
  • Making code and documentation better
  • Enhance the code

Who do I talk to?

  • Siegfried Steiner (steiner@refcodes.org)

Terms and conditions

The REFCODES.ORG group of artifacts is published under some open source licenses; covered by the refcodes-licensing (org.refcodes group) artifact - evident in each artifact in question as of the pom.xml dependency included in such artifact.