package org.bindgen.processor;
import static org.bindgen.processor.CurrentEnv.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.bindgen.processor.generators.BindKeywordGenerator;
import org.bindgen.processor.generators.BindingClassGenerator;
import org.bindgen.processor.util.BoundClass;
import org.bindgen.processor.util.ClassName;
/** Keeps a recursive list of TypeElements to generate bindings for.
*
* As each binding is generated, its properties may need their
* own binding classes generated, so they get put in this queue.
*/
public class GenerationQueue {
// Both Eclipse and javac will use the same processor instance for each compilation run (e.g. across all of the rounds), so this should be cumulative
private final Set<String> written = new HashSet<String>();
// Any TypeElements waiting to have bindings generated
private final List<TypeElement> queue = new ArrayList<TypeElement>();
// Whether to log debug messages to System.err
private final boolean logEnabled;
// For javac as it does not like the existing bindings
private final boolean skipExistingBindingCheck;
// Skip the bindgen.BindKeyword class
private final boolean skipBindKeyword;
public GenerationQueue() {
this.logEnabled = getConfig().logEnabled();
this.skipExistingBindingCheck = getConfig().skipExistingBindingCheck();
this.skipBindKeyword = getConfig().skipBindKeyword();
}
/** Enqueue <code>element</code> even if it was written during a previous compilation run. */
public void enqueueForcefully(TypeElement element) {
// Even when done forcefully, we can only touch elements once/round
if (this.hasAlreadyBeenWrittenByThisCompilationRun(element)) {
return;
}
this.enqueue(element);
}
/** Enqueue <code>element</code> only if we haven't seen it either this compilation run or during a previous compilation run. */
public void enqueueIfNew(TypeElement element) {
if (this.hasAlreadyBeenWrittenByThisCompilationRun(element) || this.hasAlreadyBeenWrittenByAPreviousCompilationRun(element)) {
return;
}
this.enqueue(element);
}
/** Generates bindings for elements in the queue unless it is empty. */
public void processQueue() {
while (this.queue.size() != 0) {
new BindingClassGenerator(this, this.queue.remove(0)).generate();
}
}
/** Creates the <code>bindgen.BindKeyword</code> file unless disabled. */
public void updateBindKeywordClass() {
if (this.skipBindKeyword) {
return;
}
new BindKeywordGenerator(this).generate(this.written);
}
/** Outputs <code>message</code> to System.out, mostly useful for Debug As Eclipse/javac debugging. */
public void log(String message) {
if (this.logEnabled) {
System.out.println(message + " in " + this);
}
}
private void enqueue(TypeElement element) {
this.queue.add(element);
this.written.add(element.toString());
}
/**
* Whether this compilation run has already seen <code>element</code>.
*
* We recursively walk into bindings, so first check our in-memory set of what we've
* seen so far this compilation run. Eclipse creates a new processor instance every time
* the user hits save, so this only has things from this compilation run.
*/
private boolean hasAlreadyBeenWrittenByThisCompilationRun(TypeElement element) {
return this.written.contains(element.toString());
}
/**
* Whether a previous compilation run has already see <code>element</code>
*
* If we haven't seen it this compilation run, see if we've already output awhile ago (e.g.
* last time they hit save), in which case we can skip it. If we really needed to do this
* element again, Eclipse would have deleted our last output and this check wouldn't find
* anything. But this does not work for javac, so we don't do it if told not to. Would be
* nice to auto-detect javac.
*/
private boolean hasAlreadyBeenWrittenByAPreviousCompilationRun(TypeElement element) {
if (this.skipExistingBindingCheck) {
return false;
}
try {
ClassName bindingClassName = new BoundClass(element).getBindingClassName();
FileObject fo = getFiler().getResource(
StandardLocation.SOURCE_OUTPUT,
bindingClassName.getPackageName(),
bindingClassName.getSimpleName() + ".java");
return fo.getLastModified() > 0; // exists already
} catch (IOException io) {
getMessager().printMessage(Kind.ERROR, io.getMessage(), element);
return false;
}
}
}