/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.jvm.java.plugin.adapter;
import com.facebook.buck.util.liteinfersupport.Nullable;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.Trees;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import javax.lang.model.element.TypeElement;
/**
* Extends {@link JavacTask} with functionality that is useful for Buck:
*
* <ul>
* <li>Exposes the enter method from JavacTaskImpl
* <li>Pre-javac-8 support for addTaskListener/removeTaskListener
* </ul>
*/
public class BuckJavacTask extends JavacTaskWrapper {
private final Map<BuckJavacPlugin, String[]> pluginsAndArgs = new LinkedHashMap<>();
private final MultiplexingTaskListener taskListeners = new MultiplexingTaskListener();
private final PostEnterTaskListener postEnterTaskListener;
private final List<Consumer<Set<TypeElement>>> postEnterCallbacks = new ArrayList<>();
private boolean pluginsInstalled = false;
@Nullable private TaskListener singleTaskListener;
public BuckJavacTask(JavacTask inner) {
super(inner);
if (inner instanceof BuckJavacTask) {
throw new IllegalArgumentException();
}
inner.setTaskListener(
new TaskListener() {
@Override
public void started(TaskEvent e) {
BuckJavacTask.this.started(e);
}
@Override
public void finished(TaskEvent e) {
BuckJavacTask.this.finished(e);
}
});
postEnterTaskListener = new PostEnterTaskListener(this, this::onPostEnter);
}
public Iterable<? extends TypeElement> enter() throws IOException {
try {
@SuppressWarnings("unchecked")
Iterable<? extends TypeElement> result =
(Iterable<? extends TypeElement>) inner.getClass().getMethod("enter").invoke(inner);
return result;
} catch (IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
throw new RuntimeException(e);
}
}
/**
* Sets a {@link TaskListener}. Like {@link JavacTask}'s implementation of this method, the
* listener does not replace listeners added with {@link #addTaskListener(TaskListener)}. Instead,
* it replaces only the listener provided in the previous call to this method, if any.
*
* <p>Presumably this behavior was to enable {@link com.sun.source.util.Plugin}s to work properly
* with build systems that were written when only a single {@link TaskListener} was supported at a
* time.
*/
@Override
public void setTaskListener(@Nullable TaskListener taskListener) {
if (singleTaskListener != null) {
removeTaskListener(singleTaskListener);
}
singleTaskListener = taskListener;
if (singleTaskListener != null) {
addTaskListener(singleTaskListener);
}
}
@Override
public void addTaskListener(TaskListener taskListener) {
taskListeners.addListener(taskListener);
}
@Override
public void removeTaskListener(TaskListener taskListener) {
taskListeners.removeListener(taskListener);
}
public void addPlugin(BuckJavacPlugin plugin, String... args) {
pluginsAndArgs.put(plugin, args);
}
public void addPostEnterCallback(Consumer<Set<TypeElement>> callback) {
postEnterCallbacks.add(callback);
}
public Trees getTrees() {
return Trees.instance(inner);
}
protected void started(TaskEvent e) {
// Initialize plugins just before sending the first event to registered listeners. We do it
// this way (rather than initializing plugins just before starting to run the task) because
// most plugins will call methods like getElements, which in javac 7 are not safe to call
// before the task is actually running.
installPlugins();
taskListeners.started(e);
postEnterTaskListener.started(e);
}
protected void finished(TaskEvent e) {
taskListeners.finished(e);
postEnterTaskListener.finished(e);
}
protected void onPostEnter(Set<TypeElement> topLevelTypes) {
postEnterCallbacks.forEach(callback -> callback.accept(topLevelTypes));
}
private void installPlugins() {
if (pluginsInstalled) {
return;
}
for (Map.Entry<BuckJavacPlugin, String[]> pluginAndArgs : pluginsAndArgs.entrySet()) {
pluginAndArgs.getKey().init(BuckJavacTask.this, pluginAndArgs.getValue());
}
pluginsAndArgs.clear();
pluginsInstalled = true;
}
}