001/*
002 *    Copyright 2015-2023 the original author or authors.
003 *
004 *    Licensed under the Apache License, Version 2.0 (the "License");
005 *    you may not use this file except in compliance with the License.
006 *    You may obtain a copy of the License at
007 *
008 *       https://www.apache.org/licenses/LICENSE-2.0
009 *
010 *    Unless required by applicable law or agreed to in writing, software
011 *    distributed under the License is distributed on an "AS IS" BASIS,
012 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *    See the License for the specific language governing permissions and
014 *    limitations under the License.
015 */
016package com.mybatisflex.spring.boot;
017
018import org.apache.ibatis.io.VFS;
019import org.springframework.core.io.Resource;
020import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
021import org.springframework.core.io.support.ResourcePatternResolver;
022
023import java.io.IOException;
024import java.io.UncheckedIOException;
025import java.net.URL;
026import java.net.URLDecoder;
027import java.nio.charset.Charset;
028import java.text.Normalizer;
029import java.util.List;
030import java.util.stream.Collectors;
031import java.util.stream.Stream;
032
033/**
034 * MyBatis 的 VFS 支持。
035 *  参考:https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/main/java/org/mybatis/spring/boot/autoconfigure/SpringBootVFS.java
036 * @author Hans Westerbeek
037 * @author Eddú Meléndez
038 * @author Kazuki Shimizu
039 * @author Michael
040 */
041public class SpringBootVFS extends VFS {
042
043    private static Charset urlDecodingCharset;
044
045    static {
046        setUrlDecodingCharset(Charset.defaultCharset());
047    }
048
049    private final ResourcePatternResolver resourceResolver;
050
051    public SpringBootVFS() {
052        this.resourceResolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader());
053    }
054
055    /**
056     * Set the charset for decoding an encoded URL string.
057     * <p>
058     * Default is system default charset.
059     * </p>
060     *
061     * @param charset the charset for decoding an encoded URL string
062     * @since 2.3.0
063     */
064    public static void setUrlDecodingCharset(Charset charset) {
065        urlDecodingCharset = charset;
066    }
067
068    private static String preserveSubpackageName(final String baseUrlString, final Resource resource,
069                                                 final String rootPath) {
070        try {
071            return rootPath + (rootPath.endsWith("/") ? "" : "/") + Normalizer
072                .normalize(URLDecoder.decode(resource.getURL().toString(), urlDecodingCharset.name()), Normalizer.Form.NFC)
073                .substring(baseUrlString.length());
074        } catch (IOException e) {
075            throw new UncheckedIOException(e);
076        }
077    }
078
079    @Override
080    public boolean isValid() {
081        return true;
082    }
083
084    @Override
085    protected List<String> list(URL url, String path) throws IOException {
086        String urlString = URLDecoder.decode(url.toString(), urlDecodingCharset.name());
087        String baseUrlString = urlString.endsWith("/") ? urlString : urlString.concat("/");
088        Resource[] resources = resourceResolver.getResources(baseUrlString + "**/*.class");
089        return Stream.of(resources).map(resource -> preserveSubpackageName(baseUrlString, resource, path))
090            .collect(Collectors.toList());
091    }
092
093}