switch(name) {
                case "--logger":
                    errors = addLogger(params, option.value, hex, errors);
                    break;
            }

package net.kanstren.tcptunnel;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * Parses command line argument and options.
 *
 * @author Teemu Kanstren.
 */
public class ArgumentParser {

    /**
     * System specific line separator.
     */
    public static String ln = System.getProperty("line.separator");

    /**
     * Parses the given arguments and options.
     *
     * @param args The arguments and options to parse. An option is expected to start with "--".
     * @return The configuration parsed from the arguments/options.
     */
    public static Params parseArgs(String[] args) {
        // in case the program is invoked with no arguments, we print the help
        if (args.length == 0)
            args = new String[] { "--help" };
        Params params = new Params();
        // we collect all errors and if any are found, report them all in the end
        String errors = "";
        // collect all options (starting with "--") here
        List<Option> options = new ArrayList<>();
        // collect all non-option parameters here
        List<String> paramStrs = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            String arg = args[i];
            if (arg.startsWith("--")) {
                if (arg.equals("--hex")) {
                    // hex is a standalone option, so give it a value and move straight to next one
                    options.add(new Option("--hex", "true"));
                    continue;
                }
                if (arg.equals("--help")) {
                    // help is a standalone option, so give it a value and move straight to next one
                    options.add(new Option("--help", "true"));
                    continue;
                }
                if (arg.equals("--trailing-lf")) {
                    options.add(new Option("--trailing-lf", "true"));
                    continue;
                }
                if (arg.equals("--udp-dns")) {
                    options.add(new Option("--udp-dns", "true"));
                    continue;
                }
                if (arg.equals("--udp-tun")) {
                    options.add(new Option("--udp-tun", "true"));
                    continue;
                }
                if (arg.equals("--gzip")) {
                    options.add(new Option("--gzip", "true"));
                    continue;
                }
                if (args.length <= i + 1) {
                    // all options coming this far should have a value. otherwise it is an error.
                    errors += "No value given for option " + arg + ". Please provide one." + ln;
                    break;
                }
                Option option = new Option(arg, args[i + 1]);
                options.add(option);
                i++;
            } else {
                paramStrs.add(arg);
            }
        }
        // now that we collected all the options, parse the actual content from their values and check they are valid
        errors = parseOptions(options, params, errors);
        // if the options were ok, move to parsing and checking the non-option parameters
        if (params.shouldRun()) {
            boolean paramsOK = true;
            if (paramStrs.size() < 3) {
                errors += "Too few arguments. Need 3, got " + paramStrs.size() + ": " + paramStrs + "." + ln;
                paramsOK = false;
            }
            if (paramStrs.size() > 3) {
                errors += "Too many arguments. Need 3, got " + paramStrs.size() + ": " + paramStrs + "." + ln;
                paramsOK = false;
            }
            if (paramsOK)
                errors = parseParams(paramStrs, params, errors);
        }
        // every error msg ends with a linefeed, so remove the last one
        if (errors.length() > 0)
            errors = errors.substring(0, errors.length() - ln.length());
        params.setErrors(errors);
        return params;
    }

    /**
     * Parse the actual content from the previously captured option definitions.
     *
     * @param options The options from which to parse the content.
     * @param params This is where we store the parsed option content.
     * @param errors The errors previously observed.
     * @return Updated errors with the old one intact and new ones added.
     */
    private static String parseOptions(List<Option> options, Params params, String errors) {
        // we set this to true if the --hex option is given
        boolean hex = false;
        // this is to trace if any loggers are defined. if not, we add the default later.
        int loggers = 0;
        for (Option option : options) {
            String name = option.name;
            switch(name) {
                case "--buffersize":
                    // here we have the buffersize to use for capturing the observed stream and for writing to files etc.
                    try {
                        int bufferSize = Integer.parseInt(option.value);
                        params.setBufferSize(bufferSize);
                        if (bufferSize <= 0)
                            errors += "Buffer size has to be > 0, was: " + bufferSize + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'buffersize':" + option.value + "." + ln;
                    }
                    break;
                case "--encoding":
                    // here we have the character encoding used to decode strings from raw bytes
                    params.setEncoding(option.value);
                    try {
                        boolean supported = Charset.isSupported(option.value);
                        if (!supported)
                            errors += "Unsupported encoding: '" + option.value + "'." + ln;
                    } catch (Exception e) {
                        errors += "Unsupported encoding: '" + option.value + "'." + ln;
                    }
                    break;
                case "--down":
                    // this is the path for storing the downstream traffic (from remote host to local port connection)
                    params.setDownFilePath(option.value);
                    break;
                case "--up":
                    // this is the path for storing the upstream traffic (from local port to remote host)
                    params.setUpFilePath(option.value);
                    break;
                case "--mirror-down-host":
                    params.setMirrorDownHost(option.value);
                    break;
                case "--mirror-down-port":
                    try {
                        int iValue = Integer.parseInt(option.value);
                        params.setMirrorDownPort(iValue);
                        if (iValue <= 1 || iValue > 65535)
                            errors += "Invalid mirror-down port value. Should be between 1-65535, was: " + iValue + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'mirrordownport':" + option.value + "." + ln;
                    }
                    break;
                case "--mirror-up-host":
                    params.setMirrorUpHost(option.value);
                    break;
                case "--mirror-up-port":
                    try {
                        int iValue = Integer.parseInt(option.value);
                        params.setMirrorUpPort(iValue);
                        if (iValue <= 1 || iValue > 65535)
                            errors += "Invalid mirror-up port value. Should be between 1-65535, was: " + iValue + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'mirrorupport':" + option.value + "." + ln;
                    }
                    break;
                case "--hex":
                    // if using byte console logger, this setting changes printing the bytes from integer list to set of hex characters
                    hex = true;
                    break;
                case "--help":
                    // obviously, print out the help
                    System.out.println(help());
                    params.setShouldRun(false);
                    return "";
                case "--logger":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    loggers++;
                    // handle creation in round 2
                    break;
                case "--udp-dns":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    params.setDNS(true);
                    break;
                case "--udp-tun":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    params.setUDP(true);
                    break;
                case "--trailing-lf":
                    // enable adding a trailing LF to every console print
                    params.setAddLF(true);
                    break;
                case "--gzip":
                    // try to decompress gzip encoding in HTTP requests for string logs
                    params.setGzip(true);
                    break;
                default:
                    // anything not processed above is invalid..
                    errors += "Invalid option '" + name + "'." + ln;
                    break;
            }
        }
        // add the default logger if none found before
        if (loggers == 0)
            options.add(new Option("--logger", "console-string"));
        for (Option option : options) {
            // we create loggers in this separate iteration to have access to affecting parameters (e.g., --hex) and default logger if none specified by user
            String name = option.name;

        }
        return errors;
    }

    /**
     * Adds a given type of logger to parser results.
     *
     * @param params For storing parsed parameters (logger instances).
     * @param type The type identifier for the logger.
     * @param hex Whether to print bytes as ints or hex in relevant loggers.
     * @param errors Errors so far.
     * @return Previous and new errors.
     */
    private static String addLogger(Params params, String type, boolean hex, String errors) {
        switch(type) {
            case "console-string":
                // print decoded strings to console
                params.enableStringConsoleLogger();
                break;
            case "console-bytes":
                // print bytes to console, hex or int
                params.enableByteConsoleLogger(hex);
                break;
            case "file-string":
                // write decoded strings to file
                try {
                    params.enableStringFileLogger(params.getDownFilePath(), params.getUpFilePath());
                } catch (IOException e) {
                    errors += "Unable to create string file logger:" + e.getMessage();
                    e.printStackTrace();
                }
                break;
            case "file-bytes":
                // write raw bytes as binary to file
                try {
                    params.enableByteFileLogger(params.getDownFilePath(), params.getUpFilePath());
                } catch (IOException e) {
                    errors += "Unable to create byte file logger:" + e.getMessage();
                }
                break;
            case "mirror-up":
                // write raw bytes as binary to another socket for upstream traffic
                params.enableMirrorUpStreamLogger(params.getMirrorUpHost(), params.getMirrorUpPort());
                break;
            case "mirror-down":
                // write raw bytes as binary to another socket for downstream traffic
                params.enableMirrorDownStreamLogger(params.getMirrorDownHost(), params.getMirrorDownPort());
                break;
            default:
                // anything not processed above is considerd invalid
                errors += "Unknown logger type: '" + type + "'." + ln;
        }
        return errors;
    }

    /**
     * Parse the actual parameters, meaning source (local) port, remote host and remote port.
     * Parameters are the ones not starting with '-'.
     *
     * @param paramStrs The strings for parameters, filtered from options.
     * @param params For storing the parsing results.
     * @param errors Previous parsing errors so far.
     * @return Previous and new parsing errors.
     */
    private static String parseParams(List<String> paramStrs, Params params, String errors) {
        try {
            int sourcePort = Integer.parseInt(paramStrs.get(0));
            params.setSourcePort(sourcePort);
            if (sourcePort < 1 || sourcePort > 65535)
                errors += "Port numbers have to be in range 1-65535, source port was: " + sourcePort + "." + ln;
        } catch (NumberFormatException e) {
            errors += "Unable to parse source port from: '" + paramStrs.get(0) + "'." + ln;
        }
        params.setRemoteHost(paramStrs.get(1));
        try {
            int remotePort = Integer.parseInt(paramStrs.get(2));
            params.setRemotePort(remotePort);
            if (remotePort < 1 || remotePort > 65535)
                errors += "Port numbers have to be in range 1-65535, remote port was: " + remotePort + "." + ln;
        } catch (NumberFormatException e) {
            errors += "Unable to parse remote port from: '" + paramStrs.get(2) + "'." + ln;
        }
        return errors;
    }

    /**
     * @return The help text to show to user.
     */
    public static String help() {
        InputStream is = ArgumentParser.class.getResourceAsStream("helptext.txt");
        String template = Utils.getResource(is);
        return String.format(template, Params.DEFAULT_BUFFER_SIZE, Params.DEFAULT_ENCONDING, Params.DEFAULT_DOWN_PATH, Params.DEFAULT_UP_PATH);
    }

    /**
     * A class for holding a name-value pair as a configuration option.
     */
    private static class Option {

        /**
         * The option name.
         */
        public final String name;

        /**
         * The unparsed option value.
         */
        public final String value;

        /**
         * @param name The option name.
         * @param value The unparsed option value.
         */
        public Option(String name, String value) {
            this.name = name;
            this.value = value;
        }
    }
}


try {
                        boolean supported = Charset.isSupported(option.value);
                        if (!supported)
                            errors += "Unsupported encoding: '" + option.value + "'." + ln;
                    } catch (Exception e) {
                        errors += "Unsupported encoding: '" + option.value + "'." + ln;
                    }

package net.kanstren.tcptunnel;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * Parses command line argument and options.
 *
 * @author Teemu Kanstren.
 */
public class ArgumentParser {

    /**
     * System specific line separator.
     */
    public static String ln = System.getProperty("line.separator");

    /**
     * Parses the given arguments and options.
     *
     * @param args The arguments and options to parse. An option is expected to start with "--".
     * @return The configuration parsed from the arguments/options.
     */
    public static Params parseArgs(String[] args) {
        // in case the program is invoked with no arguments, we print the help
        if (args.length == 0)
            args = new String[] { "--help" };
        Params params = new Params();
        // we collect all errors and if any are found, report them all in the end
        String errors = "";
        // collect all options (starting with "--") here
        List<Option> options = new ArrayList<>();
        // collect all non-option parameters here
        List<String> paramStrs = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            String arg = args[i];
            if (arg.startsWith("--")) {
                if (arg.equals("--hex")) {
                    // hex is a standalone option, so give it a value and move straight to next one
                    options.add(new Option("--hex", "true"));
                    continue;
                }
                if (arg.equals("--help")) {
                    // help is a standalone option, so give it a value and move straight to next one
                    options.add(new Option("--help", "true"));
                    continue;
                }
                if (arg.equals("--trailing-lf")) {
                    options.add(new Option("--trailing-lf", "true"));
                    continue;
                }
                if (arg.equals("--udp-dns")) {
                    options.add(new Option("--udp-dns", "true"));
                    continue;
                }
                if (arg.equals("--udp-tun")) {
                    options.add(new Option("--udp-tun", "true"));
                    continue;
                }
                if (arg.equals("--gzip")) {
                    options.add(new Option("--gzip", "true"));
                    continue;
                }
                if (args.length <= i + 1) {
                    // all options coming this far should have a value. otherwise it is an error.
                    errors += "No value given for option " + arg + ". Please provide one." + ln;
                    break;
                }
                Option option = new Option(arg, args[i + 1]);
                options.add(option);
                i++;
            } else {
                paramStrs.add(arg);
            }
        }
        // now that we collected all the options, parse the actual content from their values and check they are valid
        errors = parseOptions(options, params, errors);
        // if the options were ok, move to parsing and checking the non-option parameters
        if (params.shouldRun()) {
            boolean paramsOK = true;
            if (paramStrs.size() < 3) {
                errors += "Too few arguments. Need 3, got " + paramStrs.size() + ": " + paramStrs + "." + ln;
                paramsOK = false;
            }
            if (paramStrs.size() > 3) {
                errors += "Too many arguments. Need 3, got " + paramStrs.size() + ": " + paramStrs + "." + ln;
                paramsOK = false;
            }
            if (paramsOK)
                errors = parseParams(paramStrs, params, errors);
        }
        // every error msg ends with a linefeed, so remove the last one
        if (errors.length() > 0)
            errors = errors.substring(0, errors.length() - ln.length());
        params.setErrors(errors);
        return params;
    }

    /**
     * Parse the actual content from the previously captured option definitions.
     *
     * @param options The options from which to parse the content.
     * @param params This is where we store the parsed option content.
     * @param errors The errors previously observed.
     * @return Updated errors with the old one intact and new ones added.
     */
    private static String parseOptions(List<Option> options, Params params, String errors) {
        // we set this to true if the --hex option is given
        boolean hex = false;
        // this is to trace if any loggers are defined. if not, we add the default later.
        int loggers = 0;
        for (Option option : options) {
            String name = option.name;
            switch(name) {
                case "--buffersize":
                    // here we have the buffersize to use for capturing the observed stream and for writing to files etc.
                    try {
                        int bufferSize = Integer.parseInt(option.value);
                        params.setBufferSize(bufferSize);
                        if (bufferSize <= 0)
                            errors += "Buffer size has to be > 0, was: " + bufferSize + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'buffersize':" + option.value + "." + ln;
                    }
                    break;
                case "--encoding":
                    // here we have the character encoding used to decode strings from raw bytes
                    params.setEncoding(option.value);

                    break;
                case "--down":
                    // this is the path for storing the downstream traffic (from remote host to local port connection)
                    params.setDownFilePath(option.value);
                    break;
                case "--up":
                    // this is the path for storing the upstream traffic (from local port to remote host)
                    params.setUpFilePath(option.value);
                    break;
                case "--mirror-down-host":
                    params.setMirrorDownHost(option.value);
                    break;
                case "--mirror-down-port":
                    try {
                        int iValue = Integer.parseInt(option.value);
                        params.setMirrorDownPort(iValue);
                        if (iValue <= 1 || iValue > 65535)
                            errors += "Invalid mirror-down port value. Should be between 1-65535, was: " + iValue + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'mirrordownport':" + option.value + "." + ln;
                    }
                    break;
                case "--mirror-up-host":
                    params.setMirrorUpHost(option.value);
                    break;
                case "--mirror-up-port":
                    try {
                        int iValue = Integer.parseInt(option.value);
                        params.setMirrorUpPort(iValue);
                        if (iValue <= 1 || iValue > 65535)
                            errors += "Invalid mirror-up port value. Should be between 1-65535, was: " + iValue + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'mirrorupport':" + option.value + "." + ln;
                    }
                    break;
                case "--hex":
                    // if using byte console logger, this setting changes printing the bytes from integer list to set of hex characters
                    hex = true;
                    break;
                case "--help":
                    // obviously, print out the help
                    System.out.println(help());
                    params.setShouldRun(false);
                    return "";
                case "--logger":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    loggers++;
                    // handle creation in round 2
                    break;
                case "--udp-dns":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    params.setDNS(true);
                    break;
                case "--udp-tun":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    params.setUDP(true);
                    break;
                case "--trailing-lf":
                    // enable adding a trailing LF to every console print
                    params.setAddLF(true);
                    break;
                case "--gzip":
                    // try to decompress gzip encoding in HTTP requests for string logs
                    params.setGzip(true);
                    break;
                default:
                    // anything not processed above is invalid..
                    errors += "Invalid option '" + name + "'." + ln;
                    break;
            }
        }
        // add the default logger if none found before
        if (loggers == 0)
            options.add(new Option("--logger", "console-string"));
        for (Option option : options) {
            // we create loggers in this separate iteration to have access to affecting parameters (e.g., --hex) and default logger if none specified by user
            String name = option.name;
            switch(name) {
                case "--logger":
                    errors = addLogger(params, option.value, hex, errors);
                    break;
            }
        }
        return errors;
    }

    /**
     * Adds a given type of logger to parser results.
     *
     * @param params For storing parsed parameters (logger instances).
     * @param type The type identifier for the logger.
     * @param hex Whether to print bytes as ints or hex in relevant loggers.
     * @param errors Errors so far.
     * @return Previous and new errors.
     */
    private static String addLogger(Params params, String type, boolean hex, String errors) {
        switch(type) {
            case "console-string":
                // print decoded strings to console
                params.enableStringConsoleLogger();
                break;
            case "console-bytes":
                // print bytes to console, hex or int
                params.enableByteConsoleLogger(hex);
                break;
            case "file-string":
                // write decoded strings to file
                try {
                    params.enableStringFileLogger(params.getDownFilePath(), params.getUpFilePath());
                } catch (IOException e) {
                    errors += "Unable to create string file logger:" + e.getMessage();
                    e.printStackTrace();
                }
                break;
            case "file-bytes":
                // write raw bytes as binary to file
                try {
                    params.enableByteFileLogger(params.getDownFilePath(), params.getUpFilePath());
                } catch (IOException e) {
                    errors += "Unable to create byte file logger:" + e.getMessage();
                }
                break;
            case "mirror-up":
                // write raw bytes as binary to another socket for upstream traffic
                params.enableMirrorUpStreamLogger(params.getMirrorUpHost(), params.getMirrorUpPort());
                break;
            case "mirror-down":
                // write raw bytes as binary to another socket for downstream traffic
                params.enableMirrorDownStreamLogger(params.getMirrorDownHost(), params.getMirrorDownPort());
                break;
            default:
                // anything not processed above is considerd invalid
                errors += "Unknown logger type: '" + type + "'." + ln;
        }
        return errors;
    }

    /**
     * Parse the actual parameters, meaning source (local) port, remote host and remote port.
     * Parameters are the ones not starting with '-'.
     *
     * @param paramStrs The strings for parameters, filtered from options.
     * @param params For storing the parsing results.
     * @param errors Previous parsing errors so far.
     * @return Previous and new parsing errors.
     */
    private static String parseParams(List<String> paramStrs, Params params, String errors) {
        try {
            int sourcePort = Integer.parseInt(paramStrs.get(0));
            params.setSourcePort(sourcePort);
            if (sourcePort < 1 || sourcePort > 65535)
                errors += "Port numbers have to be in range 1-65535, source port was: " + sourcePort + "." + ln;
        } catch (NumberFormatException e) {
            errors += "Unable to parse source port from: '" + paramStrs.get(0) + "'." + ln;
        }
        params.setRemoteHost(paramStrs.get(1));
        try {
            int remotePort = Integer.parseInt(paramStrs.get(2));
            params.setRemotePort(remotePort);
            if (remotePort < 1 || remotePort > 65535)
                errors += "Port numbers have to be in range 1-65535, remote port was: " + remotePort + "." + ln;
        } catch (NumberFormatException e) {
            errors += "Unable to parse remote port from: '" + paramStrs.get(2) + "'." + ln;
        }
        return errors;
    }

    /**
     * @return The help text to show to user.
     */
    public static String help() {
        InputStream is = ArgumentParser.class.getResourceAsStream("helptext.txt");
        String template = Utils.getResource(is);
        return String.format(template, Params.DEFAULT_BUFFER_SIZE, Params.DEFAULT_ENCONDING, Params.DEFAULT_DOWN_PATH, Params.DEFAULT_UP_PATH);
    }

    /**
     * A class for holding a name-value pair as a configuration option.
     */
    private static class Option {

        /**
         * The option name.
         */
        public final String name;

        /**
         * The unparsed option value.
         */
        public final String value;

        /**
         * @param name The option name.
         * @param value The unparsed option value.
         */
        public Option(String name, String value) {
            this.name = name;
            this.value = value;
        }
    }
}


@Override
    public Object getCoreCacheKey() {
        return in.getCoreCacheKey();
    }

package org.apache.lucene.index;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.index.IndexReader.InvertParams;
import org.apache.lucene.index.IndexReader.InvertResult;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.MapBackedSet;
import org.apache.solr.schema.IndexSchema;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *  A FilterIndexReader contains another IndexReader, which it
 * uses as its basic source of data, possibly transforming the data along the
 * way or providing additional functionality. The class
 * FilterIndexReader itself simply implements all abstract methods
 * of IndexReader with versions that pass all requests to the
 * contained index reader. Subclasses of FilterIndexReader may
 * further override some of these methods and may also provide additional
 * methods and fields.
 *
Note: The default implementation of {@link FilterIndexReader#doOpenIfChanged}
 * throws {@link UnsupportedOperationException} (like the base class),
 * so it's not possible to reopen a FilterIndexReader.
 * To reopen, you have to first reopen the underlying reader
 * and wrap it again with the custom filter.
 */
public class FilterIndexReader extends IndexReader {

    /**
     * Base class for filtering {@link TermDocs} implementations.
     */
    public static class FilterTermDocs implements TermDocs {

        protected TermDocs in;

        public void debug(StringBuffer buff) {
            in.debug(buff);
        }

        public FilterTermDocs(TermDocs in) {
            this.in = in;
        }

        public void seek(Term term) throws IOException {
            in.seek(term);
        }

        public void seek(TermEnum termEnum) throws IOException {
            in.seek(termEnum);
        }

        public int doc() {
            return in.doc();
        }

        public int freq() {
            return in.freq();
        }

        public boolean next() throws IOException {
            return in.next();
        }

        public int read(int[] docs, int[] freqs) throws IOException {
            return in.read(docs, freqs);
        }

        public boolean skipTo(int i) throws IOException {
            return in.skipTo(i);
        }

        public void close() throws IOException {
            in.close();
        }
    }

    /**
     * Base class for filtering {@link TermPositions} implementations.
     */
    public static class FilterTermPositions extends FilterTermDocs implements TermPositions {

        public FilterTermPositions(TermPositions in) {
            super(in);
        }

        public int nextPosition() throws IOException {
            return ((TermPositions) this.in).nextPosition();
        }

        public int getPayloadLength() {
            return ((TermPositions) this.in).getPayloadLength();
        }

        public byte[] getPayload(byte[] data, int offset) throws IOException {
            return ((TermPositions) this.in).getPayload(data, offset);
        }

        // TODO: Remove warning after API has been finalized
        public boolean isPayloadAvailable() {
            return ((TermPositions) this.in).isPayloadAvailable();
        }
    }

    /**
     * Base class for filtering {@link TermEnum} implementations.
     */
    public static class FilterTermEnum extends TermEnum {

        protected TermEnum in;

        public FilterTermEnum(TermEnum in) {
            this.in = in;
        }

        @Override
        public boolean next() throws IOException {
            return in.next();
        }

        @Override
        public Term term() {
            return in.term();
        }

        @Override
        public int docFreq() {
            return in.docFreq();
        }

        @Override
        public void close() throws IOException {
            in.close();
        }
    }

    protected IndexReader in;

    /**
     *
Construct a FilterIndexReader based on the specified base reader.
     * Directory locking for delete, undeleteAll, and setNorm operations is
     * left to the base reader.

     *
Note that base reader is closed if this FilterIndexReader is closed.

     * @param in specified base reader.
     */
    public FilterIndexReader(IndexReader in) {
        super();
        this.in = in;
        readerFinishedListeners = new MapBackedSet<ReaderFinishedListener>(new ConcurrentHashMap<ReaderFinishedListener, Boolean>());
    }

    @Override
    public Directory directory() {
        ensureOpen();
        return in.directory();
    }

    @Override
    public TermFreqVector[] getTermFreqVectors(int docNumber) throws IOException {
        ensureOpen();
        return in.getTermFreqVectors(docNumber);
    }

    @Override
    public TermFreqVector getTermFreqVector(int docNumber, String field) throws IOException {
        ensureOpen();
        return in.getTermFreqVector(docNumber, field);
    }

    @Override
    public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
        ensureOpen();
        in.getTermFreqVector(docNumber, field, mapper);
    }

    @Override
    public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
        ensureOpen();
        in.getTermFreqVector(docNumber, mapper);
    }

    @Override
    public int numDocs() {
        // Don't call ensureOpen() here (it could affect performance)
        return in.numDocs();
    }

    @Override
    public int maxDoc() {
        // Don't call ensureOpen() here (it could affect performance)
        return in.maxDoc();
    }

    @Override
    public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
        ensureOpen();
        return in.document(n, fieldSelector);
    }

    @Override
    public boolean isDeleted(int n) {
        // Don't call ensureOpen() here (it could affect performance)
        return in.isDeleted(n);
    }

    @Override
    public boolean hasDeletions() {
        ensureOpen();
        return in.hasDeletions();
    }

    @Override
    protected void doUndeleteAll() throws CorruptIndexException, IOException {
        in.undeleteAll();
    }

    @Override
    public boolean hasNorms(String field) throws IOException {
        ensureOpen();
        return in.hasNorms(field);
    }

    @Override
    public byte[] norms(String f) throws IOException {
        ensureOpen();
        return in.norms(f);
    }

    @Override
    public void norms(String f, byte[] bytes, int offset) throws IOException {
        ensureOpen();
        in.norms(f, bytes, offset);
    }

    @Override
    protected void doSetNorm(int d, String f, byte b) throws CorruptIndexException, IOException {
        in.setNorm(d, f, b);
    }

    @Override
    public TermEnum terms() throws IOException {
        ensureOpen();
        return in.terms();
    }

    @Override
    public TermEnum terms(Term t) throws IOException {
        ensureOpen();
        return in.terms(t);
    }

    @Override
    public int docFreq(Term t) throws IOException {
        ensureOpen();
        return in.docFreq(t);
    }

    @Override
    public TermDocs termDocs(int buffer) throws IOException {
        ensureOpen();
        return in.termDocs(buffer);
    }

    @Override
    public TermDocs termDocs(Term term, int buffer) throws IOException {
        ensureOpen();
        return in.termDocs(term, buffer);
    }

    @Override
    public TermPositions termPositions() throws IOException {
        ensureOpen();
        return in.termPositions();
    }

    @Override
    protected void doDelete(int n) throws CorruptIndexException, IOException {
        in.deleteDocument(n);
    }

    @Override
    protected void doCommit(Map<String, String> commitUserData) throws IOException {
        in.commit(commitUserData);
    }

    @Override
    protected void doClose() throws IOException {
        in.close();
    }

    @Override
    public Collection<String> getFieldNames(IndexReader.FieldOption fieldNames) {
        ensureOpen();
        return in.getFieldNames(fieldNames);
    }

    @Override
    public long getVersion() {
        ensureOpen();
        return in.getVersion();
    }

    @Override
    public boolean isCurrent() throws CorruptIndexException, IOException {
        ensureOpen();
        return in.isCurrent();
    }

    @Deprecated
    @Override
    public boolean isOptimized() {
        ensureOpen();
        return in.isOptimized();
    }

    @Override
    public IndexReader[] getSequentialSubReaders() {
        return in.getSequentialSubReaders();
    }

    @Override
    public Map<String, String> getCommitUserData() {
        return in.getCommitUserData();
    }

    /**
     * If the subclass of FilteredIndexReader modifies the
     *  contents of the FieldCache, you must override this
     *  method to provide a different key
     */

    public String getStringCacheKey() {
        return in.getStringCacheKey();
    }

    /**
     * If the subclass of FilteredIndexReader modifies the
     *  deleted docs, you must override this method to provide
     *  a different key
     */
    @Override
    public Object getDeletesCacheKey() {
        return in.getDeletesCacheKey();
    }

    public InvertResult invertScan(IndexSchema schema, InvertParams params) throws Exception {
        return in.invertScan(schema, params);
    }

    @Override
    public String toString() {
        final StringBuilder buffer = new StringBuilder("FilterReader(");
        buffer.append(in);
        buffer.append(')');
        return buffer.toString();
    }

    @Override
    public void addReaderFinishedListener(ReaderFinishedListener listener) {
        super.addReaderFinishedListener(listener);
        in.addReaderFinishedListener(listener);
    }

    @Override
    public void removeReaderFinishedListener(ReaderFinishedListener listener) {
        super.removeReaderFinishedListener(listener);
        in.removeReaderFinishedListener(listener);
    }

    @Override
    public int getMaxInterval() throws Exception {
        return in.getMaxInterval();
    }
}


@ZenCodeType.Method
    public void addRecipe(String name, ItemStackIngredient input, ICrTGasStack output) {
        addRecipe(makeRecipe(getAndValidateName(name), input, getAndValidateNotEmpty(output)));
    }

package mekanism.common.integration.crafttweaker.recipe;

import com.blamejared.crafttweaker.api.annotations.ZenRegister;
import mekanism.api.chemical.gas.GasStack;
import mekanism.api.recipes.ItemStackToGasRecipe;
import mekanism.api.recipes.inputs.ItemStackIngredient;
import mekanism.common.integration.crafttweaker.CrTConstants;
import mekanism.common.integration.crafttweaker.CrTUtils;
import mekanism.common.integration.crafttweaker.chemical.CrTChemicalStack.CrTGasStack;
import mekanism.common.integration.crafttweaker.chemical.ICrTChemicalStack.ICrTGasStack;
import mekanism.common.recipe.MekanismRecipeType;
import mekanism.common.recipe.impl.ChemicalOxidizerIRecipe;
import mekanism.common.recipe.impl.GasConversionIRecipe;
import net.minecraft.util.ResourceLocation;
import org.openzen.zencode.java.ZenCodeType;

@ZenRegister
@ZenCodeType.Name(CrTConstants.CLASS_RECIPE_ITEM_STACK_TO_GAS)
public abstract class ItemStackToGasRecipeManager extends MekanismRecipeManager<ItemStackToGasRecipe> {

    protected ItemStackToGasRecipeManager(MekanismRecipeType<ItemStackToGasRecipe, ?> recipeType) {
        super(recipeType);
    }

    /**
     * Adds a recipe that an item into a gas.
     *

     * If this is called from the gas conversion recipe manager, this will be a gas conversion recipe and be able to be used in any slots in Mekanism machines that are
     * able to convert items to gases, for example in the Osmium Compressor and a variety of other machines.
     *

     * If this is called from the oxidizing recipe manager, this will be an oxidizing recipe. Chemical Oxidizers can process this recipe type.
     *
     * @param name   Name of the new recipe.
     * @param input  {@link ItemStackIngredient} representing the input of the recipe.
     * @param output {@link ICrTGasStack} representing the output of the recipe.
     */

    protected abstract ItemStackToGasRecipe makeRecipe(ResourceLocation id, ItemStackIngredient input, GasStack output);

    @Override
    protected ActionAddMekanismRecipe getAction(ItemStackToGasRecipe recipe) {
        return new ActionAddMekanismRecipe(recipe) {

            @Override
            protected String describeOutputs() {
                return CrTUtils.describeOutputs(getRecipe().getOutputDefinitionNew(), CrTGasStack::new);
            }
        };
    }

    @ZenRegister
    @ZenCodeType.Name(CrTConstants.CLASS_RECIPE_GAS_CONVERSION)
    public static class GasConversionRecipeManager extends ItemStackToGasRecipeManager {

        public static final GasConversionRecipeManager INSTANCE = new GasConversionRecipeManager();

        private GasConversionRecipeManager() {
            super(MekanismRecipeType.GAS_CONVERSION);
        }

        @Override
        protected ItemStackToGasRecipe makeRecipe(ResourceLocation id, ItemStackIngredient input, GasStack output) {
            return new GasConversionIRecipe(id, input, output);
        }
    }

    @ZenRegister
    @ZenCodeType.Name(CrTConstants.CLASS_RECIPE_OXIDIZING)
    public static class ChemicalOxidizerRecipeManager extends ItemStackToGasRecipeManager {

        public static final ChemicalOxidizerRecipeManager INSTANCE = new ChemicalOxidizerRecipeManager();

        private ChemicalOxidizerRecipeManager() {
            super(MekanismRecipeType.OXIDIZING);
        }

        @Override
        protected ItemStackToGasRecipe makeRecipe(ResourceLocation id, ItemStackIngredient input, GasStack output) {
            return new ChemicalOxidizerIRecipe(id, input, output);
        }
    }
}