class FileAnalyzerFactory extends AnalyzerFactory {

    /** The user-friendly name of this analyzer. */
    private final String name;

    /**
     * Create an instance of {@code FileAnalyzerFactory}.
     */
    FileAnalyzerFactory() {
        this(null, null, null, null, null, null, null, null, false);
    }

    /**
     * Construct an instance of {@code FileAnalyzerFactory}. This constructor
     * should be used by subclasses to override default values.
     *
     * @param names list of file names to recognize (possibly {@code null})
     * @param prefixes list of prefixes to recognize (possibly {@code null})
     * @param suffixes list of suffixes to recognize (possibly {@code null})
     * @param magics list of magic strings to recognize (possibly {@code null})
     * @param matcher a matcher for this analyzer (possibly {@code null})
     * @param contentType content type for this analyzer (possibly {@code null})
     * @param genre the genre for this analyzer (if {@code null}, {@code
     * Genre.DATA} is used)
     * @param name user-friendly name of this analyzer (or null if it shouldn't be listed)
     * @param hasAnnotations whether the files processed by the analyzers produced by this factory can be annotated
     */
    protected FileAnalyzerFactory(
            String[] names, String[] prefixes, String[] suffixes,
            String[] magics, Matcher matcher, String contentType,
            AbstractAnalyzer.Genre genre, String name, boolean hasAnnotations) {
        super(matcher, contentType);
        this.names = asList(names);
        this.prefixes = asList(prefixes);
        this.suffixes = asList(suffixes);
        this.magics = asList(magics);
        this.genre = (genre == null) ? AbstractAnalyzer.Genre.DATA : genre;
        this.name = name;
        this.hasAnnotations = hasAnnotations;
    }

    /**
     * Helper method which wraps an array in a list.
     *
     * @param a the array to wrap ({@code null} means an empty array)
     * @return a list which wraps the array
     */
    private static <T> List<T> asList(T[] a) {
        if (a == null) {
            return Collections.emptyList();
        }
        return List.of(a);
    }

    @Override
    public String getName() {
        return name;
    }

    /**
     * Get an analyzer. If the same thread calls this method multiple times on
     * the same factory object, the exact same analyzer object will be returned
     * each time. Subclasses should not override this method, but instead
     * override the {@code newAnalyzer()} method.
     *
     * @return a {@code FileAnalyzer} instance
     * @see #newAnalyzer()
     */
    @Override
    public final AbstractAnalyzer getAnalyzer() {<FILL_FUNCTION_BODY>}

    /**
     * Free thread-local data.
     */
    @Override
    public void returnAnalyzer() {
        cachedAnalyzer.remove();
    }

    /**
     * Create a new analyzer.
     * @return an analyzer
     */
    @Override
    protected AbstractAnalyzer newAnalyzer() {
        return new FileAnalyzer(this);
    }

    /**
     * Interface for matchers which map file contents to analyzer factories.
     */
    public interface Matcher {

        /**
         * Get a value indicating if the magic is byte-precise.
         * @return true if precise
         */
        default boolean isPreciseMagic() {
            return false;
        }

        /**
         * Gets a default, reportable description of the matcher.
         * <p>
         * Subclasses can override to report a more informative description,
         * with line length up to 50 characters before starting a new line with
         * {@code \n}.
         * @return a defined, reportable String
         */
        default String description() {
            return isPreciseMagic() ? "precise matcher" : "heuristic matcher";
        }

        /**
         * Try to match the file contents with an analyzer factory.
         * If the method reads from the input stream, it must reset the
         * stream before returning.
         *
         * @param contents the first few bytes of a file
         * @param in the input stream from which the full file can be read
         * @return an analyzer factory if the contents match, or {@code null}
         * if they don't match any factory known by this matcher
         * @throws java.io.IOException in case of any read error
         */
        AnalyzerFactory isMagic(byte[] contents, InputStream in)
                throws IOException;

        /**
         * Gets the instance which the matcher produces if
         * {@link #isMagic(byte[], java.io.InputStream)} matches a file.
         * @return a defined instance
         */
        AnalyzerFactory forFactory();
    }
}

        AbstractAnalyzer fa = cachedAnalyzer.get();
        if (fa == null) {
            fa = newAnalyzer();
            cachedAnalyzer.set(fa);
        }
        return fa;
    
<methods>public abstract org.opengrok.indexer.analysis.AbstractAnalyzer getAnalyzer() ,public final org.opengrok.indexer.analysis.AbstractAnalyzer.Genre getGenre() ,public abstract java.lang.String getName() ,public boolean hasAnnotations() ,public abstract void returnAnalyzer() <variables>protected final non-sealed ThreadLocal<org.opengrok.indexer.analysis.AbstractAnalyzer> cachedAnalyzer,protected final non-sealed java.lang.String contentType,protected org.opengrok.indexer.analysis.AbstractAnalyzer.Genre genre,protected boolean hasAnnotations,protected List<java.lang.String> magics,protected final non-sealed List<org.opengrok.indexer.analysis.FileAnalyzerFactory.Matcher> matchers,protected List<java.lang.String> names,protected List<java.lang.String> prefixes,protected List<java.lang.String> suffixes