001package ball.util.ant.taskdefs;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import ball.annotation.processing.ClassFileProcessor;
022import java.io.File;
023import java.net.URLClassLoader;
024import java.util.ArrayList;
025import java.util.HashSet;
026import java.util.List;
027import java.util.stream.Stream;
028import javax.tools.StandardJavaFileManager;
029import javax.tools.ToolProvider;
030import lombok.Getter;
031import lombok.NoArgsConstructor;
032import lombok.Setter;
033import lombok.ToString;
034import lombok.experimental.Accessors;
035import org.apache.tools.ant.BuildException;
036import org.apache.tools.ant.Task;
037import org.apache.tools.ant.types.Path;
038import org.apache.tools.ant.util.ClasspathUtils;
039
040import static java.lang.reflect.Modifier.isAbstract;
041import static java.util.Arrays.asList;
042import static java.util.stream.Collectors.toList;
043import static javax.tools.StandardLocation.CLASS_OUTPUT;
044import static javax.tools.StandardLocation.CLASS_PATH;
045import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
046import static javax.tools.StandardLocation.SOURCE_PATH;
047
048/**
049 * {@link.uri http://ant.apache.org/ Ant} {@link Task} to bootstrap
050 * {@link javax.annotation.processing.Processor}s.  Creates and invokes
051 * {@link ClassFileProcessor}s found on the class path.
052 *
053 * {@ant.task}
054 *
055 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
056 */
057@AntTask("process-class-files")
058@NoArgsConstructor @ToString
059public class ProcessClassFilesTask extends Task implements AnnotatedAntTask, ClasspathDelegateAntTask {
060    @Getter @Setter @Accessors(chain = true, fluent = true)
061    private ClasspathUtils.Delegate delegate = null;
062    @Getter @Setter
063    private File basedir = null;
064    @Getter
065    private Path srcPath = null;
066    @Getter @Setter
067    private File destdir = null;
068
069    private StandardJavaFileManager fm = null;
070
071    public void setSrcdir(Path srcdir) {
072        if (srcPath == null) {
073            srcPath = srcdir;
074        } else {
075            srcPath.append(srcdir);
076        }
077    }
078
079    public Path createSrc() {
080        if (srcPath == null) {
081            srcPath = new Path(getProject());
082        }
083
084        return srcPath.createPath();
085    }
086
087    @Override
088    public void init() throws BuildException {
089        super.init();
090        ClasspathDelegateAntTask.super.init();
091
092        try {
093            fm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
094        } catch (BuildException exception) {
095            throw exception;
096        } catch (RuntimeException exception) {
097            throw exception;
098        } catch (Throwable throwable) {
099            throwable.printStackTrace();
100            throw new BuildException(throwable);
101        }
102    }
103
104    @Override
105    public void execute() throws BuildException {
106        super.execute();
107        AnnotatedAntTask.super.execute();
108
109        try {
110            if (getBasedir() == null) {
111                setBasedir(getProject().resolveFile("."));
112            }
113
114            if (getDestdir() == null) {
115                setDestdir(getBasedir());
116            }
117
118            List<File> srcPaths =
119                Stream.of(createSrc().list())
120                .map(File::new)
121                .collect(toList());
122            List<File> classPaths =
123                Stream.of(delegate.getClasspath().list())
124                .map(File::new)
125                .collect(toList());
126
127            fm.setLocation(SOURCE_PATH, srcPaths);
128            fm.setLocation(PLATFORM_CLASS_PATH, classPaths);
129            fm.setLocation(CLASS_PATH, classPaths);
130            fm.setLocation(CLASS_OUTPUT, asList(getDestdir()));
131
132            ClassLoader loader = fm.getClassLoader(CLASS_PATH);
133
134            if (loader instanceof URLClassLoader) {
135                loader =
136                    URLClassLoader.newInstance(((URLClassLoader) loader).getURLs(),
137                                               getClass().getClassLoader());
138            }
139
140            HashSet<Class<?>> types = new HashSet<>();
141            List<Class<? extends ClassFileProcessor>> processors = new ArrayList<>();
142            HashSet<String> names = new HashSet<>();
143
144            for (String name : ClassFileProcessor.list(fm)) {
145                try {
146                    Class<?> type = Class.forName(name, true, loader);
147
148                    types.add(type);
149
150                    if (! isAbstract(type.getModifiers())) {
151                        if (ClassFileProcessor.class.isAssignableFrom(type)) {
152                            processors.add(type.asSubclass(ClassFileProcessor.class));
153                        }
154                    }
155                } catch (Throwable throwable) {
156                    names.add(name);
157                }
158            }
159
160            if (names.isEmpty()) {
161                for (Class<? extends ClassFileProcessor> processor : processors) {
162                    processor
163                        .getDeclaredConstructor().newInstance()
164                        .process(types, fm);
165                }
166            } else {
167                throw new BuildException("Failed to load " + names);
168            }
169        } catch (BuildException exception) {
170            throw exception;
171        } catch (RuntimeException exception) {
172            throw exception;
173        } catch (Throwable throwable) {
174            throwable.printStackTrace();
175            throw new BuildException(throwable);
176        }
177    }
178}