class QueryParser {

	private Shape filter(final JsonObject query) {
		return and(query.entrySet().stream()

				.filter(entry -> !entry.getKey().startsWith("_")) // ignore reserved properties
				.filter(entry -> !entry.getValue().equals(JsonValue.NULL)) // ignore null properties

				.map(entry -> { // longest matches first

					final String key=entry.getKey();
					final JsonValue value=entry.getValue();

					return key.startsWith("^") ? filter(key.substring(1), value, shape, this::datatype)
							: key.startsWith("@") ? filter(key.substring(1), value, shape, this::clazz)

							: key.startsWith(">=") ? filter(key.substring(2), value, shape, this::minInclusive)
							: key.startsWith("<=") ? filter(key.substring(2), value, shape, this::maxInclusive)
							: key.startsWith(">") ? filter(key.substring(1), value, shape, this::minExclusive)
							: key.startsWith("<") ? filter(key.substring(1), value, shape, this::maxExclusive)

							: key.startsWith("$>") ? filter(key.substring(2), value, shape, this::minLength)
							: key.startsWith("$<") ? filter(key.substring(2), value, shape, this::maxLength)
							: key.startsWith("*") ? filter(key.substring(1), value, shape, this::pattern)
							: key.startsWith("~") ? filter(key.substring(1), value, shape, this::like)

							: key.startsWith("#>") ? filter(key.substring(2), value, shape, this::minCount)
							: key.startsWith("#<") ? filter(key.substring(2), value, shape, this::maxCount)

							: key.startsWith("%") ? filter(key.substring(1), value, shape, this::in)
							: key.startsWith("!") ? filter(key.substring(1), value, shape, this::all)
							: key.startsWith("?") ? filter(key.substring(1), value, shape, this::any)

							: filter(key, value, shape, this::any);

				})

				.collect(toList())
		);
	}

	  QueryParser(final String base, final Shape shape, final Format<?> format);

}