001/**
002 * Copyright (c) 2022-2023, Mybatis-Flex ([email protected]).
003 * <p>
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 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
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 */
036public class SpringBootVFS extends VFS {
037
038  private static Charset urlDecodingCharset;
039  private final ResourcePatternResolver resourceResolver;
040
041  static {
042    setUrlDecodingCharset(Charset.defaultCharset());
043  }
044
045  public SpringBootVFS() {
046    this.resourceResolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader());
047  }
048
049  @Override
050  public boolean isValid() {
051    return true;
052  }
053
054  @Override
055  protected List<String> list(URL url, String path) throws IOException {
056    String urlString = URLDecoder.decode(url.toString(), urlDecodingCharset.name());
057    String baseUrlString = urlString.endsWith("/") ? urlString : urlString.concat("/");
058    Resource[] resources = resourceResolver.getResources(baseUrlString + "**/*.class");
059    return Stream.of(resources).map(resource -> preserveSubpackageName(baseUrlString, resource, path))
060        .collect(Collectors.toList());
061  }
062
063  /**
064   * Set the charset for decoding an encoded URL string.
065   * <p>
066   * Default is system default charset.
067   * </p>
068   *
069   * @param charset
070   *          the charset for decoding an encoded URL string
071   *
072   * @since 2.3.0
073   */
074  public static void setUrlDecodingCharset(Charset charset) {
075    urlDecodingCharset = charset;
076  }
077
078  private static String preserveSubpackageName(final String baseUrlString, final Resource resource,
079      final String rootPath) {
080    try {
081      return rootPath + (rootPath.endsWith("/") ? "" : "/") + Normalizer
082          .normalize(URLDecoder.decode(resource.getURL().toString(), urlDecodingCharset.name()), Normalizer.Form.NFC)
083          .substring(baseUrlString.length());
084    } catch (IOException e) {
085      throw new UncheckedIOException(e);
086    }
087  }
088
089}