/*
 * Copyright 2017 LINE Corporation
 *
 * LINE Corporation licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.linecorp.armeria.server.docs;

import static com.google.common.collect.ImmutableMap.toImmutableMap;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.reflections.Configuration;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.Resources;

/**
 * A supporting base class for implementing the standard pattern of extracting docstrings
 * from arbitrary files in a particular classpath location.
 */
public abstract class DocStringExtractor {

    private static final Logger logger = LoggerFactory.getLogger(DocStringExtractor.class);

    private static final Map<ClassLoader, Map<String, String>> cached = new ConcurrentHashMap<>();

    private final String path;

    protected DocStringExtractor(String defaultPath, String pathPropertyName) {
        this.path = computePath(defaultPath, pathPropertyName);
    }

    /**
     * Extract all docstrings from files at the configured path, delegating to
     * {@link #getDocStringsFromFiles(Map)} for actual processing.
     */
    public Map<String, String> getAllDocStrings(ClassLoader classLoader) {
        return cached.computeIfAbsent(classLoader, this::getAllDocStrings0);
    }

    private Map<String, String> getAllDocStrings0(ClassLoader classLoader) {
        final Configuration configuration = new ConfigurationBuilder()
                .filterInputsBy(new FilterBuilder().includePackage(path))
                .setUrls(ClasspathHelper.forPackage(path))
                .addClassLoader(classLoader)
                .addScanners(new ResourcesScanner());
        if (configuration.getUrls() == null || configuration.getUrls().isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, byte[]> files = new Reflections(configuration)
                .getResources(this::acceptFile).stream()
                .map(f -> {
                    try {
                        URL url = classLoader.getResource(f);
                        if (url == null) {
                            throw new IllegalStateException("not found: " + f);
                        }
                        return new SimpleImmutableEntry<>(f, Resources.toByteArray(url));
                    } catch (IOException e) {
                        throw new UncheckedIOException(e);
                    }
                })
                .collect(toImmutableMap(Entry::getKey, Entry::getValue));
        return getDocStringsFromFiles(files);
    }

    /**
     * Determine whether the file at {@code filename} should be processed by the {@link DocStringExtractor}.
     * This will usually look at the file extension, but the default implementation can be used to check all
     * files at a particular path.
     */
    protected boolean acceptFile(String filename) {
        return true;
    }

    /**
     * Extracts a {@link Map} of docstrings from the given {@link Map} of path to file contents. The result will
     * generally be used within a {@link DocServicePlugin} for finding docstrings of items.
     */
    protected abstract Map<String, String> getDocStringsFromFiles(Map<String, byte[]> files);

    private static String computePath(String defaultPath, String pathPropertyName) {
        String dir = System.getProperty(pathPropertyName, defaultPath);
        if (dir.startsWith("/") || dir.startsWith("\\")) {
            dir = dir.substring(1);
        }
        if (dir.endsWith("/") || dir.endsWith("\\")) {
            dir = dir.substring(0, dir.length() - 1);
        }
        logger.info("Using {}: {}", pathPropertyName, dir);
        return dir;
    }
}

@@ -1884,13 +1884,8 @@
     
-this.
 path
@@ -2602,19 +2602,19 @@
        .
-add
+set
 Scanners

Use only the necessary Scanners when scanning with Reflections (#640)