diff --git a/pom.xml b/pom.xml index 2041543..f2fb02d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.theoryinpractise googleformatter-maven-plugin - 1.7.6-SNAPSHOT + 1.8.0-SNAPSHOT maven-plugin googleformatter-maven-plugin Maven Mojo diff --git a/src/main/java/com/theoryinpractise/googleformatter/AbstractFormatter.java b/src/main/java/com/theoryinpractise/googleformatter/AbstractFormatter.java new file mode 100644 index 0000000..d39f1f4 --- /dev/null +++ b/src/main/java/com/theoryinpractise/googleformatter/AbstractFormatter.java @@ -0,0 +1,186 @@ +package com.theoryinpractise.googleformatter; + +import com.google.common.base.MoreObjects; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; +import com.google.common.io.CharStreams; +import com.google.googlejavaformat.java.Formatter; +import com.google.googlejavaformat.java.JavaFormatterOptions; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.manager.ScmManager; +import org.apache.maven.scm.repository.ScmRepository; +import org.codehaus.plexus.compiler.util.scan.InclusionScanException; +import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner; +import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; +import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner; +import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.*; +import java.util.stream.Collectors; + +import static com.theoryinpractise.googleformatter.Constants.DIRECTORY_MISSING; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +public abstract class AbstractFormatter extends AbstractMojo { + + public static final SuffixMapping SOURCE_MAPPING = new SuffixMapping(".java", new HashSet<>(Arrays.asList(".java", ".class"))); + @Component + ScmManager scmManager; + + @Parameter(required = true, readonly = true, property = "session") + protected MavenSession session; + + @Parameter(required = true, readonly = true, property = "project") + protected MavenProject project; + + @Parameter(required = true, readonly = true, property = "project.build.sourceDirectory") + protected File sourceDirectory; + + @Parameter(required = true, readonly = true, property = "project.build.testSourceDirectory") + protected File testSourceDirectory; + + @Parameter(required = true, readonly = true, property = "project.build.outputDirectory") + protected File outputDirectory; + + @Parameter(required = true, readonly = true, property = "project.build.testOutputDirectory") + protected File testOutputDirectory; + + @Parameter(defaultValue = "false") + protected boolean includeStale; + + @Parameter(defaultValue = "GOOGLE") + protected JavaFormatterOptions.Style style; + + @Parameter(defaultValue = "false", property = "formatter.skip") + protected boolean skip; + + @Parameter(defaultValue = "true", property = "formatter.main") + protected boolean formatMain; + + @Parameter(defaultValue = "true", property = "formatter.test") + protected boolean formatTest; + + @Parameter(defaultValue = "false", property = "formatter.modified") + protected boolean filterModified; + + @Parameter(defaultValue = "false", property = "formatter.fixImports") + protected boolean fixImports; + + @Parameter(defaultValue = "100", property = "formatter.maxLineLength") + protected int maxLineLength; + + abstract void handleFormattedSource(File file, String formattedSource) throws MojoExecutionException; + + @Override + public void execute() throws MojoExecutionException { + + if ("pom".equals(project.getPackaging())) { + getLog().info("Project packaging is POM, skipping..."); + return; + } + + if (skip) { + getLog().info("Skipping source reformatting due to plugin configuration."); + return; + } + + try { + Set sourceFiles = new HashSet<>(); + + if (formatMain) { + sourceFiles.addAll(findFilesToReformat(sourceDirectory, outputDirectory)); + } + if (formatTest) { + sourceFiles.addAll(findFilesToReformat(testSourceDirectory, testOutputDirectory)); + } + + Set sourceFilesToProcess = filterModified ? filterUnchangedFiles(sourceFiles) : sourceFiles; + + JavaFormatterOptions options = getJavaFormatterOptions(); + + for (File file : sourceFilesToProcess) { + String source = CharStreams.toString(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); + + com.google.googlejavaformat.java.Formatter formatter = new Formatter(options); + String formattedSource = fixImports ? formatter.formatSourceAndFixImports(source) : formatter.formatSource(source); + + HashCode sourceHash = Hashing.sha256().hashString(source, StandardCharsets.UTF_8); + HashCode formattedHash = Hashing.sha256().hashString(formattedSource, StandardCharsets.UTF_8); + + if (!formattedHash.equals(sourceHash)) { + handleFormattedSource(file, formattedSource); + } + } + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); + } + + } + + private JavaFormatterOptions getJavaFormatterOptions() { + JavaFormatterOptions options = spy(JavaFormatterOptions.builder().style(style).build()); + doReturn(maxLineLength).when(options).maxLineLength(); + return options; + } + + private Set filterUnchangedFiles(Set originalFiles) throws MojoExecutionException { + MavenProject topLevelProject = session.getTopLevelProject(); + try { + if (topLevelProject.getScm().getConnection() == null && topLevelProject.getScm().getDeveloperConnection() == null) { + throw new MojoExecutionException( + "You must supply at least one of scm.connection or scm.developerConnection in your POM file if you " + + "specify the filterModified or filter.modified option."); + } + String connectionUrl = MoreObjects.firstNonNull(topLevelProject.getScm().getConnection(), topLevelProject.getScm().getDeveloperConnection()); + ScmRepository repository = scmManager.makeScmRepository(connectionUrl); + ScmFileSet scmFileSet = new ScmFileSet(topLevelProject.getBasedir()); + String basePath = topLevelProject.getBasedir().getAbsoluteFile().getPath(); + List changedFiles = + scmManager.status(repository, scmFileSet).getChangedFiles().stream() + .map(f -> new File(basePath, f.getPath()).toString()) + .collect(Collectors.toList()); + + return originalFiles.stream().filter(f -> changedFiles.contains(f.getPath())).collect(Collectors.toSet()); + + } catch (ScmException e) { + throw new MojoExecutionException(e.getMessage(), e); + } + } + + private Set findFilesToReformat(File sourceDirectory, File outputDirectory) throws MojoExecutionException { + if (sourceDirectory.exists()) { + try { + SourceInclusionScanner scanner = getSourceInclusionScanner(includeStale); + scanner.addSourceMapping(SOURCE_MAPPING); + Set sourceFiles = scanner.getIncludedSources(sourceDirectory, outputDirectory); + getLog().info(String.format(Constants.FOUND_UNCOMPILED, sourceFiles.size(), sourceDirectory.getPath())); + return sourceFiles; + } catch (InclusionScanException e) { + throw new MojoExecutionException(String.format(Constants.ERROR_SCANNING_PATH, sourceDirectory.getPath()), e); + } + } else { + getLog().info(String.format(DIRECTORY_MISSING, sourceDirectory.getPath())); + return Collections.emptySet(); + } + } + + protected SourceInclusionScanner getSourceInclusionScanner(boolean includeStale) { + return includeStale ? new SimpleSourceInclusionScanner(Collections.singleton("**/*"), Collections.emptySet()) : new StaleSourceScanner(1024); + } +} diff --git a/src/main/java/com/theoryinpractise/googleformatter/GoogleFormatterMojo.java b/src/main/java/com/theoryinpractise/googleformatter/GoogleFormatterMojo.java index baa1b33..65a7139 100644 --- a/src/main/java/com/theoryinpractise/googleformatter/GoogleFormatterMojo.java +++ b/src/main/java/com/theoryinpractise/googleformatter/GoogleFormatterMojo.java @@ -1,192 +1,27 @@ package com.theoryinpractise.googleformatter; -import com.google.common.base.MoreObjects; -import com.google.common.hash.HashCode; -import com.google.common.hash.Hashing; -import com.google.common.io.CharStreams; -import com.google.googlejavaformat.java.Formatter; -import com.google.googlejavaformat.java.JavaFormatterOptions; -import org.apache.maven.execution.MavenSession; -import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; -import org.apache.maven.scm.ScmException; -import org.apache.maven.scm.ScmFileSet; -import org.apache.maven.scm.manager.ScmManager; -import org.apache.maven.scm.repository.ScmRepository; -import org.codehaus.plexus.compiler.util.scan.InclusionScanException; -import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner; -import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; -import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner; -import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import static com.google.googlejavaformat.java.JavaFormatterOptions.Style; -import static com.theoryinpractise.googleformatter.Constants.DIRECTORY_MISSING; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; /** Reformat all source files using the Google Code Formatter */ @Mojo(name = "format", defaultPhase = LifecyclePhase.PROCESS_SOURCES) -public class GoogleFormatterMojo extends AbstractMojo { - - public static final SuffixMapping SOURCE_MAPPING = new SuffixMapping(".java", new HashSet<>(Arrays.asList(".java", ".class"))); - @Component ScmManager scmManager; - - @Parameter(required = true, readonly = true, property = "session") - protected MavenSession session; - - @Parameter(required = true, readonly = true, property = "project") - protected MavenProject project; - - @Parameter(required = true, readonly = true, property = "project.build.sourceDirectory") - protected File sourceDirectory; - - @Parameter(required = true, readonly = true, property = "project.build.testSourceDirectory") - protected File testSourceDirectory; - - @Parameter(required = true, readonly = true, property = "project.build.outputDirectory") - protected File outputDirectory; - - @Parameter(required = true, readonly = true, property = "project.build.testOutputDirectory") - protected File testOutputDirectory; - - @Parameter(defaultValue = "false") - protected boolean includeStale; - - @Parameter(defaultValue = "GOOGLE") - protected Style style; - - @Parameter(defaultValue = "false", property = "formatter.skip") - protected boolean skip; - - @Parameter(defaultValue = "true", property = "formatter.main") - protected boolean formatMain; - - @Parameter(defaultValue = "true", property = "formatter.test") - protected boolean formatTest; - - @Parameter(defaultValue = "false", property = "formatter.modified") - protected boolean filterModified; - - @Parameter(defaultValue = "false", property = "formatter.fixImports") - protected boolean fixImports; - - @Parameter(defaultValue = "100", property = "formatter.maxLineLength") - protected int maxLineLength; - - @Override - public void execute() throws MojoExecutionException { - - if ("pom".equals(project.getPackaging())) { - getLog().info("Project packaging is POM, skipping..."); - return; - } - - if (skip) { - getLog().info("Skipping source reformatting due to plugin configuration."); - return; - } - - try { - Set sourceFiles = new HashSet<>(); - - if (formatMain) { - sourceFiles.addAll(findFilesToReformat(sourceDirectory, outputDirectory)); - } - if (formatTest) { - sourceFiles.addAll(findFilesToReformat(testSourceDirectory, testOutputDirectory)); - } - - Set sourceFilesToProcess = filterModified ? filterUnchangedFiles(sourceFiles) : sourceFiles; - - JavaFormatterOptions options = getJavaFormatterOptions(); - - for (File file : sourceFilesToProcess) { - String source = CharStreams.toString(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); - - Formatter formatter = new Formatter(options); - String formattedSource = fixImports ? formatter.formatSourceAndFixImports(source) : formatter.formatSource(source); - - HashCode sourceHash = Hashing.sha256().hashString(source, StandardCharsets.UTF_8); - HashCode formattedHash = Hashing.sha256().hashString(formattedSource, StandardCharsets.UTF_8); - - if (!formattedHash.equals(sourceHash)) { - // overwrite existing file - Files.write(file.toPath(), formattedSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); - - getLog().info(String.format("Reformatted file %s", file.getPath())); +public class GoogleFormatterMojo extends AbstractFormatter { + + @Override + public void handleFormattedSource(File file, String formattedSource) throws MojoExecutionException { + // overwrite existing file + try { + Files.write(file.toPath(), formattedSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + throw new MojoExecutionException(e.getMessage(), e); } - } - } catch (Exception e) { - throw new MojoExecutionException(e.getMessage(), e); + getLog().info(String.format("Reformatted file %s", file.getPath())); } - } - - private JavaFormatterOptions getJavaFormatterOptions() { - JavaFormatterOptions options = spy(JavaFormatterOptions.builder().style(style).build()); - doReturn(maxLineLength).when(options).maxLineLength(); - return options; - } - - private Set filterUnchangedFiles(Set originalFiles) throws MojoExecutionException { - MavenProject topLevelProject = session.getTopLevelProject(); - try { - if (topLevelProject.getScm().getConnection() == null && topLevelProject.getScm().getDeveloperConnection() == null) { - throw new MojoExecutionException( - "You must supply at least one of scm.connection or scm.developerConnection in your POM file if you " + - "specify the filterModified or filter.modified option."); - } - String connectionUrl = MoreObjects.firstNonNull(topLevelProject.getScm().getConnection(), topLevelProject.getScm().getDeveloperConnection()); - ScmRepository repository = scmManager.makeScmRepository(connectionUrl); - ScmFileSet scmFileSet = new ScmFileSet(topLevelProject.getBasedir()); - String basePath = topLevelProject.getBasedir().getAbsoluteFile().getPath(); - List changedFiles = - scmManager.status(repository, scmFileSet).getChangedFiles().stream() - .map(f -> new File(basePath, f.getPath()).toString()) - .collect(Collectors.toList()); - - return originalFiles.stream().filter(f -> changedFiles.contains(f.getPath())).collect(Collectors.toSet()); - - } catch (ScmException e) { - throw new MojoExecutionException(e.getMessage(), e); - } - } - - private Set findFilesToReformat(File sourceDirectory, File outputDirectory) throws MojoExecutionException { - if (sourceDirectory.exists()) { - try { - SourceInclusionScanner scanner = getSourceInclusionScanner(includeStale); - scanner.addSourceMapping(SOURCE_MAPPING); - Set sourceFiles = scanner.getIncludedSources(sourceDirectory, outputDirectory); - getLog().info(String.format(Constants.FOUND_UNCOMPILED, sourceFiles.size(), sourceDirectory.getPath())); - return sourceFiles; - } catch (InclusionScanException e) { - throw new MojoExecutionException(String.format(Constants.ERROR_SCANNING_PATH, sourceDirectory.getPath()), e); - } - } else { - getLog().info(String.format(DIRECTORY_MISSING, sourceDirectory.getPath())); - return Collections.emptySet(); - } - } - - protected SourceInclusionScanner getSourceInclusionScanner(boolean includeStale) { - return includeStale ? new SimpleSourceInclusionScanner(Collections.singleton("**/*"), Collections.emptySet()) : new StaleSourceScanner(1024); - } } diff --git a/src/main/java/com/theoryinpractise/googleformatter/ValidateCorrectFormattingMojo.java b/src/main/java/com/theoryinpractise/googleformatter/ValidateCorrectFormattingMojo.java new file mode 100644 index 0000000..0cee264 --- /dev/null +++ b/src/main/java/com/theoryinpractise/googleformatter/ValidateCorrectFormattingMojo.java @@ -0,0 +1,17 @@ +package com.theoryinpractise.googleformatter; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; + +import java.io.File; + +@Mojo(name = "check", defaultPhase = LifecyclePhase.VALIDATE) +public class ValidateCorrectFormattingMojo extends AbstractFormatter { + @Override + void handleFormattedSource(File file, String formattedSource) throws MojoExecutionException { + throw new MojoExecutionException("Project needs formatting, please run mvn process-resources"); + } +}