package io.takari.maven.plugins.compile; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import org.apache.maven.execution.scope.MojoExecutionScoped; import org.apache.maven.project.MavenProject; import io.takari.incrementalbuild.MessageSeverity; import io.takari.incrementalbuild.Output; import io.takari.incrementalbuild.Resource; import io.takari.incrementalbuild.ResourceMetadata; import io.takari.incrementalbuild.spi.AbstractBuildContext; import io.takari.incrementalbuild.spi.BuildContextEnvironment; import io.takari.incrementalbuild.spi.DefaultBuildContextState; import io.takari.incrementalbuild.spi.DefaultOutput; import io.takari.incrementalbuild.spi.DefaultResource; import io.takari.incrementalbuild.spi.DefaultResourceMetadata; // TODO replace all Default* implementation types with corresponding API interfaces @Named @MojoExecutionScoped public class CompilerBuildContext extends AbstractBuildContext { private final File pom; @Inject public CompilerBuildContext(BuildContextEnvironment configuration, MavenProject project) { super(configuration); pom = project.getFile(); } // // overall build context management // @Override public void markSkipExecution() { super.markSkipExecution(); } /** * Marks current build as up-to-date. All input and output resources and their corresponding metadata are carried-over to the next build as-is. All messages produced during previous build are * replayed. */ public void markUptodateExecution() { // TODO enforce context hasn't been modified yet. // copy output ResourceHolders from previous build. this has the same effect as input registration. // the outputs will be considered up-to-date and all their metadata will be carried-over as-is for (File outputFile : oldState.getOutputs()) { markUptodateOutput(outputFile); } } /** * Adds messages associated with mojo execution in project pom.xml. This is useful to capture compiler failures, i.e. exception in the compiler itself. These messages are carried-over during * no-changed rebuild and trigger build failures as expected. These messages are discarded during full or incremental build and must be recreated as needed. */ public void addPomMessage(String message, MessageSeverity severity, Throwable cause) { // TODO execution line/column super.addMessage(pom, 0, 0, message, severity, cause); } @Override public boolean isEscalated() { return super.isEscalated(); } /** * Returns attribute value set at context-level, i.e. not associated with any particular resource, during previous build. */ public <V extends Serializable> V getAttribute(String key, boolean previous, Class<V> clazz) { return super.getResourceAttribute(oldState, pom, key, clazz); } /** * Sets context-level, i.e. not associated with any particular resource, key/value attribute. Context-level attributes are not automatically carried-over from one build to the next and must be * explicitly set during each build. */ public <V extends Serializable> Serializable setAttribute(String key, V value) { return super.setResourceAttribute(pom, key, value); } // // sources tracking // public Collection<DefaultResourceMetadata<File>> registerSources(File basedir, Collection<String> includes, Collection<String> excludes) throws IOException { return super.registerInputs(basedir, includes, excludes); } /** * Returns sources removed since previous build. Does not return generated sources. */ public Collection<ResourceMetadata<File>> getRemovedSources() { Collection<ResourceMetadata<File>> sources = new ArrayList<>(); for (Object resource : oldState.getResources().keySet()) { if (isSource(resource) && !oldState.isOutput(resource) && !isRegisteredResource(resource)) { sources.add(newResourceMetadata(oldState, (File) resource)); } } return sources; } /** * Returns original or generated source processed during this build. Throws {@link IllegalStateException} if no such source. */ public Resource<File> getProcessedSource(File sourceFile) { if (!isProcessedResource(sourceFile) || !isSource(sourceFile)) { // JDT may decide to compile more sources than it was asked to in some cases // TODO investigate when this happens and decide what to do about it throw new IllegalArgumentException(); } return newResource(sourceFile); } /** * Returns sources registered during this build. */ public Collection<ResourceMetadata<File>> getRegisteredSources() { List<ResourceMetadata<File>> sources = new ArrayList<>(); for (Object resource : state.getResources().keySet()) { if (isSource(resource)) { DefaultBuildContextState state = isProcessedResource(resource) ? this.state : this.oldState; sources.add(newResourceMetadata(state, (File) resource)); } } return sources; } private boolean isSource(Object resource) { return resource instanceof File && ((File) resource).getName().endsWith(".java"); // TODO find proper constant } public <V extends Serializable> V getAttribute(File source, String key, Class<V> clazz) { return getResourceAttribute(getState(source), source, key, clazz); } private DefaultBuildContextState getState(File source) { return isProcessedResource(source) ? this.state : this.oldState; } public <V extends Serializable> Serializable setAttribute(File source, String key, V value) { return setResourceAttribute(source, key, value); } // // output tracking // /** * Deletes all outputs registered with the build context */ public Collection<ResourceMetadata<File>> deleteOutputs() throws IOException { List<ResourceMetadata<File>> deleted = new ArrayList<>(); for (File outputFile : oldState.getOutputs()) { deleteOutput(outputFile); deleted.add(newResourceMetadata(oldState, outputFile)); } return deleted; } /** * Returns outputs directly or indirectly derived from the source. */ public Collection<ResourceMetadata<File>> getAssociatedOutputs(ResourceMetadata<File> source) { return addAssociatedOutputs(new HashMap<File, ResourceMetadata<File>>(), source).values(); } private Map<File, ResourceMetadata<File>> addAssociatedOutputs(Map<File, ResourceMetadata<File>> outputs, ResourceMetadata<File> resource) { for (ResourceMetadata<File> output : super.getAssociatedOutputs(getState(resource.getResource()), resource.getResource())) { if (!outputs.containsKey(output.getResource())) { outputs.put(output.getResource(), output); addAssociatedOutputs(outputs, output); } } return outputs; } public Collection<ResourceMetadata<File>> getDissociatedOutputs() { List<ResourceMetadata<File>> outputs = new ArrayList<>(); for (File outputFile : oldState.getOutputs()) { Collection<Object> outputInputs = oldState.getOutputInputs(outputFile); if (outputInputs == null || outputInputs.isEmpty()) { outputs.add(new DefaultResourceMetadata<File>(this, state, outputFile) {}); } } return outputs; } @Override public DefaultOutput processOutput(File outputFile) { return super.processOutput(outputFile); } @Override public void deleteOutput(File outputFile) throws IOException { super.deleteOutput(outputFile); } public boolean isProcessedOutput(File outputFile) { return state.isOutput(outputFile) && isProcessedResource(outputFile); } public Output<File> associatedOutput(Resource<File> input, File outputFile) { return input.associateOutput(outputFile); } // // context commit // @Override protected void assertAssociation(DefaultResource<?> resource, DefaultOutput output) { // allow any input/output association } @Override protected void finalizeContext() { for (Object resource : oldState.getResources().keySet()) { if (isProcessedResource(resource) || isDeletedResource(resource)) { // known deleted or processed resource, nothing to carry over continue; } if (!oldState.isOutput(resource) && !state.isResource(resource)) { // deleted or excluded source, nothing to carry over continue; } state.putResource(resource, oldState.getResource(resource)); if (oldState.isOutput(resource)) { state.addOutput((File) resource); } state.setResourceMessages(resource, oldState.getResourceMessages(resource)); state.setResourceAttributes(resource, oldState.getResourceAttributes(resource)); state.setResourceOutputs(resource, oldState.getResourceOutputs(resource)); // XXX inputs and outputs, which ones do we need here? } } }