All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.mike10004.vhs.HeuristicEntryMatcher Maven / Gradle / Ivy

There is a newer version: 0.32
Show newest version
package io.github.mike10004.vhs;

import com.google.common.collect.ImmutableList;
import io.github.mike10004.vhs.harbridge.ParsedRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;

public class HeuristicEntryMatcher implements EntryMatcher {

    private static final Logger log = LoggerFactory.getLogger(HeuristicEntryMatcher.class);

    protected final ImmutableList entries;
    protected final Heuristic heuristic;
    private final int thresholdExclusive;
    private final Predicate ratedEntryFilter;

    protected HeuristicEntryMatcher(Heuristic heuristic, int thresholdExclusive, Collection entries) {
        this.entries = ImmutableList.copyOf(entries);
        this.thresholdExclusive = thresholdExclusive;
        this.heuristic = requireNonNull(heuristic);
        ratedEntryFilter = new RatedEntryFilter();
    }

    public static  EntryMatcherFactory factory(Heuristic heuristic, int thresholdExclusive) {
        return new Factory<>(heuristic, thresholdExclusive);
    }

    /**
     * Interface that maps a request to a response.
     */
    protected interface HttpRespondableCreator {
        /**
         * Constructs and returns a respondable.
         * @param request the request
         * @return the response
         * @throws IOException on I/O error
         */
        HttpRespondable createRespondable(ParsedRequest request) throws IOException;
    }

    protected static class Factory implements EntryMatcherFactory {

        private static final Logger log = LoggerFactory.getLogger(Factory.class);

        protected final Heuristic heuristic;
        protected final int thresholdExclusive;

        protected Factory(Heuristic heuristic, int thresholdExclusive) {
            this.thresholdExclusive = thresholdExclusive;
            this.heuristic = requireNonNull(heuristic);
        }

        protected  List parseEntries(List entries, EntryParser requestParser) throws IOException {
            List parsedEntries = new ArrayList<>(entries.size());
            for (E entry : entries) {
                ParsedRequest request = requestParser.parseRequest(entry);
                HttpRespondableCreator respondableCreator = new EntryRespondableCreator<>(entry, requestParser);
                ParsedEntry parsedEntry = new ParsedEntry(request, respondableCreator);
                parsedEntries.add(parsedEntry);
            }
            return parsedEntries;
        }


        @Override
        public  EntryMatcher createEntryMatcher(List entries, EntryParser requestParser) throws IOException {
            log.trace("constructing heuristic from {} har entries", entries.size());
            List parsedEntries = parseEntries(entries, requestParser);
            return new HeuristicEntryMatcher<>(heuristic, thresholdExclusive, parsedEntries);
        }
    }

    private static class EntryRespondableCreator implements HttpRespondableCreator {

        private final E entry;
        private final EntryParser requestParser;

        private EntryRespondableCreator(E entry, EntryParser requestParser) {
            this.requestParser = requestParser;
            this.entry = entry;
        }

        @Override
        public HttpRespondable createRespondable(ParsedRequest newRequest) throws IOException {
            return requestParser.parseResponse(newRequest, entry);
        }
    }

    private static final Comparator RATED_ENTRY_COMPARATOR = new Comparator() {
        @Override
        public int compare(RatedEntry o1, RatedEntry o2) {
            return o1.rating - o2.rating;
        }
    };

    private Comparator getRatedEntryComparator() {
        return RATED_ENTRY_COMPARATOR;
    }

    protected static class RatedEntry {

        public final ParsedEntry entry;
        public final int rating;

        public RatedEntry(ParsedEntry entry, int rating) {
            this.entry = requireNonNull(entry);
            this.rating = rating;
        }

    }

    private class DefaultEntryToRatingFunction implements java.util.function.Function {

        private final ParsedRequest request;

        private DefaultEntryToRatingFunction(ParsedRequest request) {
            this.request = requireNonNull(request);
        }

        @Override
        public RatedEntry apply(ParsedEntry entry) {
            int rating = heuristic.rate(requireNonNull(entry).request, request);
            return new RatedEntry(entry, rating);
        }
    }

    protected Predicate getRatedEntryFilter(S state) {
        return ratedEntryFilter;
    }

    private class RatedEntryFilter implements Predicate {

        @Override
        public boolean test(RatedEntry ratedEntry) {
            return ratedEntry.rating > thresholdExclusive;
        }
    }

    protected java.util.function.Function createEntryToRatingFunction(ParsedRequest request, S state) {
        return new DefaultEntryToRatingFunction(request);
    }

    @Nullable
    @Override
    public HttpRespondable findTopEntry(S state, ParsedRequest request) {
        List ratedEntryList = entries.stream()
                .map(createEntryToRatingFunction(request, state))
                .collect(Collectors.toList());
        Optional topRatedEntry = ratedEntryList.stream()
                .filter(getRatedEntryFilter(state))
                .max(getRatedEntryComparator());
        if (topRatedEntry.isPresent()) {
            try {
                return topRatedEntry.get().entry.responseCreator.createRespondable(request);
            } catch (IOException e) {
                log.warn("could not create response for top-rated entry", e);
            }
        }
        return null;
    }


    /**
     * Class that represents a HAR entry with a saved request and a method to produce
     * a response.
     */
    protected static class ParsedEntry {

        public final ParsedRequest request;

        public final HttpRespondableCreator responseCreator;

        public ParsedEntry(ParsedRequest request, HttpRespondableCreator responseCreator) {
            this.responseCreator = requireNonNull(responseCreator);
            this.request = requireNonNull(request);
        }

    }
}