public List<Stream> match(Message message) {
        final Set<Stream> result = Sets.newHashSet();
        final Set<String> blackList = Sets.newHashSet();

        for (final Rule rule : rulesList) {
            if (blackList.contains(rule.getStreamId())) {
                continue;
            }

            final StreamRule streamRule = rule.getStreamRule();
            final StreamRuleType streamRuleType = streamRule.getType();
            final Stream.MatchingType matchingType = rule.getMatchingType();
            if (!ruleTypesNotNeedingFieldPresence.contains(streamRuleType)
                    && !message.hasField(streamRule.getField())) {
                if (matchingType == Stream.MatchingType.AND) {
                    result.remove(rule.getStream());
                    // blacklist stream because it can't match anymore
                    blackList.add(rule.getStreamId());
                }

                continue;
            }

            final Stream stream;
            if (streamRuleType != StreamRuleType.REGEX) {
                stream = rule.match(message);
            } else {
                stream = rule.matchWithTimeOut(message, streamProcessingTimeout, TimeUnit.MILLISECONDS);
            }

            if (stream == null) {
                if (matchingType == Stream.MatchingType.AND) {
                    result.remove(rule.getStream());
                    // blacklist stream because it can't match anymore
                    blackList.add(rule.getStreamId());
                }
            } else {
                result.add(stream);
                if (matchingType == Stream.MatchingType.OR) {
                    // blacklist stream because it is already matched
                    blackList.add(rule.getStreamId());
                }
            }
        }

        final Stream defaultStream = defaultStreamProvider.get();
        boolean alreadyRemovedDefaultStream = false;
        for (Stream stream : result) {
            if (stream.getRemoveMatchesFromDefaultStream()) {
                if (alreadyRemovedDefaultStream || message.removeStream(defaultStream)) {
                    alreadyRemovedDefaultStream = true;
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Successfully removed default stream <{}> from message <{}>", defaultStream.getId(), message.getId());
                    }
                } else {
                    // A previously executed message processor (or Illuminate) has likely already removed the
                    // default stream from the message. Now, the message has matched a stream in the Graylog
                    // MessageFilterChain, and the matching stream is also set to remove the default stream.
                    // This is usually from user-defined stream rules, and is generally not a problem.
                    cannotRemoveDefaultMeter.inc();
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Couldn't remove default stream <{}> from message <{}>", defaultStream.getId(), message.getId());
                    }
                }
            }
        }

        return ImmutableList.copyOf(result);
    }
@Test
    public void testInvertedRulesMatch() throws Exception {
        final StreamMock stream = getStreamMock("test");
        final StreamRuleMock rule1 = new StreamRuleMock(ImmutableMap.of(
                "_id", new ObjectId(),
                "field", "testfield1",
                "value", "1",
                "type", StreamRuleType.PRESENCE.toInteger(),
                "stream_id", stream.getId()
        ));
        final StreamRuleMock rule2 = new StreamRuleMock(ImmutableMap.of(
                "_id", new ObjectId(),
                "field", "testfield2",
                "inverted", true,
                "type", StreamRuleType.PRESENCE.toInteger(),
                "stream_id", stream.getId()
        ));

        stream.setStreamRules(Lists.newArrayList(rule1, rule2));

        final StreamRouterEngine engine = newEngine(Lists.newArrayList(stream));

        // Without testfield1 and testfield2 in the message.
        final Message message1 = getMessage();

        assertTrue(engine.match(message1).isEmpty());

        // With testfield1 and testfield2 in the message.
        final Message message2 = getMessage();
        message2.addField("testfield1", "testvalue");
        message2.addField("testfield2", "testvalue");

        assertTrue(engine.match(message2).isEmpty());

        // With testfield1 and not testfield2 in the message.
        final Message message3 = getMessage();
        message3.addField("testfield1", "testvalue");

        assertEquals(Lists.newArrayList(stream), engine.match(message3));

        // With testfield2 in the message.
        final Message message4 = getMessage();
        message4.addField("testfield2", "testvalue");

        assertTrue(engine.match(message4).isEmpty());
    }