-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
omar salem
committed
Jul 30, 2023
1 parent
2034133
commit b21d2a2
Showing
4 changed files
with
215 additions
and
177 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
src/main/java/com/theoryinpractise/googleformatter/AbstractFormatter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<File> sourceFiles = new HashSet<>(); | ||
|
||
if (formatMain) { | ||
sourceFiles.addAll(findFilesToReformat(sourceDirectory, outputDirectory)); | ||
} | ||
if (formatTest) { | ||
sourceFiles.addAll(findFilesToReformat(testSourceDirectory, testOutputDirectory)); | ||
} | ||
|
||
Set<File> 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<File> filterUnchangedFiles(Set<File> 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<String> 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<File> findFilesToReformat(File sourceDirectory, File outputDirectory) throws MojoExecutionException { | ||
if (sourceDirectory.exists()) { | ||
try { | ||
SourceInclusionScanner scanner = getSourceInclusionScanner(includeStale); | ||
scanner.addSourceMapping(SOURCE_MAPPING); | ||
Set<File> 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); | ||
} | ||
} |
187 changes: 11 additions & 176 deletions
187
src/main/java/com/theoryinpractise/googleformatter/GoogleFormatterMojo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<File> sourceFiles = new HashSet<>(); | ||
|
||
if (formatMain) { | ||
sourceFiles.addAll(findFilesToReformat(sourceDirectory, outputDirectory)); | ||
} | ||
if (formatTest) { | ||
sourceFiles.addAll(findFilesToReformat(testSourceDirectory, testOutputDirectory)); | ||
} | ||
|
||
Set<File> 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<File> filterUnchangedFiles(Set<File> 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<String> 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<File> findFilesToReformat(File sourceDirectory, File outputDirectory) throws MojoExecutionException { | ||
if (sourceDirectory.exists()) { | ||
try { | ||
SourceInclusionScanner scanner = getSourceInclusionScanner(includeStale); | ||
scanner.addSourceMapping(SOURCE_MAPPING); | ||
Set<File> 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); | ||
} | ||
} |
Oops, something went wrong.