FileAndClasspathUtils.java

package cn.home1.oss.lib.common;

import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Sets.newLinkedHashSetWithExpectedSize;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.ArrayUtils.contains;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import lombok.SneakyThrows;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.io.Resource;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

public final class FileAndClasspathUtils {

  private FileAndClasspathUtils() {
  }

  /**
   * Create if not exists.
   *
   * @param fileFullPath path
   */
  @SneakyThrows
  public static void ensureFile(final String fileFullPath) {
    checkState(isNotBlank(fileFullPath), "file undefined. file: %s", fileFullPath);
    final File file = new File(fileFullPath);
    if (!file.exists()) {
      checkState(file.createNewFile(), "file create failed. file: %s", fileFullPath);
    }
    checkState(file.canRead() && file.canWrite(),
      "file can't read or write. file: %s", fileFullPath);
  }

  /**
   * Parse file lines.
   *
   * @param resource resource
   * @param mapper   line mapper
   * @param <T>      type
   * @return list
   */
  @SneakyThrows
  public static <T> List<T> parseFile(final Resource resource, final Function<String, T> mapper) {
    return parseFile(resource.getInputStream(), mapper);
  }

  /**
   * Parse file lines.
   *
   * @param inputStream stream
   * @param mapper      line mapper
   * @param <T>         type
   * @return list
   */
  @SneakyThrows
  public static <T> List<T> parseFile(final InputStream inputStream, final Function<String, T> mapper) {
    return IOUtils.readLines(inputStream, UTF_8).stream() //
      .filter(line -> isNotBlank(line) && line.trim().charAt(0) != '#') //
      .map(mapper) //
      .collect(toList());
  }

  public static class InterfaceFilter extends AbstractClassTestingTypeFilter {

    private final Class<?> type;
    private final boolean includeInterface;

    /**
     * filter find types of interface type.
     *
     * @param type             type
     * @param includeInterface include interface
     */
    public InterfaceFilter(final Class<?> type, final boolean includeInterface) {
      super();
      this.type = type;
      this.includeInterface = includeInterface;
    }

    @Override
    protected boolean match(final ClassMetadata metadata) {
      return contains(metadata.getInterfaceNames(), this.type.getName())
        && (this.includeInterface || !metadata.isInterface());
    }
  }

  public static class AssignableFilter extends AssignableTypeFilter {

    private final boolean includeInterface;
    private final boolean includeAbstract;

    public AssignableFilter(final Class<?> targetType, final boolean includeInterface, final boolean includeAbstract) {
      super(targetType);
      this.includeInterface = includeInterface;
      this.includeAbstract = includeAbstract;
    }

    @Override
    public boolean match(final MetadataReader reader, final MetadataReaderFactory readerFactory) throws IOException {
      final ClassMetadata metadata = reader.getClassMetadata();
      final boolean match = super.match(reader, readerFactory);
      return match && //
        (this.includeInterface || !metadata.isInterface()) && //
        (this.includeAbstract || !metadata.isAbstract());
    }
  }

  @SneakyThrows
  @SuppressWarnings("unchecked")
  private static <T> Class<T> classForName(final String className) {
    return (Class<T>) Class.forName(className);
  }

  /**
   * scan.
   *
   * @param basePackage   from where (package)
   * @param includeFilter filter
   * @param <T>           type
   * @return classes found
   */
  public static <T> Set<Class<T>> scan(final String basePackage, final TypeFilter includeFilter) {
    checkArgument(isNotBlank(basePackage));
    // log.info("domainEnums basePackage: {}", basePackage);

    final ClassPathScanningCandidateProvider provider = new ClassPathScanningCandidateProvider();
    provider.addIncludeFilter(includeFilter);
    final Set<BeanDefinition> beanDefinitions =
      provider.findCandidateComponents(basePackage.replaceAll("\\.", "/"));

    final Set<Class<T>> result = newLinkedHashSetWithExpectedSize(beanDefinitions.size());
    for (final BeanDefinition beanDefinition : beanDefinitions) {
      result.add(classForName(beanDefinition.getBeanClassName()));
    }
    return result;
  }

  /**
   * A ClassPathScanningCandidateComponentProvider.
   *
   * @author zhanghaolun
   */
  public static class ClassPathScanningCandidateProvider
    extends ClassPathScanningCandidateComponentProvider {

    /**
     * new a ClassPathScanningCandidateProvider.
     */
    public ClassPathScanningCandidateProvider() {
      super(false);
    }

    @Override
    protected boolean isCandidateComponent(final AnnotatedBeanDefinition beanDefinition) {
      return beanDefinition.getMetadata().isIndependent();
    }
  }
}