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