├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .mvn ├── jvm.config └── wrapper │ ├── .gitignore │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── core ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── cosium │ │ │ └── code │ │ │ └── format │ │ │ ├── AbstractFormatMojo.java │ │ │ ├── AbstractMavenGitCodeFormatMojo.java │ │ │ ├── AbstractModuleMavenGitCodeFormatMojo.java │ │ │ ├── FormatCodeMojo.java │ │ │ ├── InstallHooksMojo.java │ │ │ ├── MavenGitCodeFormatException.java │ │ │ ├── OnPreCommitMojo.java │ │ │ ├── TemporaryFile.java │ │ │ ├── ValidateCodeFormat.java │ │ │ ├── executable │ │ │ ├── CommandRunException.java │ │ │ ├── CommandRunner.java │ │ │ ├── DefaulExecutable.java │ │ │ ├── DefaultCommandRunner.java │ │ │ ├── Executable.java │ │ │ └── ExecutableManager.java │ │ │ ├── formatter │ │ │ ├── CodeFormatterConfigurationFactory.java │ │ │ ├── CodeFormatters.java │ │ │ └── SimpleCodeFormatterConfiguration.java │ │ │ ├── git │ │ │ ├── AutoCRLFObjectLoader.java │ │ │ ├── AutoCRLFObjectReader.java │ │ │ ├── AutoCRLFObjectStream.java │ │ │ ├── AutoCRLFRepository.java │ │ │ ├── GitIndexEntry.java │ │ │ ├── GitStagedFiles.java │ │ │ └── Index.java │ │ │ └── maven │ │ │ └── MavenEnvironment.java │ └── resources │ │ └── com │ │ └── cosium │ │ └── code │ │ └── format │ │ └── git-code-format.pre-commit.sh │ └── test │ ├── java │ └── com │ │ └── cosium │ │ └── code │ │ └── format │ │ ├── AbstractMavenModuleTest.java │ │ ├── AbstractTest.java │ │ ├── NonRootModuleTest.java │ │ ├── SingleModuleTest.java │ │ ├── TestingLog.java │ │ └── maven │ │ └── MavenEnvironmentTest.java │ ├── projects │ ├── non-root-module │ │ └── module │ │ │ ├── .gitignore │ │ │ ├── .mvn │ │ │ ├── extensions.xml │ │ │ └── jvm.config │ │ │ ├── pom.xml │ │ │ ├── src │ │ │ └── main │ │ │ │ └── java │ │ │ │ └── BadFormat.java │ │ │ └── target │ │ │ └── generated-sources │ │ │ └── GeneratedBadFormat.java │ └── single-module │ │ ├── .gitignore │ │ ├── .mvn │ │ ├── extensions.xml │ │ └── jvm.config │ │ ├── pom.xml │ │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── BadFormat.java │ │ └── target │ │ └── generated-sources │ │ └── GeneratedBadFormat.java │ └── resources │ ├── expected │ ├── non-root-module │ │ └── module │ │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── BadFormat.java │ └── single-module │ │ └── src │ │ └── main │ │ └── java │ │ └── BadFormat.java │ └── logback-test.xml ├── google-java-format ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── cosium │ │ └── code │ │ └── format_gjf │ │ ├── GoogleJavaFormatException.java │ │ ├── GoogleJavaFormatter.java │ │ ├── GoogleJavaFormatterFactory.java │ │ └── GoogleJavaFormatterOptions.java │ └── resources │ └── META-INF │ └── services │ └── com.cosium.code.format_spi.CodeFormatterFactory ├── mvnw ├── mvnw.cmd ├── pom.xml ├── release.sh └── spi ├── .gitignore ├── pom.xml └── src └── main └── java └── com └── cosium └── code └── format_spi ├── CodeFormatter.java ├── CodeFormatterConfiguration.java ├── CodeFormatterFactory.java ├── FileExtension.java └── LineRanges.java /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **What steps will reproduce the problem?** 13 | Steps to reproduce the behavior: 14 | 1. 15 | 2. 16 | 3. 17 | 18 | **What is the expected output?** 19 | 20 | **What happens instead?** 21 | 22 | **Environment:** 23 | - OS: [e.g. Windows 10] 24 | - Git version: [e.g. 2.17.1] 25 | - git-code-format-maven-plugin version: [e.g. 2.5] 26 | - Maven version: [e.g. 3.5.4] 27 | 28 | **Link to a git repository that can be cloned to reproduce the problem:** 29 | This is critical. The absence of such repository will greatly reduce the chances of seeing the issue resolved. 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 17 18 | uses: actions/setup-java@v2 19 | with: 20 | java-version: '17' 21 | distribution: 'temurin' 22 | - name: Build with Maven 23 | run: ./mvnw --batch-mode clean verify 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | ### JetBrains template 26 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 27 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 28 | 29 | # User-specific stuff: 30 | .idea 31 | 32 | # CMake 33 | cmake-build-debug/ 34 | 35 | ## File-based project format: 36 | *.iws 37 | 38 | ## Plugin-specific files: 39 | 40 | # IntelliJ 41 | out/ 42 | 43 | # Eclipse 44 | .classpath 45 | .project 46 | .settings/ 47 | 48 | # JIRA plugin 49 | atlassian-ide-plugin.xml 50 | 51 | # Crashlytics plugin (for Android Studio and IntelliJ) 52 | com_crashlytics_export_strings.xml 53 | crashlytics.properties 54 | crashlytics-build.properties 55 | fabric.properties 56 | ### Maven template 57 | /target 58 | pom.xml.tag 59 | pom.xml.releaseBackup 60 | pom.xml.versionsBackup 61 | pom.xml.next 62 | release.properties 63 | dependency-reduced-pom.xml 64 | buildNumber.properties 65 | .mvn/timing.properties 66 | 67 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 68 | !/.mvn/wrapper/maven-wrapper.jar 69 | *.iml -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central Latest](https://img.shields.io/maven-central/v/com.cosium.code/git-code-format-maven-plugin.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.cosium.code%22%20AND%20a%3A%22git-code-format-maven-plugin%22) 2 | [![Build Status](https://github.com/Cosium/git-code-format-maven-plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/Cosium/git-code-format-maven-plugin/actions/workflows/ci.yml) 3 | 4 | # Git Code Format Maven Plugin 5 | 6 | A maven plugin that automatically deploys code formatters as `pre-commit` git hook. 7 | On commit, the hook will automatically format staged files. 8 | 9 | # Breaking changes between 4.x and 5.x 10 | 11 | * If the plugin runs without any formatter dependency, it will fail. This is done to prevent silent misconfiguration from happening. 12 | 13 | # Breaking changes between 3.x and 4.x 14 | 15 | * `Google Java Format` is not enabled by default anymore. `com.cosium.code:google-java-format` must be added as a dependency to the plugin to keep using it. 16 | * `Google Java Format` options declaration structure has changed. You will need to migrate any eventual existing declaration to the new structure described by [the google-java-format-options chapter](#google-java-format-options) . 17 | 18 | # Breaking changes between 2.x and 3.x 19 | 20 | * [#64](https://github.com/Cosium/git-code-format-maven-plugin/issues/64) `google-java-format 1.8` [dropped support for java 8](https://github.com/google/google-java-format/releases/tag/google-java-format-1.8). 21 | The minimum supported runtime version for the plugin is JDK 11. i.e. Maven must run on JDK 11+ while the target project can still be built and run using JDK 8. 22 | 23 | # Breaking changes between 1.x and 2.x 24 | 25 | * [#37](https://github.com/Cosium/git-code-format-maven-plugin/issues/37) To prevent conflicts with other plugins all keys are now 26 | prefixed with `gcf`. e.g. `-DglobPattern=**/*` becomes `-Dgcf.globPattern=**/*` 27 | * [#38](https://github.com/Cosium/git-code-format-maven-plugin/issues/38) To avoid infringement to Apache Maven Trademark, 28 | the plugin was renamed to `git-code-format-maven-plugin`. Its new coordinates are 29 | `com.cosium.code:git-code-format-maven-plugin`. 30 | 31 | `1.x` documentation can be found [here](https://github.com/Cosium/git-code-format-maven-plugin/blob/1.39/README.md) 32 | 33 | # Automatic code format and validation activation 34 | 35 | Add this to your maven project **root** pom.xml : 36 | 37 | ```xml 38 | 39 | 40 | 41 | com.cosium.code 42 | git-code-format-maven-plugin 43 | ${git-code-format-maven-plugin.version} 44 | 45 | 46 | 47 | install-formatter-hook 48 | 49 | install-hooks 50 | 51 | 52 | 54 | 55 | validate-code-format 56 | 57 | validate-code-format 58 | 59 | 60 | 61 | 62 | 63 | 64 | com.cosium.code 65 | google-java-format 66 | ${git-code-format-maven-plugin.version} 67 | 68 | 69 | 70 | 71 | 72 | ``` 73 | 74 | # Manual code formatting 75 | 76 | ```console 77 | mvn git-code-format:format-code -Dgcf.globPattern=**/* 78 | ``` 79 | 80 | # Manual code format validation 81 | 82 | ```console 83 | mvn git-code-format:validate-code-format -Dgcf.globPattern=**/* 84 | ``` 85 | 86 | # Google Java Format 87 | 88 | ## Google Java Format options 89 | 90 | The plugin allows you to tweak Google Java Format options : 91 | 92 | ```xml 93 | 94 | 95 | 96 | com.cosium.code 97 | git-code-format-maven-plugin 98 | ${git-code-format-maven-plugin.version} 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | false 109 | 110 | true 111 | 112 | false 113 | 114 | false 115 | 116 | false 117 | 118 | 119 | 120 | 121 | 122 | ``` 123 | 124 | ## JDK 16+ peculiarities 125 | 126 | Since google-java-format uses JDK internal apis, if you need to run the plugin with JDK 16+, you must pass some additional arguments to the JVM. 127 | Those are described at https://github.com/google/google-java-format/releases/tag/v1.10.0. 128 | 129 | Thanks to https://maven.apache.org/configure.html#mvn-jvm-config-file, you should be able to pass them to `.mvn/jvm.config` as follow: 130 | 131 | ``` 132 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 133 | ``` 134 | 135 | # Custom code formatter 136 | 137 | Thanks to its code formatter SPI, this plugin can execute any code formatter. 138 | 139 | ## How to 140 | 141 | Note that you can take inspiration from the `google-java-format` module of this project. 142 | 143 | 1. Implement `com.cosium.code.format_spi.CodeFormatterFactory`. This interface is provided by `com.cosium.code:git-code-format-maven-plugin-spi`. 144 | 2. Add your `com.cosium.code.format_spi.CodeFormatterFactory` implementation canonical name in `META-INF/services/com.cosium.code.format_spi.CodeFormatterFactory`. 145 | 3. Pack this in a jar that you declare as a dependency in this plugin declaration. 146 | 147 | ## Example of usage 148 | 149 | Suppose: 150 | - the chosen `configurationId` (declared by `com.cosium.code.format_spi.CodeFormatterFactory#configurationId()`) is `aqme` 151 | - the formatter dependency is `com.aqme.formatter:formatter:1.0` 152 | 153 | A plugin declaration making use of this custom code formatter would look like this: 154 | 155 | ```xml 156 | 157 | 158 | 159 | com.cosium.code 160 | git-code-format-maven-plugin 161 | ${git-code-format-maven-plugin.version} 162 | 163 | 164 | 165 | 166 | 167 | com.aqme.formatter 168 | formatter 169 | 1.0 170 | 171 | 172 | 173 | 174 | false 175 | foo 176 | 177 | 178 | 179 | 180 | 181 | ``` 182 | 183 | # Frequently asked questions 184 | 185 | ## If I have a multi-module project, do I need to install anything in the sub-projects? 186 | You only need to put the plugin in your *root* project pom.xml. By default all submodules will be handled. 187 | 188 | ## Do I need to run mvn initialize or is that a stage that happens automatically when I run mvn compile or mvn test? 189 | `initialize` is the first phase of the Maven lifecycle. Any goal that you perform (e.g. `compile` or `test`) will automatically trigger `initialize` and thus trigger the git pre-commit hook installation. 190 | 191 | ## I'm not noticing anything happening. 192 | If after setting up the plugin in your pom, you just executed a maven goal, the only expected output is a pre-commit hook installed in your `.git/hooks` directory. To trigger the automatic formatting, you have to perform a commit of a modified file. 193 | You can also manually [format](#manual-code-formatting) or [validate](#manual-code-format-validation) any file. 194 | 195 | ## I'd like to skip code formatting in a child project 196 | I inherit an enterprise parent pom, which I cannot modify, with formatting plugin specified, and I need to turn off formatting for my group's project. 197 | Either use add a ```true``` configuration in the inheriting project or set the ```gcf.skip``` property to true. 198 | 199 | # How the hook works 200 | 201 | On the `initialize` maven phase, `git-code-format:install-hooks` installs a git `pre-commit` hook that looks like this : 202 | ```bash 203 | #!/bin/bash 204 | "./.git/hooks/${project.artifactId}.git-code-format.pre-commit.sh" 205 | ``` 206 | and `.git/hooks/${project.artifactId}.git-code-format.pre-commit.sh` has the following content: 207 | ```bash 208 | #!/bin/bash 209 | set -e 210 | "${env.M2_HOME}/bin/mvn" -f "${project.basedir}/pom.xml" git-code-format:on-pre-commit 211 | ``` 212 | 213 | On `pre-commit` git phase, the hook triggers the `git-code-format:on-pre-commit` which formats the code of the modified files. 214 | 215 | # Advanced pre-commit pipeline hook 216 | If you wish to modify the output of the pre-commit hook, you can set the `preCommitHookPipeline` configuration. 217 | 218 | To completely ignore the hook output, you could use the following configuration: 219 | ```xml 220 | 221 | >/dev/null 222 | 223 | ``` 224 | 225 | To display error lines from the maven output and fail build with any errors, you could use the following configuration: 226 | ```xml 227 | 228 | | grep -F '[ERROR]' || exit 0 && exit 1 229 | 230 | ``` 231 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | com.cosium.code 6 | git-code-format-maven-plugin-parent 7 | 5.4-SNAPSHOT 8 | 9 | 10 | git-code-format-maven-plugin 11 | maven-plugin 12 | 13 | Git Code Format Maven Plugin 14 | 15 | 16 | 17 | ${project.groupId} 18 | git-code-format-maven-plugin-spi 19 | ${project.version} 20 | 21 | 22 | 23 | commons-io 24 | commons-io 25 | 26 | 27 | org.apache.commons 28 | commons-lang3 29 | 30 | 31 | org.apache.commons 32 | commons-exec 33 | 34 | 35 | com.google.googlejavaformat 36 | google-java-format 37 | 38 | 39 | org.eclipse.jgit 40 | org.eclipse.jgit 41 | 42 | 43 | org.apache.maven 44 | maven-core 45 | 46 | 47 | org.apache.maven 48 | maven-plugin-api 49 | 50 | 51 | org.apache.maven.plugin-tools 52 | maven-plugin-annotations 53 | provided 54 | 55 | 56 | org.codehaus.plexus 57 | plexus-utils 58 | 59 | 60 | junit 61 | junit 62 | test 63 | 64 | 65 | org.assertj 66 | assertj-core 67 | test 68 | 69 | 70 | io.takari.maven.plugins 71 | takari-plugin-testing 72 | test 73 | 74 | 75 | io.takari.maven.plugins 76 | takari-plugin-integration-testing 77 | pom 78 | test 79 | 80 | 81 | ch.qos.logback 82 | logback-classic 83 | test 84 | 85 | 86 | commons-codec 87 | commons-codec 88 | test 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-surefire-plugin 97 | 3.5.3 98 | 99 | false 100 | @{argLine} --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports 101 | jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports 102 | jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports 103 | jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports 104 | jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 105 | 106 | 107 | 108 | 109 | ${project.groupId} 110 | google-java-format 111 | ${project.version} 112 | 113 | 114 | 115 | 116 | org.apache.maven.plugins 117 | maven-plugin-plugin 118 | 3.15.1 119 | 120 | git-code-format 121 | true 122 | 123 | 124 | 125 | mojo-descriptor 126 | 127 | descriptor 128 | 129 | 130 | 131 | help-goal 132 | 133 | helpmojo 134 | 135 | 136 | 137 | 138 | 139 | io.takari.maven.plugins 140 | takari-lifecycle-plugin 141 | 2.3.1 142 | true 143 | 144 | 145 | testProperties 146 | process-test-resources 147 | 148 | testProperties 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/AbstractFormatMojo.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import com.cosium.code.format.formatter.CodeFormatters; 4 | import java.io.IOException; 5 | import java.nio.file.FileSystems; 6 | import java.nio.file.FileVisitResult; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.PathMatcher; 10 | import java.nio.file.SimpleFileVisitor; 11 | import java.nio.file.attribute.BasicFileAttributes; 12 | import org.apache.maven.plugin.MojoExecutionException; 13 | import org.apache.maven.plugin.MojoFailureException; 14 | import org.apache.maven.plugins.annotations.Parameter; 15 | 16 | /** 17 | * @author Réda Housni Alaoui 18 | */ 19 | public abstract class AbstractFormatMojo extends AbstractModuleMavenGitCodeFormatMojo { 20 | 21 | @Parameter(property = "gcf.globPattern", required = true, defaultValue = "**/*") 22 | private String globPattern; 23 | 24 | @Override 25 | protected final void doExecute() throws MojoExecutionException, MojoFailureException { 26 | String pattern = "glob:" + globPattern; 27 | getLog().debug("Using pattern '" + pattern + "'"); 28 | PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + globPattern); 29 | 30 | CodeFormatters codeFormatters = collectCodeFormatters(); 31 | for (Path sourceDir : sourceDirs()) { 32 | walk(codeFormatters, sourceDir, pathMatcher); 33 | } 34 | } 35 | 36 | private void walk(CodeFormatters codeFormatters, Path directoryToWalk, PathMatcher pathMatcher) 37 | throws MojoExecutionException, MojoFailureException { 38 | Path targetDir = targetDir(); 39 | try { 40 | Files.walkFileTree( 41 | directoryToWalk, 42 | new SimpleFileVisitor() { 43 | 44 | @Override 45 | public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { 46 | if (path.startsWith(targetDir)) { 47 | return FileVisitResult.CONTINUE; 48 | } 49 | if (!pathMatcher.matches(path)) { 50 | return FileVisitResult.CONTINUE; 51 | } 52 | try { 53 | process(codeFormatters, path); 54 | } catch (MojoExecutionException | MojoFailureException e) { 55 | throw new MavenGitCodeFormatException(e); 56 | } 57 | return FileVisitResult.CONTINUE; 58 | } 59 | 60 | @Override 61 | public FileVisitResult visitFileFailed(Path file, IOException exc) { 62 | return FileVisitResult.CONTINUE; 63 | } 64 | }); 65 | 66 | } catch (IOException e) { 67 | throw new MojoExecutionException(e.getMessage(), e); 68 | } catch (Exception e) { 69 | Throwable cause = e.getCause(); 70 | if (cause instanceof MojoExecutionException) { 71 | throw (MojoExecutionException) cause; 72 | } 73 | if (cause instanceof MojoFailureException) { 74 | throw (MojoFailureException) cause; 75 | } 76 | throw e; 77 | } 78 | } 79 | 80 | protected abstract void process(CodeFormatters codeFormatters, Path path) throws MojoExecutionException, MojoFailureException; 81 | } 82 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/AbstractMavenGitCodeFormatMojo.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import com.cosium.code.format.formatter.CodeFormatterConfigurationFactory; 4 | import com.cosium.code.format.formatter.CodeFormatters; 5 | import com.cosium.code.format_spi.CodeFormatter; 6 | import com.cosium.code.format_spi.CodeFormatterFactory; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.util.ArrayList; 13 | import java.util.Collection; 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Optional; 18 | import java.util.ServiceLoader; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.Stream; 21 | import org.apache.commons.lang3.StringUtils; 22 | import org.apache.maven.plugin.AbstractMojo; 23 | import org.apache.maven.plugins.annotations.Parameter; 24 | import org.apache.maven.project.MavenProject; 25 | import org.eclipse.jgit.lib.Repository; 26 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder; 27 | 28 | /** 29 | * @author Réda Housni Alaoui 30 | */ 31 | public abstract class AbstractMavenGitCodeFormatMojo extends AbstractMojo { 32 | 33 | protected static final String HOOKS_DIR = "hooks"; 34 | 35 | @Parameter(readonly = true, defaultValue = "${project}") 36 | private MavenProject currentProject; 37 | 38 | @Parameter(defaultValue = "${project.build.sourceEncoding}") 39 | private String sourceEncoding; 40 | 41 | @Parameter private Map formatterOptions; 42 | 43 | protected final Repository gitRepository() { 44 | Repository gitRepository; 45 | try { 46 | FileRepositoryBuilder repositoryBuilder = 47 | new FileRepositoryBuilder().findGitDir(currentProject.getBasedir()); 48 | String gitIndexFileEnvVariable = System.getenv("GIT_INDEX_FILE"); 49 | if (StringUtils.isNotBlank(gitIndexFileEnvVariable)) { 50 | repositoryBuilder = repositoryBuilder.setIndexFile(new File(gitIndexFileEnvVariable)); 51 | } 52 | gitRepository = repositoryBuilder.build(); 53 | } catch (IOException e) { 54 | throw new MavenGitCodeFormatException( 55 | "Could not find the git repository. Run 'git init' if you did not.", e); 56 | } 57 | return gitRepository; 58 | } 59 | 60 | protected final Path pomFile() { 61 | return currentProject.getFile().toPath(); 62 | } 63 | 64 | protected final List sourceDirs() { 65 | return Stream.of( 66 | currentProject.getCompileSourceRoots(), currentProject.getTestCompileSourceRoots()) 67 | .flatMap(Collection::stream) 68 | .map(Paths::get) 69 | .collect(Collectors.toList()); 70 | } 71 | 72 | protected final Path targetDir() { 73 | return Paths.get(currentProject.getBuild().getDirectory()); 74 | } 75 | 76 | protected final String artifactId() { 77 | return currentProject.getArtifactId(); 78 | } 79 | 80 | protected final CodeFormatters collectCodeFormatters() { 81 | return new CodeFormatters(createCodeFormatters()); 82 | } 83 | 84 | private List createCodeFormatters() { 85 | 86 | List formatterFactories = new ArrayList<>(); 87 | ServiceLoader.load(CodeFormatterFactory.class).forEach(formatterFactories::add); 88 | if (formatterFactories.isEmpty()) { 89 | throw new IllegalStateException( 90 | "No " 91 | + CodeFormatter.class 92 | + " instance found. You probably forgot to declare a code formatter as a plugin's dependency. You can use https://github.com/Cosium/git-code-format-maven-plugin#automatic-code-format-and-validation-activation as an example."); 93 | } 94 | 95 | CodeFormatterConfigurationFactory formatterConfigurationFactory = 96 | new CodeFormatterConfigurationFactory( 97 | Optional.ofNullable(formatterOptions).orElseGet(Collections::emptyMap)); 98 | 99 | return formatterFactories.stream() 100 | .map( 101 | codeFormatterFactory -> 102 | codeFormatterFactory.build( 103 | formatterConfigurationFactory.build(codeFormatterFactory.configurationId()), 104 | sourceEncoding)) 105 | .collect(Collectors.toList()); 106 | } 107 | 108 | protected final boolean isExecutionRoot() { 109 | return currentProject.isExecutionRoot(); 110 | } 111 | 112 | /** 113 | * Get or creates the git hooks directory 114 | * 115 | * @return The git hooks directory 116 | */ 117 | protected final Path getOrCreateHooksDirectory() { 118 | Path hooksDirectory = gitRepository().getDirectory().toPath().resolve(HOOKS_DIR); 119 | if (!Files.exists(hooksDirectory)) { 120 | getLog().debug("Creating directory " + hooksDirectory); 121 | try { 122 | Files.createDirectories(hooksDirectory); 123 | } catch (IOException e) { 124 | throw new MavenGitCodeFormatException(e); 125 | } 126 | } else { 127 | getLog().debug(hooksDirectory + " already exists"); 128 | } 129 | return hooksDirectory; 130 | } 131 | 132 | protected final Path gitBaseDir() { 133 | return gitRepository().getDirectory().getParentFile().toPath(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/AbstractModuleMavenGitCodeFormatMojo.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import org.apache.maven.plugin.MojoExecutionException; 7 | import org.apache.maven.plugin.MojoFailureException; 8 | import org.apache.maven.plugins.annotations.Parameter; 9 | 10 | /** 11 | * @author Réda Housni Alaoui 12 | */ 13 | public abstract class AbstractModuleMavenGitCodeFormatMojo extends AbstractMavenGitCodeFormatMojo { 14 | 15 | /** Skip execution of this goal */ 16 | @Parameter(property = "gcf.skip", defaultValue = "false") 17 | private boolean skip; 18 | 19 | @Parameter(property = "gcf.includedModules") 20 | private List includedModules; 21 | 22 | @Parameter(property = "gcf.excludedModules") 23 | private List excludedModules; 24 | 25 | /** 26 | * @return True if the goal is enabled for the current module 27 | */ 28 | private boolean isEnabled() { 29 | List modulesToExclude = 30 | Optional.ofNullable(this.excludedModules).orElse(Collections.emptyList()); 31 | if (modulesToExclude.contains(artifactId())) { 32 | getLog().info(artifactId() + " is part of the excluded modules. Goal disabled."); 33 | return false; 34 | } 35 | 36 | List modulesToInclude = 37 | Optional.ofNullable(this.includedModules).orElse(Collections.emptyList()); 38 | if (!modulesToInclude.isEmpty() && !modulesToInclude.contains(artifactId())) { 39 | getLog().info(artifactId() + " is not part of defined included modules. Goal disabled."); 40 | return false; 41 | } 42 | 43 | if ((!modulesToInclude.isEmpty() || !modulesToExclude.isEmpty()) && isExecutionRoot()) { 44 | getLog() 45 | .info( 46 | "Explicit included or excluded modules defined and the current module is the execution root. Goal disabled."); 47 | return false; 48 | } 49 | 50 | getLog().debug("Goal enabled"); 51 | return true; 52 | } 53 | 54 | @Override 55 | public final void execute() throws MojoExecutionException, MojoFailureException { 56 | if (skip) { 57 | getLog().info("skipped"); 58 | return; 59 | } 60 | if (!isEnabled()) { 61 | return; 62 | } 63 | doExecute(); 64 | } 65 | 66 | protected abstract void doExecute() throws MojoExecutionException, MojoFailureException; 67 | } 68 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/FormatCodeMojo.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import com.cosium.code.format.formatter.CodeFormatters; 4 | import com.cosium.code.format_spi.CodeFormatter; 5 | import com.cosium.code.format_spi.FileExtension; 6 | import com.cosium.code.format_spi.LineRanges; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import org.apache.commons.io.IOUtils; 13 | import org.apache.maven.plugins.annotations.LifecyclePhase; 14 | import org.apache.maven.plugins.annotations.Mojo; 15 | 16 | /** 17 | * @author Réda Housni Alaoui 18 | */ 19 | @Mojo(name = "format-code", defaultPhase = LifecyclePhase.NONE, threadSafe = true) 20 | public class FormatCodeMojo extends AbstractFormatMojo { 21 | 22 | @Override 23 | protected void process(CodeFormatters codeFormatters, Path path) { 24 | codeFormatters 25 | .forFileExtension(FileExtension.parse(path)) 26 | .forEach(formatter -> format(path, formatter)); 27 | } 28 | 29 | private void format(Path path, CodeFormatter formatter) { 30 | Path relativePath = gitBaseDir().relativize(path); 31 | getLog().debug("Formatting '" + relativePath + "'"); 32 | 33 | try (TemporaryFile temporaryFormattedFile = 34 | TemporaryFile.create(getLog(), path + ".formatted")) { 35 | try (InputStream content = Files.newInputStream(path); 36 | OutputStream formattedContent = temporaryFormattedFile.newOutputStream()) { 37 | formatter.format(content, LineRanges.all(), formattedContent); 38 | } 39 | 40 | try (InputStream formattedContent = temporaryFormattedFile.newInputStream(); 41 | OutputStream unformattedContent = Files.newOutputStream(path)) { 42 | IOUtils.copy(formattedContent, unformattedContent); 43 | } 44 | } catch (IOException | RuntimeException e) { 45 | throw new MavenGitCodeFormatException( 46 | String.format("Failed to format '%s': %s", relativePath, e.getMessage()), e); 47 | } 48 | 49 | getLog().debug("Formatted '" + gitBaseDir().relativize(path) + "'"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/InstallHooksMojo.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import static java.util.Optional.ofNullable; 4 | 5 | import com.cosium.code.format.executable.Executable; 6 | import com.cosium.code.format.executable.ExecutableManager; 7 | import com.cosium.code.format.maven.MavenEnvironment; 8 | import java.io.IOException; 9 | import java.nio.charset.StandardCharsets; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.Stream; 18 | import org.apache.maven.plugin.MojoExecutionException; 19 | import org.apache.maven.plugin.logging.Log; 20 | import org.apache.maven.plugins.annotations.LifecyclePhase; 21 | import org.apache.maven.plugins.annotations.Mojo; 22 | import org.apache.maven.plugins.annotations.Parameter; 23 | 24 | /** 25 | * Installs git hooks on each initialization. Hooks are always overriden in case of changes in: 26 | * 27 | *
    28 | *
  • maven installation 29 | *
  • plugin structure 30 | *
31 | */ 32 | @Mojo(name = "install-hooks", defaultPhase = LifecyclePhase.INITIALIZE, threadSafe = true) 33 | public class InstallHooksMojo extends AbstractMavenGitCodeFormatMojo { 34 | 35 | /** Name of 1.x plugin pre-commit hook */ 36 | private static final String LEGACY_BASE_PLUGIN_PRE_COMMIT_HOOK = 37 | "maven-git-code-format.pre-commit.sh"; 38 | 39 | private static final String BASE_PLUGIN_PRE_COMMIT_HOOK = "git-code-format.pre-commit.sh"; 40 | private static final String PRE_COMMIT_HOOK_BASE_SCRIPT = "pre-commit"; 41 | 42 | private final ExecutableManager executableManager = new ExecutableManager(this::getLog); 43 | private final MavenEnvironment mavenEnvironment = new MavenEnvironment(this::getLog); 44 | 45 | /** Skip execution of this goal */ 46 | @Parameter(property = "gcf.skip", defaultValue = "false") 47 | private boolean skip; 48 | 49 | /** Skip execution of this specific goal */ 50 | @Parameter(property = "gcf.skipInstallHooks", defaultValue = "false") 51 | private boolean skipInstallHooks; 52 | 53 | /** 54 | * True to truncate hooks base scripts before each install.
55 | * Do not use this option if any other system or human manipulate the hooks 56 | */ 57 | @Parameter(property = "gcf.truncateHooksBaseScripts", defaultValue = "false") 58 | private boolean truncateHooksBaseScripts; 59 | 60 | /** The list of properties to propagate to the hooks */ 61 | @Parameter(property = "gcf.propertiesToPropagate") 62 | private String[] propertiesToPropagate; 63 | 64 | /** The list of properties to add to the hooks */ 65 | @Parameter(property = "gcf.propertiesToAdd") 66 | private String[] propertiesToAdd; 67 | 68 | @Parameter(property = "gcf.debug", defaultValue = "false") 69 | private boolean debug; 70 | 71 | /** 72 | * Add pipeline to process the results of the pre-commit hook. Exit non-zero to prevent the commit 73 | */ 74 | @Parameter(property = "gcf.preCommitHookPipeline", defaultValue = "") 75 | private String preCommitHookPipeline; 76 | 77 | /** 78 | * If present, this value will be passed to '-T, --threads Thread count, for instance 2.0C where C 79 | * is core multiplied' 80 | */ 81 | @Parameter(property = "gcf.hookMavenThreadCount") 82 | private String hookMavenThreadCount; 83 | 84 | public void execute() throws MojoExecutionException { 85 | if (!isExecutionRoot()) { 86 | getLog().debug("Not in execution root. Do not execute."); 87 | return; 88 | } 89 | if (skip || skipInstallHooks) { 90 | Log log = getLog(); 91 | if (log.isInfoEnabled()) { 92 | log.info("skipped"); 93 | } 94 | return; 95 | } 96 | 97 | try { 98 | getLog().info("Installing git hooks"); 99 | doExecute(); 100 | getLog().info("Installed git hooks"); 101 | } catch (Exception e) { 102 | throw new MojoExecutionException(e.getMessage(), e); 103 | } 104 | } 105 | 106 | private void doExecute() throws IOException { 107 | Path hooksDirectory = prepareHooksDirectory(); 108 | 109 | writePluginHooks(hooksDirectory); 110 | 111 | configureHookBaseScripts(hooksDirectory); 112 | } 113 | 114 | private void writePluginHooks(Path hooksDirectory) throws IOException { 115 | getLog().debug("Removing legacy pre commit hook file"); 116 | Files.deleteIfExists(hooksDirectory.resolve(legacyPluginPreCommitHookFileName())); 117 | getLog().debug("Rmeoved legacy pre commit hook file"); 118 | 119 | getLog().debug("Writing plugin pre commit hook file"); 120 | executableManager 121 | .getOrCreateExecutableScript(hooksDirectory.resolve(pluginPreCommitHookFileName())) 122 | .truncateWithTemplate( 123 | () -> getClass().getResourceAsStream(BASE_PLUGIN_PRE_COMMIT_HOOK), 124 | StandardCharsets.UTF_8.toString(), 125 | mavenEnvironment.getMavenExecutable(debug).toAbsolutePath(), 126 | pomFile().toAbsolutePath(), 127 | mavenCliArguments()); 128 | getLog().debug("Written plugin pre commit hook file"); 129 | } 130 | 131 | private void configureHookBaseScripts(Path hooksDirectory) throws IOException { 132 | Executable basePreCommitHook = 133 | executableManager.getOrCreateExecutableScript( 134 | hooksDirectory.resolve(PRE_COMMIT_HOOK_BASE_SCRIPT)); 135 | getLog().debug("Configuring '" + basePreCommitHook + "'"); 136 | if (truncateHooksBaseScripts) { 137 | basePreCommitHook.truncate(); 138 | } else { 139 | legacyPreCommitHookBaseScriptCalls().forEach(basePreCommitHook::removeCommandCall); 140 | } 141 | basePreCommitHook.appendCommandCall(preCommitHookBaseScriptCall()); 142 | } 143 | 144 | private String mavenCliArguments() { 145 | Stream propagatedProperties = 146 | ofNullable(propertiesToPropagate) 147 | .map(Arrays::asList) 148 | .orElse(Collections.emptyList()) 149 | .stream() 150 | .filter(prop -> System.getProperty(prop) != null) 151 | .map(prop -> "-D" + prop + "=" + System.getProperty(prop)); 152 | 153 | Stream arguments = Stream.concat(propagatedProperties, Stream.of(propertiesToAdd)); 154 | if (preCommitHookPipeline != null && !preCommitHookPipeline.isEmpty()) { 155 | arguments = Stream.concat(arguments, Stream.of(preCommitHookPipeline)); 156 | } 157 | if (hookMavenThreadCount != null && !hookMavenThreadCount.isEmpty()) { 158 | arguments = Stream.concat(arguments, Stream.of("-T " + hookMavenThreadCount)); 159 | } 160 | return arguments.collect(Collectors.joining(" ")); 161 | } 162 | 163 | private Path prepareHooksDirectory() { 164 | getLog().debug("Preparing git hook directory"); 165 | Path hooksDirectory; 166 | hooksDirectory = getOrCreateHooksDirectory(); 167 | getLog().debug("Prepared git hook directory"); 168 | return hooksDirectory; 169 | } 170 | 171 | private String preCommitHookBaseScriptCall() { 172 | return "$(git rev-parse --git-dir)/" + HOOKS_DIR + "/" + pluginPreCommitHookFileName(); 173 | } 174 | 175 | private List legacyPreCommitHookBaseScriptCalls() { 176 | List calls = new ArrayList<>(); 177 | calls.add( 178 | "./" 179 | + gitBaseDir().relativize(getOrCreateHooksDirectory()) 180 | + "/" 181 | + legacyPluginPreCommitHookFileName()); 182 | calls.add( 183 | "./" 184 | + gitBaseDir().relativize(getOrCreateHooksDirectory()) 185 | + "/" 186 | + pluginPreCommitHookFileName()); 187 | return calls; 188 | } 189 | 190 | private String pluginPreCommitHookFileName() { 191 | return artifactId() + "." + BASE_PLUGIN_PRE_COMMIT_HOOK; 192 | } 193 | 194 | private String legacyPluginPreCommitHookFileName() { 195 | return artifactId() + "." + LEGACY_BASE_PLUGIN_PRE_COMMIT_HOOK; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/MavenGitCodeFormatException.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | /** 4 | * @author Réda Housni Alaoui 5 | */ 6 | public class MavenGitCodeFormatException extends RuntimeException { 7 | 8 | public MavenGitCodeFormatException(Throwable cause) { 9 | super(cause); 10 | } 11 | 12 | public MavenGitCodeFormatException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public MavenGitCodeFormatException(String message) { 17 | super(message); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/OnPreCommitMojo.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import com.cosium.code.format.git.GitStagedFiles; 4 | import java.io.IOException; 5 | import java.nio.file.Path; 6 | import org.apache.maven.plugin.MojoExecutionException; 7 | import org.apache.maven.plugins.annotations.LifecyclePhase; 8 | import org.apache.maven.plugins.annotations.Mojo; 9 | import org.eclipse.jgit.api.errors.GitAPIException; 10 | 11 | /** 12 | * @author Réda Housni Alaoui 13 | */ 14 | @Mojo(name = "on-pre-commit", defaultPhase = LifecyclePhase.NONE, threadSafe = true) 15 | public class OnPreCommitMojo extends AbstractModuleMavenGitCodeFormatMojo { 16 | 17 | protected void doExecute() throws MojoExecutionException { 18 | try { 19 | getLog().info("Executing pre-commit hooks"); 20 | onPreCommit(); 21 | getLog().info("Executed pre-commit hooks"); 22 | } catch (Exception e) { 23 | throw new MojoExecutionException(e.getMessage(), e); 24 | } 25 | } 26 | 27 | private void onPreCommit() throws IOException, GitAPIException { 28 | GitStagedFiles.read(getLog(), gitRepository(), this::isFormattable).format(collectCodeFormatters()); 29 | } 30 | 31 | private boolean isFormattable(Path path) { 32 | return sourceDirs().stream().anyMatch(path::startsWith); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/TemporaryFile.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import org.apache.maven.plugin.logging.Log; 10 | 11 | /** 12 | * @author Réda Housni Alaoui 13 | */ 14 | public class TemporaryFile implements Closeable { 15 | private final Log log; 16 | 17 | private final Path file; 18 | 19 | private TemporaryFile(Log log, String virtualName) { 20 | this.log = log; 21 | try { 22 | this.file = Files.createTempFile(null, null); 23 | } catch (IOException e) { 24 | throw new MavenGitCodeFormatException(e); 25 | } 26 | log.debug("Temporary file virtually named '" + virtualName + "' is viewable at '" + file + "'"); 27 | } 28 | 29 | public static TemporaryFile create(Log log, String virtualName) { 30 | return new TemporaryFile(log, virtualName); 31 | } 32 | 33 | public OutputStream newOutputStream() throws IOException { 34 | return Files.newOutputStream(file); 35 | } 36 | 37 | public InputStream newInputStream() throws IOException { 38 | return Files.newInputStream(file); 39 | } 40 | 41 | public long size() throws IOException { 42 | return Files.size(file); 43 | } 44 | 45 | @Override 46 | public void close() throws IOException { 47 | if (log.isDebugEnabled()) { 48 | return; 49 | } 50 | Files.delete(file); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/ValidateCodeFormat.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import com.cosium.code.format.formatter.CodeFormatters; 4 | import com.cosium.code.format_spi.CodeFormatter; 5 | import com.cosium.code.format_spi.FileExtension; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import org.apache.maven.plugin.MojoFailureException; 11 | import org.apache.maven.plugins.annotations.LifecyclePhase; 12 | import org.apache.maven.plugins.annotations.Mojo; 13 | 14 | /** 15 | * @author Réda Housni Alaoui 16 | */ 17 | @Mojo(name = "validate-code-format", defaultPhase = LifecyclePhase.VERIFY, threadSafe = true) 18 | public class ValidateCodeFormat extends AbstractFormatMojo { 19 | @Override 20 | protected void process(CodeFormatters codeFormatters, Path path) throws MojoFailureException { 21 | if (validate(codeFormatters, path)) { 22 | return; 23 | } 24 | throw new MojoFailureException(path + " is not correctly formatted !"); 25 | } 26 | 27 | private boolean validate(CodeFormatters codeFormatters, Path path) { 28 | return codeFormatters.forFileExtension(FileExtension.parse(path)).stream() 29 | .map(formatter -> doValidate(path, formatter)) 30 | .filter(valid -> !valid) 31 | .findFirst() 32 | .orElse(true); 33 | } 34 | 35 | private boolean doValidate(Path path, CodeFormatter formatter) { 36 | Path relativePath = gitBaseDir().relativize(path); 37 | getLog().debug("Validating '" + relativePath + "'"); 38 | try (InputStream content = Files.newInputStream(path)) { 39 | return formatter.validate(content); 40 | } catch (IOException | RuntimeException e) { 41 | throw new MavenGitCodeFormatException( 42 | String.format("Failed to validate '%s': %s", relativePath, e.getMessage()), e); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/executable/CommandRunException.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.executable; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | /** 6 | * @author Réda Housni Alaoui 7 | */ 8 | public class CommandRunException extends RuntimeException { 9 | 10 | private final int exitCode; 11 | 12 | public CommandRunException(int exitCode, String output, String... command) { 13 | super( 14 | String.format( 15 | "'%s' failed with code %s: \n\n %s", 16 | StringUtils.join(command, StringUtils.SPACE), exitCode, output)); 17 | this.exitCode = exitCode; 18 | } 19 | 20 | public int getExitCode() { 21 | return exitCode; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/executable/CommandRunner.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.executable; 2 | 3 | import java.nio.file.Path; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author Réda Housni Alaoui 8 | */ 9 | public interface CommandRunner { 10 | String run(Path workingDir, Map environment, String... command); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/executable/DefaulExecutable.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.executable; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import com.cosium.code.format.MavenGitCodeFormatException; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.StandardOpenOption; 11 | import java.nio.file.attribute.PosixFilePermission; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Set; 15 | import java.util.function.Supplier; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.Stream; 18 | import org.apache.commons.io.IOUtils; 19 | import org.apache.commons.lang3.StringUtils; 20 | import org.apache.maven.plugin.logging.Log; 21 | 22 | /** 23 | * @author Réda Housni Alaoui 24 | */ 25 | class DefaulExecutable implements Executable { 26 | 27 | private static final String SHIBANG = "#!/bin/bash"; 28 | 29 | private final Supplier log; 30 | private final Path file; 31 | 32 | DefaulExecutable(Supplier log, Path file) throws IOException { 33 | requireNonNull(log); 34 | requireNonNull(file); 35 | this.log = log; 36 | 37 | this.file = file; 38 | 39 | if (!Files.exists(file)) { 40 | this.log.get().debug("Creating " + file); 41 | Files.createFile(file); 42 | truncate(); 43 | } else { 44 | this.log.get().debug(file + " already exists"); 45 | } 46 | 47 | this.log.get().debug("Marking '" + file + "' as executable"); 48 | Set permissions; 49 | try { 50 | permissions = Files.getPosixFilePermissions(file); 51 | } catch (UnsupportedOperationException ignored) { 52 | return; 53 | } 54 | 55 | permissions.add(PosixFilePermission.OWNER_EXECUTE); 56 | permissions.add(PosixFilePermission.GROUP_EXECUTE); 57 | permissions.add(PosixFilePermission.OTHERS_EXECUTE); 58 | 59 | Files.setPosixFilePermissions(file, permissions); 60 | } 61 | 62 | @Override 63 | public Executable truncate() throws IOException { 64 | log.get().debug("Truncating '" + file + "'"); 65 | Files.write(file, Collections.singleton(SHIBANG), StandardOpenOption.TRUNCATE_EXISTING); 66 | return this; 67 | } 68 | 69 | @Override 70 | public Executable truncateWithTemplate( 71 | Supplier template, String sourceEncoding, Object... values) throws IOException { 72 | try (InputStream inputStream = template.get()) { 73 | String rawContent = IOUtils.toString(inputStream, sourceEncoding); 74 | Object[] refinedValues = Stream.of(values).map(this::unixifyPath).toArray(); 75 | String content = String.format(rawContent, refinedValues); 76 | Files.write(file, content.getBytes(), StandardOpenOption.TRUNCATE_EXISTING); 77 | } 78 | return this; 79 | } 80 | 81 | @Override 82 | public Executable appendCommandCall(String commandCall) throws IOException { 83 | String unixCommandCall = unixifyPath(commandCall, true); 84 | boolean callExists = 85 | Files.readAllLines(file).stream().anyMatch(s -> s.contains(unixCommandCall)); 86 | if (callExists) { 87 | log.get().debug("Command call already exists in " + file); 88 | } else { 89 | log.get().debug("No command call found in " + file); 90 | log.get().debug("Appending the command call to " + file); 91 | Files.write(file, Collections.singletonList(unixCommandCall), StandardOpenOption.APPEND); 92 | log.get().debug("Appended the command call to " + file); 93 | } 94 | return this; 95 | } 96 | 97 | @Override 98 | public Executable removeCommandCall(String commandCall) { 99 | String unixCommandCall = unixifyPath(commandCall, true); 100 | try { 101 | List linesToKeep = 102 | Files.readAllLines(file).stream() 103 | .filter(line -> !unixCommandCall.equals(line)) 104 | .collect(Collectors.toList()); 105 | Files.write(file, linesToKeep, StandardOpenOption.TRUNCATE_EXISTING); 106 | } catch (IOException e) { 107 | throw new MavenGitCodeFormatException(e); 108 | } 109 | return this; 110 | } 111 | 112 | private String unixifyPath(Object o) { 113 | return unixifyPath(o, false); 114 | } 115 | 116 | private String unixifyPath(Object o, boolean force) { 117 | if (!force && !(o instanceof Path)) { 118 | return String.valueOf(o); 119 | } 120 | 121 | String result; 122 | if (o instanceof Path) { 123 | Path path = (Path) o; 124 | result = path.toAbsolutePath().toString(); 125 | } else { 126 | result = String.valueOf(o); 127 | } 128 | return "\"" + StringUtils.replace(result, "\\", "/") + "\""; 129 | } 130 | 131 | @Override 132 | public String toString() { 133 | return file.toString(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/executable/DefaultCommandRunner.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.executable; 2 | 3 | import com.cosium.code.format.MavenGitCodeFormatException; 4 | import java.io.IOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.nio.file.Path; 7 | import java.util.Map; 8 | import java.util.function.Supplier; 9 | import org.apache.commons.io.IOUtils; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.apache.maven.plugin.logging.Log; 12 | 13 | /** 14 | * @author Réda Housni Alaoui 15 | */ 16 | public class DefaultCommandRunner implements CommandRunner { 17 | private final Supplier log; 18 | 19 | public DefaultCommandRunner(Supplier log) { 20 | this.log = log; 21 | } 22 | 23 | @Override 24 | public String run(Path workingDir, Map environment, String... command) { 25 | try { 26 | ProcessBuilder processBuilder = new ProcessBuilder(command); 27 | processBuilder.environment().putAll(environment); 28 | if (workingDir != null) { 29 | processBuilder.directory(workingDir.toFile()); 30 | } 31 | processBuilder.redirectInput(ProcessBuilder.Redirect.INHERIT); 32 | 33 | log.get().debug("Executing '" + StringUtils.join(command, StringUtils.SPACE) + "'"); 34 | Process process = processBuilder.start(); 35 | 36 | String output = 37 | IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8).trim() 38 | + IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8).trim(); 39 | 40 | int exitCode = process.waitFor(); 41 | if (exitCode != 0) { 42 | throw new CommandRunException(exitCode, output, command); 43 | } 44 | 45 | log.get().debug(output); 46 | return StringUtils.defaultIfBlank(output, null); 47 | } catch (InterruptedException e) { 48 | Thread.currentThread().interrupt(); 49 | throw new MavenGitCodeFormatException(e); 50 | } catch (IOException e) { 51 | throw new MavenGitCodeFormatException(e); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/executable/Executable.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.executable; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.function.Supplier; 6 | 7 | /** 8 | * @author Réda Housni Alaoui 9 | */ 10 | public interface Executable { 11 | 12 | /** Erase the executable content */ 13 | Executable truncate() throws IOException; 14 | 15 | /** 16 | * @param template The template to truncate with 17 | * @param sourceEncoding The source encoding 18 | * @param values The values to use for the template interpolations 19 | */ 20 | Executable truncateWithTemplate( 21 | Supplier template, String sourceEncoding, Object... values) throws IOException; 22 | 23 | /** 24 | * Appends a command call to the executable 25 | * 26 | * @param commandCall The command call to append to the executable 27 | */ 28 | Executable appendCommandCall(String commandCall) throws IOException; 29 | 30 | /** 31 | * Remove a command call from the executable 32 | * 33 | * @param commandCall The command call to remove 34 | */ 35 | Executable removeCommandCall(String commandCall); 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/executable/ExecutableManager.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.executable; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.Path; 7 | import java.util.function.Supplier; 8 | import org.apache.maven.plugin.logging.Log; 9 | 10 | /** 11 | * @author Réda Housni Alaoui 12 | */ 13 | public class ExecutableManager { 14 | 15 | private final Supplier log; 16 | 17 | public ExecutableManager(Supplier log) { 18 | requireNonNull(log); 19 | this.log = log; 20 | } 21 | 22 | /** 23 | * Get or creates a file then mark it as executable. 24 | * 25 | * @param file The file 26 | */ 27 | public Executable getOrCreateExecutableScript(Path file) throws IOException { 28 | return new DefaulExecutable(log, file); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/formatter/CodeFormatterConfigurationFactory.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.formatter; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import com.cosium.code.format_spi.CodeFormatterConfiguration; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author Réda Housni Alaoui 10 | */ 11 | public class CodeFormatterConfigurationFactory { 12 | 13 | private final Map globalConfiguration; 14 | 15 | public CodeFormatterConfigurationFactory(Map globalConfiguration) { 16 | this.globalConfiguration = requireNonNull(globalConfiguration); 17 | } 18 | 19 | public CodeFormatterConfiguration build(String formatterConfigurationId) { 20 | return new SimpleCodeFormatterConfiguration(globalConfiguration, formatterConfigurationId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/formatter/CodeFormatters.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.formatter; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import com.cosium.code.format_spi.CodeFormatter; 6 | import com.cosium.code.format_spi.FileExtension; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * @author Réda Housni Alaoui 12 | */ 13 | public class CodeFormatters { 14 | 15 | private final List formatters; 16 | 17 | public CodeFormatters(List formatters) { 18 | this.formatters = requireNonNull(formatters); 19 | } 20 | 21 | public List forFileExtension(FileExtension fileExtension) { 22 | return formatters.stream() 23 | .filter(formatter -> formatter.fileExtension().equals(fileExtension)) 24 | .collect(Collectors.toList()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/formatter/SimpleCodeFormatterConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.formatter; 2 | 3 | import com.cosium.code.format_spi.CodeFormatterConfiguration; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | /** 8 | * @author Réda Housni Alaoui 9 | */ 10 | class SimpleCodeFormatterConfiguration implements CodeFormatterConfiguration { 11 | 12 | private final Map globalConfiguration; 13 | private final String formatterConfigurationId; 14 | 15 | public SimpleCodeFormatterConfiguration( 16 | Map globalConfiguration, String formatterConfigurationId) { 17 | this.globalConfiguration = globalConfiguration; 18 | this.formatterConfigurationId = formatterConfigurationId; 19 | } 20 | 21 | @Override 22 | public Optional getValue(String key) { 23 | return Optional.ofNullable( 24 | globalConfiguration.get(String.format("%s.%s", formatterConfigurationId, key))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/git/AutoCRLFObjectLoader.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.git; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.IOException; 7 | import java.io.OutputStream; 8 | import org.apache.commons.io.IOUtils; 9 | import org.apache.commons.io.input.CountingInputStream; 10 | import org.eclipse.jgit.errors.LargeObjectException; 11 | import org.eclipse.jgit.errors.MissingObjectException; 12 | import org.eclipse.jgit.lib.CoreConfig.EolStreamType; 13 | import org.eclipse.jgit.lib.ObjectLoader; 14 | import org.eclipse.jgit.lib.ObjectStream; 15 | import org.eclipse.jgit.util.io.EolStreamTypeUtil; 16 | 17 | /** 18 | * @author Réda Housni Alaoui 19 | */ 20 | public class AutoCRLFObjectLoader extends ObjectLoader { 21 | 22 | private final ObjectLoader delegate; 23 | private final EolStreamType eolStreamType; 24 | private Long cachedSize; 25 | 26 | public AutoCRLFObjectLoader(ObjectLoader delegate, EolStreamType eolStreamType) { 27 | this.delegate = requireNonNull(delegate); 28 | this.eolStreamType = requireNonNull(eolStreamType); 29 | } 30 | 31 | @Override 32 | public int getType() { 33 | return delegate.getType(); 34 | } 35 | 36 | @Override 37 | public long getSize() { 38 | // https://github.com/Cosium/git-code-format-maven-plugin/issues/42: It is very important to 39 | // return the exact transformed content size 40 | if (cachedSize != null) { 41 | return cachedSize; 42 | } 43 | try (CountingInputStream countingInputStream = new CountingInputStream(openStream())) { 44 | while (countingInputStream.read() != -1) { 45 | // Do nothing in the while, we are just moving bytes through CountingInputstream to retrieve 46 | // the total stream size 47 | } 48 | cachedSize = countingInputStream.getByteCount(); 49 | } catch (IOException e) { 50 | throw new RuntimeException(e); 51 | } 52 | return cachedSize; 53 | } 54 | 55 | @Override 56 | public boolean isLarge() { 57 | return delegate.isLarge(); 58 | } 59 | 60 | @Override 61 | public byte[] getCachedBytes() throws LargeObjectException { 62 | return convertBytes(delegate.getCachedBytes()); 63 | } 64 | 65 | @Override 66 | public byte[] getCachedBytes(int sizeLimit) 67 | throws LargeObjectException, MissingObjectException, IOException { 68 | return convertBytes(delegate.getCachedBytes(sizeLimit)); 69 | } 70 | 71 | private byte[] convertBytes(byte[] bytes) { 72 | try { 73 | return IOUtils.toByteArray( 74 | EolStreamTypeUtil.wrapInputStream(new ByteArrayInputStream(bytes), eolStreamType)); 75 | } catch (IOException e) { 76 | throw new RuntimeException(e); 77 | } 78 | } 79 | 80 | @Override 81 | public ObjectStream openStream() throws MissingObjectException, IOException { 82 | return new AutoCRLFObjectStream(delegate.openStream(), eolStreamType); 83 | } 84 | 85 | @Override 86 | public void copyTo(OutputStream out) throws MissingObjectException, IOException { 87 | delegate.copyTo(EolStreamTypeUtil.wrapOutputStream(out, eolStreamType)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/git/AutoCRLFObjectReader.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.git; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.io.IOException; 6 | import java.util.Collection; 7 | import java.util.Set; 8 | import org.eclipse.jgit.errors.IncorrectObjectTypeException; 9 | import org.eclipse.jgit.errors.MissingObjectException; 10 | import org.eclipse.jgit.lib.AbbreviatedObjectId; 11 | import org.eclipse.jgit.lib.AnyObjectId; 12 | import org.eclipse.jgit.lib.CoreConfig.EolStreamType; 13 | import org.eclipse.jgit.lib.ObjectId; 14 | import org.eclipse.jgit.lib.ObjectLoader; 15 | import org.eclipse.jgit.lib.ObjectReader; 16 | 17 | /** 18 | * @author Réda Housni Alaoui 19 | */ 20 | public class AutoCRLFObjectReader extends ObjectReader { 21 | 22 | private final ObjectReader delegate; 23 | private final EolStreamType eolStreamType; 24 | 25 | public AutoCRLFObjectReader(ObjectReader delegate, EolStreamType eolStreamType) { 26 | this.delegate = requireNonNull(delegate); 27 | this.eolStreamType = requireNonNull(eolStreamType); 28 | } 29 | 30 | @Override 31 | public ObjectReader newReader() { 32 | return new AutoCRLFObjectReader(delegate.newReader(), eolStreamType); 33 | } 34 | 35 | @Override 36 | public Collection resolve(AbbreviatedObjectId id) throws IOException { 37 | return delegate.resolve(id); 38 | } 39 | 40 | @Override 41 | public ObjectLoader open(AnyObjectId objectId, int typeHint) 42 | throws MissingObjectException, IncorrectObjectTypeException, IOException { 43 | return new AutoCRLFObjectLoader(delegate.open(objectId, typeHint), eolStreamType); 44 | } 45 | 46 | @Override 47 | public Set getShallowCommits() throws IOException { 48 | return delegate.getShallowCommits(); 49 | } 50 | 51 | @Override 52 | public void close() { 53 | delegate.close(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/git/AutoCRLFObjectStream.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.git; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import org.eclipse.jgit.lib.CoreConfig.EolStreamType; 8 | import org.eclipse.jgit.lib.ObjectStream; 9 | import org.eclipse.jgit.util.io.EolStreamTypeUtil; 10 | 11 | /** 12 | * @author Réda Housni Alaoui 13 | */ 14 | public class AutoCRLFObjectStream extends ObjectStream { 15 | 16 | private final ObjectStream delegate; 17 | private final InputStream autoCRLFInputStream; 18 | 19 | public AutoCRLFObjectStream(ObjectStream delegate, EolStreamType eolStreamType) { 20 | this.delegate = requireNonNull(delegate); 21 | this.autoCRLFInputStream = 22 | requireNonNull(EolStreamTypeUtil.wrapInputStream(delegate, eolStreamType)); 23 | } 24 | 25 | @Override 26 | public int getType() { 27 | return delegate.getType(); 28 | } 29 | 30 | @Override 31 | public long getSize() { 32 | return delegate.getSize(); 33 | } 34 | 35 | @Override 36 | public int read() throws IOException { 37 | return autoCRLFInputStream.read(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/git/AutoCRLFRepository.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.git; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import org.eclipse.jgit.internal.storage.file.FileRepository; 8 | import org.eclipse.jgit.lib.CoreConfig.EolStreamType; 9 | import org.eclipse.jgit.lib.ObjectReader; 10 | 11 | /** 12 | * @author Réda Housni Alaoui 13 | */ 14 | public class AutoCRLFRepository extends FileRepository { 15 | 16 | private final EolStreamType eolStreamType; 17 | 18 | public AutoCRLFRepository(File gitDir, EolStreamType eolStreamType) throws IOException { 19 | super(gitDir); 20 | this.eolStreamType = requireNonNull(eolStreamType); 21 | } 22 | 23 | @Override 24 | public ObjectReader newObjectReader() { 25 | return new AutoCRLFObjectReader(super.newObjectReader(), eolStreamType); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/git/GitIndexEntry.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.git; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; 5 | 6 | import com.cosium.code.format.MavenGitCodeFormatException; 7 | import com.cosium.code.format.TemporaryFile; 8 | import com.cosium.code.format.formatter.CodeFormatters; 9 | import com.cosium.code.format_spi.CodeFormatter; 10 | import com.cosium.code.format_spi.FileExtension; 11 | import com.cosium.code.format_spi.LineRanges; 12 | import com.google.common.collect.Range; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.OutputStream; 16 | import java.util.Set; 17 | import java.util.function.Predicate; 18 | import java.util.stream.Collectors; 19 | import java.util.stream.Stream; 20 | import org.apache.commons.io.IOUtils; 21 | import org.apache.maven.plugin.logging.Log; 22 | import org.eclipse.jgit.api.Git; 23 | import org.eclipse.jgit.api.errors.GitAPIException; 24 | import org.eclipse.jgit.diff.DiffEntry; 25 | import org.eclipse.jgit.diff.DiffFormatter; 26 | import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; 27 | import org.eclipse.jgit.dircache.DirCacheEntry; 28 | import org.eclipse.jgit.lib.ObjectDatabase; 29 | import org.eclipse.jgit.lib.ObjectId; 30 | import org.eclipse.jgit.lib.ObjectInserter; 31 | import org.eclipse.jgit.lib.ObjectLoader; 32 | import org.eclipse.jgit.lib.Repository; 33 | import org.eclipse.jgit.patch.FileHeader; 34 | import org.eclipse.jgit.patch.HunkHeader; 35 | import org.eclipse.jgit.treewalk.filter.PathFilter; 36 | import org.eclipse.jgit.util.io.NullOutputStream; 37 | 38 | /** 39 | * @author Réda Housni Alaoui 40 | */ 41 | class GitIndexEntry { 42 | 43 | private final Log log; 44 | private final Repository repository; 45 | private final String path; 46 | 47 | GitIndexEntry(Log log, Repository repository, String path) { 48 | this.log = requireNonNull(log); 49 | this.repository = requireNonNull(repository); 50 | this.path = requireNonNull(path); 51 | } 52 | 53 | PathEdit entryFormatter(CodeFormatters formatters) { 54 | return new EntryFormatter(log, repository, formatters, path); 55 | } 56 | 57 | private static class EntryFormatter extends PathEdit { 58 | 59 | private final Log log; 60 | private final Repository repository; 61 | private final CodeFormatters formatters; 62 | 63 | EntryFormatter(Log log, Repository repository, CodeFormatters formatters, String entryPath) { 64 | super(entryPath); 65 | this.log = log; 66 | this.repository = requireNonNull(repository); 67 | this.formatters = requireNonNull(formatters); 68 | } 69 | 70 | @Override 71 | public void apply(DirCacheEntry dirCacheEntry) { 72 | formatters 73 | .forFileExtension(FileExtension.parse(dirCacheEntry.getPathString())) 74 | .forEach(formatter -> doFormat(dirCacheEntry, formatter)); 75 | } 76 | 77 | private void doFormat(DirCacheEntry dirCacheEntry, CodeFormatter formatter) { 78 | LineRanges lineRanges = computeLineRanges(dirCacheEntry); 79 | if (lineRanges.isAll()) { 80 | log.info("Formatting '" + dirCacheEntry.getPathString() + "'"); 81 | } else { 82 | log.info("Formatting lines " + lineRanges + " of '" + dirCacheEntry.getPathString() + "'"); 83 | } 84 | 85 | try (TemporaryFile temporaryFormattedFile = 86 | TemporaryFile.create(log, dirCacheEntry.getPathString() + ".formatted")) { 87 | ObjectId unformattedObjectId = dirCacheEntry.getObjectId(); 88 | log.debug("Unformatted object id is '" + unformattedObjectId + "'"); 89 | ObjectDatabase objectDatabase = repository.getObjectDatabase(); 90 | ObjectLoader objectLoader = objectDatabase.open(unformattedObjectId); 91 | 92 | logObjectContent(objectLoader, dirCacheEntry.getPathString() + ".unformatted"); 93 | 94 | try (InputStream content = objectLoader.openStream(); 95 | OutputStream formattedContent = temporaryFormattedFile.newOutputStream()) { 96 | formatter.format(content, lineRanges, formattedContent); 97 | } 98 | 99 | long formattedSize = temporaryFormattedFile.size(); 100 | ObjectId formattedObjectId; 101 | try (InputStream formattedContent = temporaryFormattedFile.newInputStream(); 102 | ObjectInserter objectInserter = objectDatabase.newInserter()) { 103 | formattedObjectId = objectInserter.insert(OBJ_BLOB, formattedSize, formattedContent); 104 | objectInserter.flush(); 105 | } 106 | 107 | log.debug("Formatted size is " + formattedSize); 108 | dirCacheEntry.setLength(formattedSize); 109 | log.debug("Formatted object id is '" + formattedObjectId + "'"); 110 | dirCacheEntry.setObjectId(formattedObjectId); 111 | } catch (IOException e) { 112 | throw new MavenGitCodeFormatException(e); 113 | } 114 | 115 | if (lineRanges.isAll()) { 116 | log.info("Formatted '" + dirCacheEntry.getPathString() + "'"); 117 | } else { 118 | log.info("Formatted lines " + lineRanges + " of '" + dirCacheEntry.getPathString() + "'"); 119 | } 120 | } 121 | 122 | private void logObjectContent(ObjectLoader objectLoader, String virtualName) 123 | throws IOException { 124 | if (!log.isDebugEnabled()) { 125 | return; 126 | } 127 | 128 | try (InputStream input = objectLoader.openStream(); 129 | OutputStream output = TemporaryFile.create(log, virtualName).newOutputStream()) { 130 | IOUtils.copy(input, output); 131 | } 132 | } 133 | 134 | private LineRanges computeLineRanges(DirCacheEntry dirCacheEntry) { 135 | Git git = new Git(repository); 136 | boolean partiallyStaged; 137 | try { 138 | partiallyStaged = 139 | !git.status().addPath(dirCacheEntry.getPathString()).call().getModified().isEmpty(); 140 | } catch (GitAPIException e) { 141 | throw new MavenGitCodeFormatException(e); 142 | } 143 | 144 | if (!partiallyStaged) { 145 | return LineRanges.all(); 146 | } 147 | 148 | try { 149 | return git 150 | .diff() 151 | .setPathFilter(PathFilter.create(dirCacheEntry.getPathString())) 152 | .setCached(true) 153 | .call() 154 | .stream() 155 | .map(this::computeLineRanges) 156 | .reduce(LineRanges::concat) 157 | .orElse(LineRanges.all()); 158 | 159 | } catch (GitAPIException e) { 160 | throw new MavenGitCodeFormatException(e); 161 | } 162 | } 163 | 164 | private LineRanges computeLineRanges(DiffEntry diffEntry) { 165 | DiffFormatter diffFormatter = new DiffFormatter(NullOutputStream.INSTANCE); 166 | diffFormatter.setRepository(repository); 167 | 168 | try { 169 | FileHeader fileHeader = diffFormatter.toFileHeader(diffEntry); 170 | return fileHeader.getHunks().stream() 171 | .map(HunkHeader.class::cast) 172 | .map(this::computeLineRanges) 173 | .reduce(LineRanges::concat) 174 | .orElse(LineRanges.all()); 175 | } catch (IOException e) { 176 | throw new MavenGitCodeFormatException(e); 177 | } 178 | } 179 | 180 | private LineRanges computeLineRanges(HunkHeader hunkHeader) { 181 | Set> ranges = 182 | hunkHeader.toEditList().stream() 183 | .flatMap( 184 | edit -> 185 | Stream.of( 186 | Range.closedOpen(edit.getBeginA(), edit.getEndA()), 187 | Range.closedOpen(edit.getBeginB(), edit.getEndB()))) 188 | .filter(((Predicate>) Range::isEmpty).negate()) 189 | .collect(Collectors.toSet()); 190 | return LineRanges.of(ranges); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/git/GitStagedFiles.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.git; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import com.cosium.code.format.MavenGitCodeFormatException; 6 | import com.cosium.code.format.TemporaryFile; 7 | import com.cosium.code.format.formatter.CodeFormatters; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | import java.nio.file.Path; 12 | import java.util.Collections; 13 | import java.util.Set; 14 | import java.util.function.Predicate; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.Stream; 17 | import org.apache.maven.plugin.logging.Log; 18 | import org.eclipse.jgit.api.Git; 19 | import org.eclipse.jgit.api.Status; 20 | import org.eclipse.jgit.api.errors.GitAPIException; 21 | import org.eclipse.jgit.dircache.DirCache; 22 | import org.eclipse.jgit.dircache.DirCacheEditor; 23 | import org.eclipse.jgit.dircache.DirCacheIterator; 24 | import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; 25 | import org.eclipse.jgit.lib.CoreConfig.EolStreamType; 26 | import org.eclipse.jgit.lib.Repository; 27 | import org.eclipse.jgit.treewalk.AbstractTreeIterator; 28 | import org.eclipse.jgit.treewalk.WorkingTreeOptions; 29 | 30 | /** 31 | * @author Réda Housni Alaoui 32 | */ 33 | public class GitStagedFiles { 34 | 35 | private final Log log; 36 | private final Repository repository; 37 | private final Set filePaths; 38 | private final EolStreamType eolStreamType; 39 | 40 | private GitStagedFiles(Log log, Repository repository, Set filePaths) { 41 | this.log = requireNonNull(log); 42 | this.repository = requireNonNull(repository); 43 | this.filePaths = Collections.unmodifiableSet(filePaths); 44 | 45 | WorkingTreeOptions workingTreeOptions = repository.getConfig().get(WorkingTreeOptions.KEY); 46 | if (workingTreeOptions.getAutoCRLF() == AutoCRLF.TRUE) { 47 | eolStreamType = EolStreamType.AUTO_CRLF; 48 | } else { 49 | eolStreamType = EolStreamType.DIRECT; 50 | } 51 | log.debug("eolStreamType is '" + eolStreamType + "'"); 52 | } 53 | 54 | public static GitStagedFiles read(Log log, Repository repository, Predicate fileFilter) 55 | throws GitAPIException { 56 | Status gitStatus = new Git(repository).status().call(); 57 | Path workTree = repository.getWorkTree().toPath(); 58 | Set filePaths = 59 | Stream.concat(gitStatus.getChanged().stream(), gitStatus.getAdded().stream()) 60 | .filter(relativePath -> fileFilter.test(workTree.resolve(relativePath))) 61 | .collect(Collectors.toSet()); 62 | log.debug("Staged files: " + filePaths); 63 | return new GitStagedFiles(log, repository, filePaths); 64 | } 65 | 66 | public void format(CodeFormatters formatters) throws IOException { 67 | if (filePaths.isEmpty()) { 68 | log.debug("No staged files to format"); 69 | return; 70 | } 71 | 72 | Git git = new Git(repository); 73 | 74 | try (Index index = Index.lock(repository); 75 | TemporaryFile temporaryDiffFile = 76 | TemporaryFile.create(log, "diff-between-unformatted-and-formatted-files")) { 77 | DirCacheEditor dirCacheEditor = index.editor(); 78 | filePaths.stream() 79 | .map(path -> new GitIndexEntry(log, repository, path)) 80 | .map(indexEntry -> indexEntry.entryFormatter(formatters)) 81 | .forEach(dirCacheEditor::add); 82 | dirCacheEditor.finish(); 83 | 84 | index.write(); 85 | 86 | try (Repository autoCRLFRepository = 87 | new AutoCRLFRepository(git.getRepository().getDirectory(), eolStreamType); 88 | OutputStream diffOutput = temporaryDiffFile.newOutputStream()) { 89 | new Git(autoCRLFRepository) 90 | .diff() 91 | .setOutputStream(diffOutput) 92 | .setOldTree(treeIterator(repository.readDirCache())) 93 | .setNewTree(index.treeIterator()) 94 | .call(); 95 | } 96 | 97 | try (InputStream diffInput = temporaryDiffFile.newInputStream()) { 98 | git.apply().setPatch(diffInput).call(); 99 | } 100 | 101 | index.commit(); 102 | } catch (GitAPIException e) { 103 | throw new MavenGitCodeFormatException(e); 104 | } 105 | } 106 | 107 | private AbstractTreeIterator treeIterator(DirCache dirCache) { 108 | return new DirCacheIterator(dirCache); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/git/Index.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.git; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.concurrent.locks.Lock; 8 | import java.util.concurrent.locks.ReentrantLock; 9 | import org.eclipse.jgit.dircache.DirCache; 10 | import org.eclipse.jgit.dircache.DirCacheEditor; 11 | import org.eclipse.jgit.dircache.DirCacheIterator; 12 | import org.eclipse.jgit.lib.Repository; 13 | import org.eclipse.jgit.treewalk.AbstractTreeIterator; 14 | 15 | /** 16 | * @author Réda Housni Alaoui 17 | */ 18 | public class Index implements AutoCloseable { 19 | 20 | private static final Map GLOBAL_LOCK_BY_REPOSITORY_DIRECTORY = 21 | new ConcurrentHashMap<>(); 22 | 23 | private final Lock globalLock; 24 | private final DirCache dirCache; 25 | 26 | private Index(Repository repository) throws IOException { 27 | globalLock = 28 | GLOBAL_LOCK_BY_REPOSITORY_DIRECTORY.computeIfAbsent( 29 | repository.getDirectory(), repositoryDirectory -> new ReentrantLock()); 30 | globalLock.lock(); 31 | try { 32 | dirCache = repository.lockDirCache(); 33 | } catch (RuntimeException e) { 34 | globalLock.unlock(); 35 | throw e; 36 | } 37 | } 38 | 39 | public static Index lock(Repository repository) throws IOException { 40 | return new Index(repository); 41 | } 42 | 43 | public DirCacheEditor editor() { 44 | return dirCache.editor(); 45 | } 46 | 47 | public void write() throws IOException { 48 | dirCache.write(); 49 | } 50 | 51 | public void commit() { 52 | dirCache.commit(); 53 | } 54 | 55 | public AbstractTreeIterator treeIterator() { 56 | return new DirCacheIterator(dirCache); 57 | } 58 | 59 | @Override 60 | public void close() { 61 | try { 62 | dirCache.unlock(); 63 | } finally { 64 | globalLock.unlock(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/src/main/java/com/cosium/code/format/maven/MavenEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.maven; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import com.cosium.code.format.MavenGitCodeFormatException; 6 | import com.cosium.code.format.executable.CommandRunner; 7 | import com.cosium.code.format.executable.DefaultCommandRunner; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.function.Supplier; 15 | import java.util.function.UnaryOperator; 16 | import org.apache.commons.exec.OS; 17 | import org.apache.maven.plugin.logging.Log; 18 | 19 | /** 20 | * @author Réda Housni Alaoui 21 | * @author Matt.Ruel 22 | */ 23 | public class MavenEnvironment { 24 | 25 | private static final String MAVEN_HOME_PROP = "maven.home"; 26 | 27 | private final Supplier log; 28 | private final UnaryOperator systemProperties; 29 | private final CommandRunner commandRunner; 30 | 31 | public MavenEnvironment(Supplier log) { 32 | this(log, System::getProperty, new DefaultCommandRunner(log)); 33 | } 34 | 35 | MavenEnvironment( 36 | Supplier log, UnaryOperator systemProperties, CommandRunner commandRunner) { 37 | this.log = log; 38 | this.systemProperties = requireNonNull(systemProperties); 39 | this.commandRunner = requireNonNull(commandRunner); 40 | } 41 | 42 | public Path getMavenExecutable(boolean debug) { 43 | Path mavenHome = Paths.get(systemProperties.apply(MAVEN_HOME_PROP)); 44 | log.get().debug("maven.home=" + mavenHome); 45 | Path mavenBinDirectory = mavenHome.resolve("bin"); 46 | 47 | List> executableCandidates = 48 | Arrays.asList( 49 | Arrays.asList( 50 | new Executable(debug, mavenBinDirectory, Extension.NONE), 51 | new Executable(debug, null, Extension.NONE)), 52 | Arrays.asList( 53 | new Executable(debug, mavenBinDirectory, Extension.CMD), 54 | new Executable(debug, null, Extension.CMD))); 55 | 56 | if (OS.isFamilyWindows()) { 57 | Collections.reverse(executableCandidates); 58 | } 59 | 60 | return executableCandidates.stream() 61 | .flatMap(Collection::stream) 62 | .filter(Executable::isValid) 63 | .findFirst() 64 | .map(Executable::path) 65 | .orElseThrow(() -> new MavenGitCodeFormatException("No valid maven executable found !")); 66 | } 67 | 68 | private class Executable { 69 | 70 | private final Path path; 71 | 72 | private Executable(boolean debug, Path prefix, Extension extension) { 73 | String name = "mvn"; 74 | if (debug) { 75 | name += "Debug"; 76 | } 77 | if (extension != Extension.NONE) { 78 | name += "." + extension.value; 79 | } 80 | if (prefix != null) { 81 | path = prefix.resolve(name); 82 | } else { 83 | path = Paths.get(name); 84 | } 85 | } 86 | 87 | Path path() { 88 | return path; 89 | } 90 | 91 | boolean isValid() { 92 | try { 93 | commandRunner.run( 94 | null, Collections.singletonMap("MAVEN_DEBUG_OPTS", ""), path.toString(), "--version"); 95 | return true; 96 | } catch (Exception e) { 97 | log.get().debug(e.getMessage()); 98 | } 99 | return false; 100 | } 101 | } 102 | 103 | private enum Extension { 104 | NONE(null), 105 | CMD("cmd"); 106 | private final String value; 107 | 108 | Extension(String value) { 109 | this.value = value; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /core/src/main/resources/com/cosium/code/format/git-code-format.pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | %s -f %s com.cosium.code:git-code-format-maven-plugin:on-pre-commit %s 4 | -------------------------------------------------------------------------------- /core/src/test/java/com/cosium/code/format/AbstractMavenModuleTest.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import io.takari.maven.testing.executor.MavenExecution; 6 | import io.takari.maven.testing.executor.MavenRuntime; 7 | import java.nio.file.Paths; 8 | import org.junit.Test; 9 | 10 | /** 11 | * @author Réda Housni Alaoui 12 | */ 13 | public abstract class AbstractMavenModuleTest extends AbstractTest { 14 | 15 | private final String badFormatJava; 16 | private final String mavenModuleDirectory; 17 | 18 | public AbstractMavenModuleTest( 19 | MavenRuntime.MavenRuntimeBuilder mavenBuilder, 20 | String projectRootDirectoryName, 21 | String mavenModuleDirectory) 22 | throws Exception { 23 | super(mavenBuilder, projectRootDirectoryName); 24 | this.mavenModuleDirectory = mavenModuleDirectory; 25 | this.badFormatJava = 26 | Paths.get(mavenModuleDirectory).resolve("src/main/java/BadFormat.java").toString(); 27 | } 28 | 29 | @Test 30 | public void GIVEN_bad_formatted_files_WHEN_format_code_THEN_all_files_should_have_correct_format() 31 | throws Exception { 32 | mavenExecution() 33 | .withCliOptions(goalCliOption("validate-code-format")) 34 | .execute() 35 | .assertLogText("is not correctly formatted"); 36 | 37 | mavenExecution().withCliOptions(goalCliOption("format-code")).execute().assertErrorFreeLog(); 38 | 39 | mavenExecution() 40 | .withCliOptions(goalCliOption("validate-code-format")) 41 | .execute() 42 | .assertErrorFreeLog(); 43 | 44 | assertMatchExpected(badFormatJava); 45 | } 46 | 47 | @Test 48 | public void 49 | GIVEN_bad_formatted_file_WHEN_adding_and_committing_it_THEN_it_should_have_correct_format() 50 | throws Exception { 51 | mavenExecution() 52 | .withCliOptions(goalCliOption("validate-code-format")) 53 | .execute() 54 | .assertLogText("is not correctly formatted"); 55 | 56 | mavenExecution().execute("initialize").assertErrorFreeLog(); 57 | 58 | touch(badFormatJava); 59 | 60 | jGit().add().addFilepattern(".").call(); 61 | jGit() 62 | .commit() 63 | .setCommitter(gitIdentity()) 64 | .setAuthor(gitIdentity()) 65 | .setMessage("Trying to commit badly formatted file") 66 | .call(); 67 | 68 | mavenExecution() 69 | .withCliOptions(goalCliOption("validate-code-format")) 70 | .execute() 71 | .assertErrorFreeLog(); 72 | 73 | assertMatchExpected(badFormatJava); 74 | } 75 | 76 | @Test 77 | public void 78 | GIVEN_bad_formatted_generated_file_WHEN_formatting_THEN_generated_file_should_be_skipped() 79 | throws Exception { 80 | String generatedSourceFile = 81 | Paths.get(mavenModuleDirectory) 82 | .resolve("target/generated-sources/GeneratedBadFormat.java") 83 | .toString(); 84 | String oldChecksum = sha1(generatedSourceFile); 85 | 86 | mavenExecution().withCliOptions(goalCliOption("format-code")).execute(); 87 | 88 | String newChecksum = sha1(generatedSourceFile); 89 | assertThat(newChecksum).isEqualTo(oldChecksum); 90 | } 91 | 92 | @Test 93 | public void 94 | GIVEN_bad_formatted_files_WHEN_format_code_with_aosp_enabled_THEN_all_files_should_be_formatted_according_to_aosp() 95 | throws Exception { 96 | mavenExecution() 97 | .withCliOptions(goalCliOption("validate-code-format"), "-Daosp=true") 98 | .execute() 99 | .assertLogText("is not correctly formatted"); 100 | 101 | mavenExecution() 102 | .withCliOptions(goalCliOption("format-code"), "-Daosp=true") 103 | .execute() 104 | .assertErrorFreeLog(); 105 | 106 | mavenExecution() 107 | .withCliOptions(goalCliOption("validate-code-format"), "-Daosp=true") 108 | .execute() 109 | .assertErrorFreeLog(); 110 | 111 | mavenExecution() 112 | .withCliOptions(goalCliOption("validate-code-format")) 113 | .execute() 114 | .assertLogText("is not correctly formatted"); 115 | } 116 | 117 | @Test 118 | public void GIVEN_bad_formatted_file_WHEN_committing_all_THEN_it_should_have_correct_format() 119 | throws Exception { 120 | mavenExecution() 121 | .withCliOptions(goalCliOption("validate-code-format")) 122 | .execute() 123 | .assertLogText("is not correctly formatted"); 124 | 125 | mavenExecution().execute("initialize").assertErrorFreeLog(); 126 | 127 | touch(badFormatJava); 128 | 129 | // When using CommitCommand 'all' option, JGit trigger the precommit hooks before adding the 130 | // files to staging. 131 | // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=577333 132 | // Until it is fixed, we perform 'all' option staging operation before calling commit manually. 133 | jGit().add().addFilepattern(".").setUpdate(true).call(); 134 | jGit() 135 | .commit() 136 | .setCommitter(gitIdentity()) 137 | .setAuthor(gitIdentity()) 138 | .setMessage("Trying to commit badly formatted file") 139 | .call(); 140 | 141 | mavenExecution() 142 | .withCliOptions(goalCliOption("validate-code-format")) 143 | .execute() 144 | .assertErrorFreeLog(); 145 | 146 | assertMatchExpected(badFormatJava); 147 | } 148 | 149 | private MavenExecution mavenExecution() { 150 | return buildMavenExecution(projectRoot().resolve(mavenModuleDirectory)); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /core/src/test/java/com/cosium/code/format/AbstractTest.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import io.takari.maven.testing.TestResources; 6 | import io.takari.maven.testing.executor.MavenExecution; 7 | import io.takari.maven.testing.executor.MavenRuntime; 8 | import io.takari.maven.testing.executor.MavenVersions; 9 | import io.takari.maven.testing.executor.junit.MavenJUnitTestRunner; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.nio.charset.StandardCharsets; 14 | import java.nio.file.FileVisitOption; 15 | import java.nio.file.Files; 16 | import java.nio.file.Path; 17 | import java.nio.file.Paths; 18 | import java.util.Comparator; 19 | import org.apache.commons.codec.digest.DigestUtils; 20 | import org.apache.commons.io.IOUtils; 21 | import org.eclipse.jgit.api.Git; 22 | import org.eclipse.jgit.lib.PersonIdent; 23 | import org.junit.After; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.runner.RunWith; 27 | 28 | /** 29 | * @author Réda Housni Alaoui 30 | */ 31 | @MavenVersions({"3.5.0"}) 32 | @RunWith(MavenJUnitTestRunner.class) 33 | public abstract class AbstractTest { 34 | private static final String GROUP_ID = "com.cosium.code"; 35 | private static final String ARTIFACT_ID = "git-code-format-maven-plugin"; 36 | private static final PersonIdent gitIdentity = 37 | new PersonIdent("John Doe", "john.doe@example.org"); 38 | @Rule public final TestResources resources; 39 | 40 | private final MavenRuntime maven; 41 | private final String projectRootDirectoryName; 42 | private Path projectDestination; 43 | private Path projectRoot; 44 | private Git jGit; 45 | 46 | public AbstractTest( 47 | MavenRuntime.MavenRuntimeBuilder mavenBuilder, String projectRootDirectoryName) 48 | throws Exception { 49 | this.resources = 50 | new TestResources( 51 | "src/test/projects", Files.createTempDirectory(ARTIFACT_ID + "-test").toString()); 52 | this.maven = mavenBuilder.withCliOptions("-B", "-U").build(); 53 | this.projectRootDirectoryName = projectRootDirectoryName; 54 | } 55 | 56 | @Before 57 | public final void before() throws Exception { 58 | projectRoot = resources.getBasedir(projectRootDirectoryName).toPath(); 59 | 60 | jGit = Git.init().setDirectory(projectRoot.toFile()).call(); 61 | jGit.add().addFilepattern(".").call(); 62 | jGit.commit() 63 | .setCommitter(gitIdentity) 64 | .setAuthor(gitIdentity) 65 | .setAll(true) 66 | .setMessage("First commit") 67 | .call(); 68 | 69 | projectDestination = 70 | Files.createDirectories(Paths.get("target/test-projects")) 71 | .resolve(projectRoot.getParent().relativize(projectRoot)); 72 | 73 | if (Files.notExists(projectDestination)) { 74 | return; 75 | } 76 | 77 | Files.walk(projectDestination, FileVisitOption.FOLLOW_LINKS) 78 | .sorted(Comparator.reverseOrder()) 79 | .forEach( 80 | path -> { 81 | try { 82 | Files.deleteIfExists(path); 83 | } catch (IOException e) { 84 | throw new RuntimeException(e); 85 | } 86 | }); 87 | Files.deleteIfExists(projectDestination); 88 | } 89 | 90 | protected final MavenExecution buildMavenExecution(Path mavenProjectPath) { 91 | return maven.forProject(mavenProjectPath.toFile()); 92 | } 93 | 94 | protected final Git jGit() { 95 | return jGit; 96 | } 97 | 98 | protected final PersonIdent gitIdentity() { 99 | return gitIdentity; 100 | } 101 | 102 | protected void touch(String sourceName) throws IOException { 103 | Path sourceFile = resolveRelativelyToProjectRoot(sourceName); 104 | String content; 105 | try (InputStream inputStream = Files.newInputStream(sourceFile)) { 106 | content = IOUtils.toString(inputStream, StandardCharsets.UTF_8) + "\n"; 107 | } 108 | try (OutputStream outputStream = Files.newOutputStream(sourceFile)) { 109 | IOUtils.write(content, outputStream, StandardCharsets.UTF_8); 110 | } 111 | } 112 | 113 | protected Path resolveRelativelyToProjectRoot(String sourceName) { 114 | return projectRoot.resolve(sourceName); 115 | } 116 | 117 | protected String sha1(String sourceName) { 118 | try (InputStream inputStream = 119 | Files.newInputStream(resolveRelativelyToProjectRoot(sourceName))) { 120 | return DigestUtils.shaHex(inputStream); 121 | } catch (IOException e) { 122 | throw new RuntimeException(e); 123 | } 124 | } 125 | 126 | protected void assertMatchExpected(String sourceName) { 127 | try (InputStream actual = Files.newInputStream(resolveRelativelyToProjectRoot(sourceName)); 128 | InputStream expected = 129 | getClass() 130 | .getResourceAsStream("/expected/" + projectRootDirectoryName + "/" + sourceName)) { 131 | assertThat(IOUtils.toString(actual, StandardCharsets.UTF_8)) 132 | .isEqualTo(IOUtils.toString(expected, StandardCharsets.UTF_8)); 133 | } catch (IOException e) { 134 | throw new RuntimeException(e); 135 | } 136 | } 137 | 138 | protected Path projectRoot() { 139 | return projectRoot; 140 | } 141 | 142 | protected String goalCliOption(String goal) { 143 | return GROUP_ID + ":" + ARTIFACT_ID + ":" + goal; 144 | } 145 | 146 | @After 147 | public final void moveFiles() throws Exception { 148 | Files.move(projectRoot, projectDestination); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /core/src/test/java/com/cosium/code/format/NonRootModuleTest.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import io.takari.maven.testing.executor.MavenRuntime; 4 | 5 | /** 6 | * @author Réda Housni Alaoui 7 | */ 8 | public class NonRootModuleTest extends AbstractMavenModuleTest { 9 | public NonRootModuleTest(MavenRuntime.MavenRuntimeBuilder mavenBuilder) throws Exception { 10 | super(mavenBuilder, "non-root-module", "module"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/src/test/java/com/cosium/code/format/SingleModuleTest.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import io.takari.maven.testing.executor.MavenRuntime; 4 | 5 | public class SingleModuleTest extends AbstractMavenModuleTest { 6 | 7 | public SingleModuleTest(MavenRuntime.MavenRuntimeBuilder mavenBuilder) throws Exception { 8 | super(mavenBuilder, "single-module", ""); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/src/test/java/com/cosium/code/format/TestingLog.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format; 2 | 3 | import org.apache.maven.plugin.logging.Log; 4 | 5 | /** 6 | * @author Réda Housni Alaoui 7 | */ 8 | public class TestingLog implements Log { 9 | 10 | @Override 11 | public boolean isDebugEnabled() { 12 | return false; 13 | } 14 | 15 | @Override 16 | public void debug(CharSequence content) {} 17 | 18 | @Override 19 | public void debug(CharSequence content, Throwable error) {} 20 | 21 | @Override 22 | public void debug(Throwable error) {} 23 | 24 | @Override 25 | public boolean isInfoEnabled() { 26 | return false; 27 | } 28 | 29 | @Override 30 | public void info(CharSequence content) {} 31 | 32 | @Override 33 | public void info(CharSequence content, Throwable error) {} 34 | 35 | @Override 36 | public void info(Throwable error) {} 37 | 38 | @Override 39 | public boolean isWarnEnabled() { 40 | return false; 41 | } 42 | 43 | @Override 44 | public void warn(CharSequence content) {} 45 | 46 | @Override 47 | public void warn(CharSequence content, Throwable error) {} 48 | 49 | @Override 50 | public void warn(Throwable error) {} 51 | 52 | @Override 53 | public boolean isErrorEnabled() { 54 | return false; 55 | } 56 | 57 | @Override 58 | public void error(CharSequence content) {} 59 | 60 | @Override 61 | public void error(CharSequence content, Throwable error) {} 62 | 63 | @Override 64 | public void error(Throwable error) {} 65 | } 66 | -------------------------------------------------------------------------------- /core/src/test/java/com/cosium/code/format/maven/MavenEnvironmentTest.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format.maven; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.cosium.code.format.TestingLog; 6 | import com.cosium.code.format.executable.CommandRunException; 7 | import com.cosium.code.format.executable.CommandRunner; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | /** 18 | * @author Réda Housni Alaoui 19 | */ 20 | public class MavenEnvironmentTest { 21 | 22 | private Map systemProperties; 23 | private TestingCommandRunner commandRunner; 24 | private MavenEnvironment tested; 25 | 26 | @Before 27 | public void before() { 28 | systemProperties = new HashMap<>(); 29 | commandRunner = new TestingCommandRunner(); 30 | tested = new MavenEnvironment(TestingLog::new, systemProperties::get, commandRunner); 31 | } 32 | 33 | @Test 34 | public void testMavenHomeExecutable() { 35 | systemProperties.put("maven.home", "/opt/maven"); 36 | Path expectedPath = Paths.get("/opt/maven/bin/mvn"); 37 | commandRunner.validExecutables.add(expectedPath.toString()); 38 | Path path = tested.getMavenExecutable(false); 39 | assertThat(path).isEqualTo(expectedPath); 40 | } 41 | 42 | @Test 43 | public void testMavenHomeDebugExecutable() { 44 | systemProperties.put("maven.home", "/opt/maven"); 45 | Path expectedPath = Paths.get("/opt/maven/bin/mvnDebug"); 46 | commandRunner.validExecutables.add(expectedPath.toString()); 47 | Path path = tested.getMavenExecutable(true); 48 | assertThat(path).isEqualTo(expectedPath); 49 | } 50 | 51 | @Test 52 | public void testMavenPathExecutableFallback() { 53 | systemProperties.put("maven.home", "/opt/maven"); 54 | commandRunner.validExecutables.add("mvn"); 55 | Path path = tested.getMavenExecutable(false); 56 | assertThat(path).isEqualTo(Paths.get("mvn")); 57 | } 58 | 59 | @Test 60 | public void testMavenPathDebugExecutableFallback() { 61 | systemProperties.put("maven.home", "/opt/maven"); 62 | commandRunner.validExecutables.add("mvnDebug"); 63 | Path path = tested.getMavenExecutable(true); 64 | assertThat(path).isEqualTo(Paths.get("mvnDebug")); 65 | } 66 | 67 | private static class TestingCommandRunner implements CommandRunner { 68 | 69 | final Set validExecutables = new HashSet<>(); 70 | 71 | @Override 72 | public String run(Path workingDir, Map environment, String... command) { 73 | if (validExecutables.contains(command[0])) { 74 | return null; 75 | } 76 | throw new CommandRunException(1, ""); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /core/src/test/projects/non-root-module/module/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | ### JetBrains template 26 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 27 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 28 | 29 | # User-specific stuff: 30 | .idea 31 | 32 | # CMake 33 | cmake-build-debug/ 34 | 35 | ## File-based project format: 36 | *.iws 37 | 38 | ## Plugin-specific files: 39 | 40 | # IntelliJ 41 | out/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Crashlytics plugin (for Android Studio and IntelliJ) 47 | com_crashlytics_export_strings.xml 48 | crashlytics.properties 49 | crashlytics-build.properties 50 | fabric.properties 51 | ### Maven template 52 | pom.xml.tag 53 | pom.xml.releaseBackup 54 | pom.xml.versionsBackup 55 | pom.xml.next 56 | release.properties 57 | dependency-reduced-pom.xml 58 | buildNumber.properties 59 | .mvn/timing.properties 60 | 61 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 62 | !/.mvn/wrapper/maven-wrapper.jar 63 | *.iml 64 | 65 | log.txt 66 | -------------------------------------------------------------------------------- /core/src/test/projects/non-root-module/module/.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.takari.m2e.workspace 5 | org.eclipse.m2e.workspace.cli 6 | 0.4.0 7 | 8 | 9 | -------------------------------------------------------------------------------- /core/src/test/projects/non-root-module/module/.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 2 | -------------------------------------------------------------------------------- /core/src/test/projects/non-root-module/module/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.cosium.code.format.test 6 | module 7 | 1.00-SNAPSHOT 8 | 9 | 10 | false 11 | false 12 | false 13 | false 14 | 15 | 16 | 17 | 18 | 19 | com.cosium.code 20 | git-code-format-maven-plugin 21 | ${it-project.version} 22 | 23 | 24 | 25 | install-hooks 26 | 27 | 28 | 29 | 30 | 31 | com.cosium.code 32 | google-java-format 33 | ${it-project.version} 34 | 35 | 36 | 37 | 1C 38 | 39 | -X 40 | 41 | 42 | maven.repo.local 43 | it-project.version 44 | m2e.workspace.state 45 | workspaceResolver 46 | workspaceStateProperties 47 | maven.ext.class.path 48 | classworlds.conf 49 | 50 | 51 | ${aosp} 52 | ${fixImportsOnly} 53 | ${skipSortingImports} 54 | ${skipRemovingUnusedImports} 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /core/src/test/projects/non-root-module/module/src/main/java/BadFormat.java: -------------------------------------------------------------------------------- 1 | public class BadFormat { 2 | 3 | public BadFormat(String a, String b, String c, String d, String e, String f){ 4 | 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /core/src/test/projects/non-root-module/module/target/generated-sources/GeneratedBadFormat.java: -------------------------------------------------------------------------------- 1 | public class GeneratedBadFormat { 2 | 3 | public GeneratedBadFormat(String a, String b, String c, String d, String e, String f) {} 4 | } 5 | -------------------------------------------------------------------------------- /core/src/test/projects/single-module/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | ### JetBrains template 26 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 27 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 28 | 29 | # User-specific stuff: 30 | .idea 31 | 32 | # CMake 33 | cmake-build-debug/ 34 | 35 | ## File-based project format: 36 | *.iws 37 | 38 | ## Plugin-specific files: 39 | 40 | # IntelliJ 41 | out/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Crashlytics plugin (for Android Studio and IntelliJ) 47 | com_crashlytics_export_strings.xml 48 | crashlytics.properties 49 | crashlytics-build.properties 50 | fabric.properties 51 | ### Maven template 52 | pom.xml.tag 53 | pom.xml.releaseBackup 54 | pom.xml.versionsBackup 55 | pom.xml.next 56 | release.properties 57 | dependency-reduced-pom.xml 58 | buildNumber.properties 59 | .mvn/timing.properties 60 | 61 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 62 | !/.mvn/wrapper/maven-wrapper.jar 63 | *.iml 64 | 65 | log.txt 66 | -------------------------------------------------------------------------------- /core/src/test/projects/single-module/.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.takari.m2e.workspace 5 | org.eclipse.m2e.workspace.cli 6 | 0.4.0 7 | 8 | 9 | -------------------------------------------------------------------------------- /core/src/test/projects/single-module/.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 2 | -------------------------------------------------------------------------------- /core/src/test/projects/single-module/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.cosium.code.format.test 6 | single-module 7 | 1.00-SNAPSHOT 8 | 9 | 10 | false 11 | false 12 | false 13 | false 14 | 15 | 16 | 17 | 18 | 19 | com.cosium.code 20 | git-code-format-maven-plugin 21 | ${it-project.version} 22 | 23 | 24 | 25 | install-hooks 26 | 27 | 28 | 29 | 30 | 31 | com.cosium.code 32 | google-java-format 33 | ${it-project.version} 34 | 35 | 36 | 37 | 38 | -X 39 | 40 | 41 | maven.repo.local 42 | it-project.version 43 | m2e.workspace.state 44 | workspaceResolver 45 | workspaceStateProperties 46 | maven.ext.class.path 47 | classworlds.conf 48 | 49 | 50 | ${aosp} 51 | ${fixImportsOnly} 52 | ${skipSortingImports} 53 | ${skipRemovingUnusedImports} 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /core/src/test/projects/single-module/src/main/java/BadFormat.java: -------------------------------------------------------------------------------- 1 | public class BadFormat { 2 | 3 | public BadFormat(String a, String b, String c, String d, String e, String f){ 4 | 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /core/src/test/projects/single-module/target/generated-sources/GeneratedBadFormat.java: -------------------------------------------------------------------------------- 1 | public class GeneratedBadFormat { 2 | 3 | public GeneratedBadFormat(String a, String b, String c, String d, String e, String f) {} 4 | } 5 | -------------------------------------------------------------------------------- /core/src/test/resources/expected/non-root-module/module/src/main/java/BadFormat.java: -------------------------------------------------------------------------------- 1 | public class BadFormat { 2 | 3 | public BadFormat(String a, String b, String c, String d, String e, String f) {} 4 | } 5 | -------------------------------------------------------------------------------- /core/src/test/resources/expected/single-module/src/main/java/BadFormat.java: -------------------------------------------------------------------------------- 1 | public class BadFormat { 2 | 3 | public BadFormat(String a, String b, String c, String d, String e, String f) {} 4 | } 5 | -------------------------------------------------------------------------------- /core/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /google-java-format/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /google-java-format/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | com.cosium.code 6 | git-code-format-maven-plugin-parent 7 | 5.4-SNAPSHOT 8 | 9 | 10 | google-java-format 11 | jar 12 | 13 | Google Java Format for Git Code Format Maven Plugin 14 | 15 | 16 | 17 | ${project.groupId} 18 | git-code-format-maven-plugin-spi 19 | ${project.version} 20 | 21 | 22 | org.apache.maven.plugin-tools 23 | maven-plugin-annotations 24 | provided 25 | 26 | 27 | com.google.googlejavaformat 28 | google-java-format 29 | 30 | 31 | commons-io 32 | commons-io 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /google-java-format/src/main/java/com/cosium/code/format_gjf/GoogleJavaFormatException.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_gjf; 2 | 3 | /** 4 | * @author Réda Housni Alaoui 5 | */ 6 | class GoogleJavaFormatException extends RuntimeException { 7 | 8 | public GoogleJavaFormatException(Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /google-java-format/src/main/java/com/cosium/code/format_gjf/GoogleJavaFormatter.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_gjf; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import com.cosium.code.format_spi.CodeFormatter; 6 | import com.cosium.code.format_spi.FileExtension; 7 | import com.cosium.code.format_spi.LineRanges; 8 | import com.google.common.collect.RangeSet; 9 | import com.google.googlejavaformat.java.Formatter; 10 | import com.google.googlejavaformat.java.FormatterException; 11 | import com.google.googlejavaformat.java.ImportOrderer; 12 | import com.google.googlejavaformat.java.RemoveUnusedImports; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.OutputStream; 16 | import org.apache.commons.io.IOUtils; 17 | 18 | /** 19 | * @author Réda Housni Alaoui 20 | */ 21 | class GoogleJavaFormatter implements CodeFormatter { 22 | 23 | private final GoogleJavaFormatterOptions options; 24 | private final Formatter formatter; 25 | private final String sourceEncoding; 26 | 27 | public GoogleJavaFormatter(GoogleJavaFormatterOptions options, String sourceEncoding) { 28 | this.options = requireNonNull(options); 29 | this.formatter = new Formatter(options.javaFormatterOptions()); 30 | this.sourceEncoding = sourceEncoding; 31 | } 32 | 33 | @Override 34 | public FileExtension fileExtension() { 35 | return FileExtension.of("java"); 36 | } 37 | 38 | @Override 39 | public void format(InputStream content, LineRanges lineRanges, OutputStream formattedContent) { 40 | final String formattedContentToWrite; 41 | try { 42 | String unformattedContent = IOUtils.toString(content, sourceEncoding); 43 | formattedContentToWrite = doFormat(unformattedContent, lineRanges); 44 | } catch (IOException | FormatterException e) { 45 | throw new GoogleJavaFormatException(e); 46 | } 47 | 48 | try { 49 | IOUtils.write(formattedContentToWrite, formattedContent, sourceEncoding); 50 | } catch (IOException e) { 51 | throw new GoogleJavaFormatException(e); 52 | } 53 | } 54 | 55 | @Override 56 | public boolean validate(InputStream content) { 57 | try { 58 | String unformattedContent = IOUtils.toString(content, sourceEncoding); 59 | String formattedContent = doFormat(unformattedContent, LineRanges.all()); 60 | return unformattedContent.equals(formattedContent); 61 | } catch (IOException | FormatterException e) { 62 | throw new GoogleJavaFormatException(e); 63 | } 64 | } 65 | 66 | private String doFormat(String unformattedContent, LineRanges lineRanges) 67 | throws FormatterException { 68 | if (options.isFixImportsOnly()) { 69 | if (!lineRanges.isAll()) { 70 | return unformattedContent; 71 | } 72 | return fixImports(unformattedContent); 73 | } 74 | if (lineRanges.isAll()) { 75 | return fixImports(formatter.formatSource(unformattedContent)); 76 | } 77 | 78 | RangeSet charRangeSet = 79 | Formatter.lineRangesToCharRanges(unformattedContent, lineRanges.rangeSet()); 80 | return formatter.formatSource(unformattedContent, charRangeSet.asRanges()); 81 | } 82 | 83 | private String fixImports(final String unformattedContent) throws FormatterException { 84 | String formattedContent = unformattedContent; 85 | if (!options.isSkipRemovingUnusedImports()) { 86 | formattedContent = RemoveUnusedImports.removeUnusedImports(formattedContent); 87 | } 88 | if (!options.isSkipSortingImports()) { 89 | formattedContent = ImportOrderer.reorderImports(formattedContent); 90 | } 91 | return formattedContent; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /google-java-format/src/main/java/com/cosium/code/format_gjf/GoogleJavaFormatterFactory.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_gjf; 2 | 3 | import com.cosium.code.format_spi.CodeFormatter; 4 | import com.cosium.code.format_spi.CodeFormatterConfiguration; 5 | import com.cosium.code.format_spi.CodeFormatterFactory; 6 | 7 | /** 8 | * @author Réda Housni Alaoui 9 | */ 10 | public class GoogleJavaFormatterFactory implements CodeFormatterFactory { 11 | @Override 12 | public String configurationId() { 13 | return "googleJavaFormat"; 14 | } 15 | 16 | @Override 17 | public CodeFormatter build(CodeFormatterConfiguration configuration, String sourceEncoding) { 18 | 19 | return new GoogleJavaFormatter(new GoogleJavaFormatterOptions(configuration), sourceEncoding); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /google-java-format/src/main/java/com/cosium/code/format_gjf/GoogleJavaFormatterOptions.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_gjf; 2 | 3 | import static com.google.googlejavaformat.java.JavaFormatterOptions.Style.AOSP; 4 | import static com.google.googlejavaformat.java.JavaFormatterOptions.Style.GOOGLE; 5 | 6 | import com.cosium.code.format_spi.CodeFormatterConfiguration; 7 | import com.google.googlejavaformat.java.JavaFormatterOptions; 8 | 9 | /** 10 | * @author Réda Housni Alaoui 11 | */ 12 | class GoogleJavaFormatterOptions { 13 | 14 | private final JavaFormatterOptions.Style style; 15 | private final boolean formatJavadoc; 16 | private final boolean fixImportsOnly; 17 | private final boolean skipSortingImports; 18 | private final boolean skipRemovingUnusedImports; 19 | 20 | public GoogleJavaFormatterOptions(CodeFormatterConfiguration configuration) { 21 | boolean aosp = configuration.getValue("aosp").map(Boolean::parseBoolean).orElse(false); 22 | if (aosp) { 23 | style = AOSP; 24 | } else { 25 | style = GOOGLE; 26 | } 27 | formatJavadoc = configuration.getValue("formatJavadoc").map(Boolean::parseBoolean).orElse(true); 28 | fixImportsOnly = 29 | configuration.getValue("fixImportsOnly").map(Boolean::parseBoolean).orElse(false); 30 | skipSortingImports = 31 | configuration.getValue("skipSortingImports").map(Boolean::parseBoolean).orElse(false); 32 | skipRemovingUnusedImports = 33 | configuration 34 | .getValue("skipRemovingUnusedImports") 35 | .map(Boolean::parseBoolean) 36 | .orElse(false); 37 | } 38 | 39 | public JavaFormatterOptions javaFormatterOptions() { 40 | return JavaFormatterOptions.builder().style(style).formatJavadoc(formatJavadoc).build(); 41 | } 42 | 43 | public boolean isFixImportsOnly() { 44 | return fixImportsOnly; 45 | } 46 | 47 | public boolean isSkipSortingImports() { 48 | return skipSortingImports; 49 | } 50 | 51 | public boolean isSkipRemovingUnusedImports() { 52 | return skipRemovingUnusedImports; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "GoogleJavaFormatterOptions{" 58 | + "style=" 59 | + style 60 | + ", fixImportsOnly=" 61 | + fixImportsOnly 62 | + ", skipSortingImports=" 63 | + skipSortingImports 64 | + ", skipRemovingUnusedImports=" 65 | + skipRemovingUnusedImports 66 | + '}'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /google-java-format/src/main/resources/META-INF/services/com.cosium.code.format_spi.CodeFormatterFactory: -------------------------------------------------------------------------------- 1 | com.cosium.code.format_gjf.GoogleJavaFormatterFactory 2 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.1.1 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "`uname`" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=`java-config --jre-home` 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && 89 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="`which javac`" 94 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=`which readlink` 97 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 98 | if $darwin ; then 99 | javaHome="`dirname \"$javaExecutable\"`" 100 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 101 | else 102 | javaExecutable="`readlink -f \"$javaExecutable\"`" 103 | fi 104 | javaHome="`dirname \"$javaExecutable\"`" 105 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="`\\unset -f command; \\command -v java`" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=`cd "$wdir/.."; pwd` 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir"; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | echo "$(tr -s '\n' ' ' < "$1")" 164 | fi 165 | } 166 | 167 | BASE_DIR=$(find_maven_basedir "$(dirname $0)") 168 | if [ -z "$BASE_DIR" ]; then 169 | exit 1; 170 | fi 171 | 172 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | echo $MAVEN_PROJECTBASEDIR 175 | fi 176 | 177 | ########################################################################################## 178 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 179 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 180 | ########################################################################################## 181 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 182 | if [ "$MVNW_VERBOSE" = true ]; then 183 | echo "Found .mvn/wrapper/maven-wrapper.jar" 184 | fi 185 | else 186 | if [ "$MVNW_VERBOSE" = true ]; then 187 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 188 | fi 189 | if [ -n "$MVNW_REPOURL" ]; then 190 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 191 | else 192 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 193 | fi 194 | while IFS="=" read key value; do 195 | case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; 196 | esac 197 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 198 | if [ "$MVNW_VERBOSE" = true ]; then 199 | echo "Downloading from: $wrapperUrl" 200 | fi 201 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 202 | if $cygwin; then 203 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 204 | fi 205 | 206 | if command -v wget > /dev/null; then 207 | QUIET="--quiet" 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found wget ... using wget" 210 | QUIET="" 211 | fi 212 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 213 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" 214 | else 215 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" 216 | fi 217 | [ $? -eq 0 ] || rm -f "$wrapperJarPath" 218 | elif command -v curl > /dev/null; then 219 | QUIET="--silent" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Found curl ... using curl" 222 | QUIET="" 223 | fi 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L 228 | fi 229 | [ $? -eq 0 ] || rm -f "$wrapperJarPath" 230 | else 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Falling back to using Java to download" 233 | fi 234 | javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 235 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" 236 | # For Cygwin, switch paths to Windows format before running javac 237 | if $cygwin; then 238 | javaSource=`cygpath --path --windows "$javaSource"` 239 | javaClass=`cygpath --path --windows "$javaClass"` 240 | fi 241 | if [ -e "$javaSource" ]; then 242 | if [ ! -e "$javaClass" ]; then 243 | if [ "$MVNW_VERBOSE" = true ]; then 244 | echo " - Compiling MavenWrapperDownloader.java ..." 245 | fi 246 | # Compiling the Java class 247 | ("$JAVA_HOME/bin/javac" "$javaSource") 248 | fi 249 | if [ -e "$javaClass" ]; then 250 | # Running the downloader 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo " - Running MavenWrapperDownloader.java ..." 253 | fi 254 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 255 | fi 256 | fi 257 | fi 258 | fi 259 | ########################################################################################## 260 | # End of extension 261 | ########################################################################################## 262 | 263 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 264 | 265 | # For Cygwin, switch paths to Windows format before running java 266 | if $cygwin; then 267 | [ -n "$JAVA_HOME" ] && 268 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 269 | [ -n "$CLASSPATH" ] && 270 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 271 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 272 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 273 | fi 274 | 275 | # Provide a "standardized" way to retrieve the CLI args that will 276 | # work with both Windows and non-Windows executions. 277 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 278 | export MAVEN_CMD_LINE_ARGS 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | $MAVEN_DEBUG_OPTS \ 285 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 286 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 287 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 288 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.1.1 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM Provide a "standardized" way to retrieve the CLI args that will 157 | @REM work with both Windows and non-Windows executions. 158 | set MAVEN_CMD_LINE_ARGS=%* 159 | 160 | %MAVEN_JAVA_EXE% ^ 161 | %JVM_CONFIG_MAVEN_PROPS% ^ 162 | %MAVEN_OPTS% ^ 163 | %MAVEN_DEBUG_OPTS% ^ 164 | -classpath %WRAPPER_JAR% ^ 165 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 166 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 167 | if ERRORLEVEL 1 goto error 168 | goto end 169 | 170 | :error 171 | set ERROR_CODE=1 172 | 173 | :end 174 | @endlocal & set ERROR_CODE=%ERROR_CODE% 175 | 176 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 177 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 178 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 179 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 180 | :skipRcPost 181 | 182 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 183 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 184 | 185 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 186 | 187 | cmd /C exit /B %ERROR_CODE% 188 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.cosium.code 5 | git-code-format-maven-plugin-parent 6 | 5.4-SNAPSHOT 7 | pom 8 | 9 | Git Code Format Maven Plugin Parent 10 | A maven plugin that automatically deploys code formatters 11 | as pre-commit git hook 12 | 13 | https://github.com/Cosium/git-code-format-maven-plugin 14 | 15 | 16 | UTF-8 17 | 1.8 18 | 2.19.0 19 | 3.17.0 20 | 1.5.0 21 | 5.13.1.202206130422-r 22 | 0.8.13 23 | 3.1.1 24 | 1.27.0 25 | 26 | 27 | 28 | spi 29 | core 30 | google-java-format 31 | 32 | 33 | 34 | 35 | 36 | com.google.guava 37 | guava 38 | 33.4.8-jre 39 | 40 | 41 | commons-io 42 | commons-io 43 | ${org.apache.commons.io.version} 44 | 45 | 46 | org.apache.commons 47 | commons-lang3 48 | ${org.apache.commons.lang.version} 49 | 50 | 51 | org.apache.commons 52 | commons-exec 53 | ${org.apache.commons.exec.version} 54 | 55 | 56 | com.google.googlejavaformat 57 | google-java-format 58 | ${google-java-format.version} 59 | 60 | 61 | org.eclipse.jgit 62 | org.eclipse.jgit 63 | ${jgit.version} 64 | 65 | 66 | org.apache.maven 67 | maven-core 68 | 3.3.9 69 | 70 | 71 | org.apache.maven 72 | maven-plugin-api 73 | 3.3.9 74 | 75 | 76 | org.apache.maven.plugin-tools 77 | maven-plugin-annotations 78 | 3.15.1 79 | 80 | 81 | org.codehaus.plexus 82 | plexus-utils 83 | 3.2.1 84 | 85 | 86 | org.eclipse.sisu 87 | org.eclipse.sisu.plexus 88 | 0.3.5 89 | 90 | 91 | junit 92 | junit 93 | 4.13.2 94 | 95 | 96 | org.assertj 97 | assertj-core 98 | 3.27.3 99 | 100 | 101 | io.takari.maven.plugins 102 | takari-plugin-testing 103 | ${takari-plugin-testing.version} 104 | 105 | 106 | io.takari.maven.plugins 107 | takari-plugin-integration-testing 108 | ${takari-plugin-testing.version} 109 | pom 110 | 111 | 112 | ch.qos.logback 113 | logback-classic 114 | 1.5.18 115 | 116 | 117 | commons-codec 118 | commons-codec 119 | 1.18.0 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-compiler-plugin 129 | 3.14.0 130 | 131 | ${source.level} 132 | ${source.level} 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-source-plugin 138 | 3.3.1 139 | 140 | 141 | attach-sources 142 | 143 | jar-no-fork 144 | 145 | 146 | 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-javadoc-plugin 151 | 3.11.2 152 | 153 | 154 | attach-javadocs 155 | 156 | jar 157 | 158 | 159 | 160 | 161 | false 162 | -Xdoclint:none 163 | 164 | 165 | 166 | org.apache.maven.plugins 167 | maven-deploy-plugin 168 | 3.1.4 169 | 170 | 171 | org.apache.maven.plugins 172 | maven-release-plugin 173 | 2.5.3 174 | 175 | false 176 | true 177 | true 178 | true 179 | release 180 | deploy 181 | @{project.version} 182 | 183 | 184 | 185 | org.jacoco 186 | jacoco-maven-plugin 187 | ${jacoco.version} 188 | 189 | 190 | 191 | prepare-agent 192 | 193 | 194 | 195 | report 196 | test 197 | 198 | report 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | release-sign-artifacts 208 | 209 | 210 | performRelease 211 | true 212 | 213 | 214 | 215 | 216 | 217 | org.apache.maven.plugins 218 | maven-gpg-plugin 219 | 1.6 220 | 221 | 222 | sign-artifacts 223 | verify 224 | 225 | sign 226 | 227 | 228 | 229 | 230 | 231 | org.sonatype.plugins 232 | nexus-staging-maven-plugin 233 | 1.7.0 234 | true 235 | 236 | ossrh 237 | https://oss.sonatype.org/ 238 | true 239 | true 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | ossrh 250 | https://oss.sonatype.org/content/repositories/snapshots 251 | 252 | 253 | ossrh 254 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 255 | 256 | 257 | 258 | 259 | scm:git:https://github.com/Cosium/git-code-format-maven-plugin 260 | scm:git:https://github.com/Cosium/git-code-format-maven-plugin 261 | https://github.com/Cosium/git-code-format-maven-plugin 262 | HEAD 263 | 264 | 265 | 266 | Cosium 267 | http://www.cosium.com 268 | 269 | 270 | 271 | 272 | reda-alaoui 273 | Réda Housni Alaoui 274 | reda-alaoui@hey.com 275 | https://github.com/reda-alaoui 276 | 277 | 278 | 279 | 280 | 281 | MIT License 282 | http://www.opensource.org/licenses/mit-license.php 283 | repo 284 | 285 | 286 | 287 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ./mvnw --batch-mode clean release:prepare release:perform && git push && git push --tags 3 | -------------------------------------------------------------------------------- /spi/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /spi/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | com.cosium.code 6 | git-code-format-maven-plugin-parent 7 | 5.4-SNAPSHOT 8 | 9 | 10 | git-code-format-maven-plugin-spi 11 | jar 12 | 13 | Git Code Format Maven Plugin SPI 14 | 15 | 16 | 17 | com.google.guava 18 | guava 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /spi/src/main/java/com/cosium/code/format_spi/CodeFormatter.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_spi; 2 | 3 | import java.io.InputStream; 4 | import java.io.OutputStream; 5 | 6 | /** 7 | * @author Réda Housni Alaoui 8 | */ 9 | public interface CodeFormatter { 10 | 11 | /** 12 | * @return The file extension supported by this formatter. 13 | */ 14 | FileExtension fileExtension(); 15 | 16 | /** 17 | * Formats a content. 18 | * 19 | *

The formatter SHOULD strive to format only lines part of the provided line ranges. 20 | * 21 | * @param content The content to format. 22 | * @param lineRanges The line ranges to format. 23 | * @param formattedContent The {@link OutputStream} to write the formatted content to. 24 | */ 25 | void format(InputStream content, LineRanges lineRanges, OutputStream formattedContent); 26 | 27 | /** 28 | * @return true if the provided content is correctly formatted. false otherwise. 29 | */ 30 | boolean validate(InputStream content); 31 | } 32 | -------------------------------------------------------------------------------- /spi/src/main/java/com/cosium/code/format_spi/CodeFormatterConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_spi; 2 | 3 | import java.util.Optional; 4 | 5 | /** 6 | * @author Réda Housni Alaoui 7 | */ 8 | public interface CodeFormatterConfiguration { 9 | 10 | Optional getValue(String key); 11 | } 12 | -------------------------------------------------------------------------------- /spi/src/main/java/com/cosium/code/format_spi/CodeFormatterFactory.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_spi; 2 | 3 | /** 4 | * @author Réda Housni Alaoui 5 | */ 6 | public interface CodeFormatterFactory { 7 | 8 | /** 9 | * The prefix that will be used by the plugin user to configure formatters created by this 10 | * factory. 11 | * 12 | *

e.g. If the prefix was 'fooBar', a configuration attribute named 'baz' would have to be 13 | * declared as 'fooBar.baz' in the plugin configuration. 14 | */ 15 | String configurationId(); 16 | 17 | /** 18 | * @param configuration This code formatter factory configuration 19 | * @param sourceEncoding The files source encoding 20 | * @return The formatter to use 21 | */ 22 | CodeFormatter build(CodeFormatterConfiguration configuration, String sourceEncoding); 23 | } 24 | -------------------------------------------------------------------------------- /spi/src/main/java/com/cosium/code/format_spi/FileExtension.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_spi; 2 | 3 | import com.google.common.io.Files; 4 | import java.nio.file.Path; 5 | import java.util.Objects; 6 | 7 | /** 8 | * @author Réda Housni Alaoui 9 | */ 10 | public final class FileExtension { 11 | 12 | private final String value; 13 | 14 | private FileExtension(String value) { 15 | this.value = value; 16 | } 17 | 18 | public static FileExtension parse(Path path) { 19 | return new FileExtension(Files.getFileExtension(path.getFileName().toString())); 20 | } 21 | 22 | public static FileExtension parse(String path) { 23 | return new FileExtension(Files.getFileExtension(path)); 24 | } 25 | 26 | public static FileExtension of(String value) { 27 | return new FileExtension(value); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return value; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) { 38 | return true; 39 | } 40 | if (o == null || getClass() != o.getClass()) { 41 | return false; 42 | } 43 | 44 | FileExtension that = (FileExtension) o; 45 | 46 | return Objects.equals(value, that.value); 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return value != null ? value.hashCode() : 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spi/src/main/java/com/cosium/code/format_spi/LineRanges.java: -------------------------------------------------------------------------------- 1 | package com.cosium.code.format_spi; 2 | 3 | import com.google.common.collect.Range; 4 | import com.google.common.collect.RangeSet; 5 | import com.google.common.collect.TreeRangeSet; 6 | import java.util.Collections; 7 | import java.util.Set; 8 | 9 | /** 10 | * @author Réda Housni Alaoui 11 | */ 12 | public final class LineRanges { 13 | 14 | private static final LineRanges ALL = 15 | new LineRanges(TreeRangeSet.create(Collections.singleton(Range.all()))); 16 | 17 | private final RangeSet rangeSet; 18 | 19 | private LineRanges(RangeSet rangeSet) { 20 | if (rangeSet.isEmpty()) { 21 | throw new IllegalArgumentException("There must be at least one range"); 22 | } 23 | 24 | this.rangeSet = rangeSet; 25 | } 26 | 27 | public static LineRanges all() { 28 | return ALL; 29 | } 30 | 31 | public static LineRanges of(Set> ranges) { 32 | return new LineRanges(TreeRangeSet.create(ranges)); 33 | } 34 | 35 | public static LineRanges singleton(Range range) { 36 | return new LineRanges(TreeRangeSet.create(Collections.singleton(range))); 37 | } 38 | 39 | public boolean isAll() { 40 | return this == ALL; 41 | } 42 | 43 | public RangeSet rangeSet() { 44 | return rangeSet; 45 | } 46 | 47 | public static LineRanges concat(LineRanges lineRanges1, LineRanges lineRanges2) { 48 | if (lineRanges1.isAll()) { 49 | return lineRanges1; 50 | } 51 | if (lineRanges2.isAll()) { 52 | return lineRanges2; 53 | } 54 | 55 | RangeSet newRangeSet = TreeRangeSet.create(); 56 | newRangeSet.addAll(lineRanges1.rangeSet); 57 | newRangeSet.addAll(lineRanges2.rangeSet); 58 | 59 | return new LineRanges(newRangeSet); 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return rangeSet.toString(); 65 | } 66 | } 67 | --------------------------------------------------------------------------------