001/**
002 * Copyright (C) 2006-2025 Talend Inc. - www.talend.com
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 * http://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 org.talend.sdk.component.tools;
017
018import static java.util.Optional.ofNullable;
019import static java.util.stream.Collectors.joining;
020
021import java.io.File;
022import java.io.FileOutputStream;
023import java.io.IOException;
024import java.io.OutputStream;
025import java.lang.reflect.Method;
026import java.net.MalformedURLException;
027import java.util.Collection;
028import java.util.Date;
029import java.util.List;
030import java.util.Properties;
031import java.util.stream.Stream;
032
033import org.apache.xbean.finder.AnnotationFinder;
034import org.apache.xbean.finder.archive.Archive;
035import org.apache.xbean.finder.archive.ClasspathArchive;
036import org.apache.xbean.finder.archive.CompositeArchive;
037import org.apache.xbean.finder.filter.ExcludeIncludeFilter;
038import org.apache.xbean.finder.filter.Filter;
039import org.apache.xbean.finder.filter.Filters;
040import org.apache.xbean.finder.filter.IncludeExcludeFilter;
041import org.talend.sdk.component.api.input.Emitter;
042import org.talend.sdk.component.api.input.PartitionMapper;
043import org.talend.sdk.component.api.internationalization.Internationalized;
044import org.talend.sdk.component.api.processor.Processor;
045import org.talend.sdk.component.api.service.Service;
046import org.talend.sdk.component.api.service.http.Request;
047import org.talend.sdk.component.api.standalone.DriverRunner;
048
049import lombok.RequiredArgsConstructor;
050
051@RequiredArgsConstructor
052public class ScanTask implements Runnable {
053
054    private final Collection<File> scannedFiles;
055
056    private final List<String> excludes;
057
058    private final List<String> includes;
059
060    private final String filterStrategy;
061
062    private final File output;
063
064    @Override
065    public void run() {
066        output.getParentFile().mkdirs();
067        try (final OutputStream stream = new FileOutputStream(output)) {
068            final Properties properties = new Properties();
069            properties.setProperty("classes.list", scanList().collect(joining(",")));
070            properties.store(stream, "generated by " + getClass() + " at " + new Date());
071        } catch (final IOException e) {
072            throw new IllegalStateException(e.getMessage(), e);
073        }
074    }
075
076    private Stream<String> scanList() {
077        final AnnotationFinder finder = newFinder();
078        final Filter filter = newFilter();
079        return Stream
080                .concat(Stream
081                        .of(PartitionMapper.class, Processor.class, Emitter.class, DriverRunner.class, Service.class,
082                                Internationalized.class)
083                        .flatMap(it -> finder.findAnnotatedClasses(it).stream()),
084                        Stream
085                                .of(Request.class)
086                                .flatMap(it -> finder.findAnnotatedMethods(it).stream())
087                                .map(Method::getDeclaringClass))
088                .distinct()
089                .map(Class::getName)
090                .sorted()
091                .filter(filter::accept);
092    }
093
094    private Filter newFilter() {
095        final Filter accept = ofNullable(includes)
096                .filter(it -> it.size() > 0)
097                .map(i -> i.toArray(new String[0]))
098                .map(Filters::patterns)
099                .orElseGet(() -> name -> true);
100        final Filter reject = ofNullable(excludes)
101                .filter(it -> it.size() > 0)
102                .map(i -> i.toArray(new String[0]))
103                .map(Filters::patterns)
104                .orElseGet(() -> name -> false);
105        if ("include-exclude".equals(filterStrategy)) {
106            return new IncludeExcludeFilter(accept, reject);
107        }
108        return new ExcludeIncludeFilter(accept, reject);
109    }
110
111    private AnnotationFinder newFinder() {
112        return new AnnotationFinder(new CompositeArchive(scannedFiles.stream().map(c -> {
113            try {
114                return ClasspathArchive.archive(Thread.currentThread().getContextClassLoader(), c.toURI().toURL());
115            } catch (final MalformedURLException e) {
116                throw new IllegalArgumentException(e);
117            }
118        }).toArray(Archive[]::new)));
119    }
120}