├── .gitignore ├── .travis.yml ├── LICENSE ├── NOTICE ├── README.md ├── api ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ ├── osjava │ │ │ └── jardiff │ │ │ │ ├── AbstractDiffHandler.java │ │ │ │ ├── AbstractInfo.java │ │ │ │ ├── ClassInfo.java │ │ │ │ ├── ClassInfoVisitor.java │ │ │ │ ├── DOMDiffHandler.java │ │ │ │ ├── DiffCriteria.java │ │ │ │ ├── DiffException.java │ │ │ │ ├── DiffHandler.java │ │ │ │ ├── FieldInfo.java │ │ │ │ ├── JarDiff.java │ │ │ │ ├── MethodInfo.java │ │ │ │ ├── PublicDiffCriteria.java │ │ │ │ ├── SimpleDiffCriteria.java │ │ │ │ ├── StreamDiffHandler.java │ │ │ │ └── Tools.java │ │ │ └── semver │ │ │ ├── Comparer.java │ │ │ ├── Delta.java │ │ │ ├── Dumper.java │ │ │ ├── Main.java │ │ │ ├── Version.java │ │ │ └── jardiff │ │ │ └── DifferenceAccumulatingHandler.java │ └── resources │ │ └── org │ │ └── semver │ │ └── Messages_de.properties │ └── test │ └── java │ └── org │ ├── osjava │ └── jardiff │ │ └── ToolsTest.java │ └── semver │ ├── DeltaTest.java │ ├── VersionTest.java │ └── jardiff │ ├── ClassInheritanceTest.java │ ├── DeprecateDetectionTest.java │ └── DifferenceAccumulatingHandlerTest.java ├── enforcer-rule ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── semver │ └── enforcer │ ├── AbstractEnforcerRule.java │ ├── RequireBackwardCompatibility.java │ └── RequireSemanticVersioningConformance.java ├── example └── pom.xml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .classpath 3 | .project 4 | .settings 5 | .idea/ 6 | *.iml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2012-2014 Julien Eluard and contributors 2 | This project includes software developed by Julien Eluard: https://github.com/jeluard/ 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _This library is in dormant state and won't add any new feature. You might want to consider [japicmd](https://github.com/siom79/japicmp) instead._ 2 | 3 | ![CI status](https://secure.travis-ci.org/jeluard/semantic-versioning.png) 4 | 5 | Version number changes implications are not always clearly identified. Can I be sure this new minor version didn't break the public API? 6 | As a library writer, how to continuously validate I don't break binary compatibility? 7 | 8 | `semantic-versioning` is a Java library allowing to validate (using bytecode inspection) if library version numbers follows Semantic Versioning principles as defined by [Semantic Versioning](http://semver.org). 9 | It can check JAR files or classes to identify changes between versions and validate if the new version number is correct according to semver. 10 | 11 | `semantic-versioning` is available as an [API](#api), a [command line tool](#cli) and a [maven enforcer](http://maven.apache.org/enforcer/maven-enforcer-plugin/) [rule](#rule). 12 | 13 | 14 | ## API overview 15 | 16 | Semantic Versioning also provides an API for programmatically validating your project's version number. 17 | 18 | ```java 19 | final File previousJar = ...; 20 | final File currentJar = ...; 21 | 22 | final Comparer comparer = new Comparer(previousJar, currentJar, ..., ...); 23 | final Delta delta = comparer.diff(); 24 | 25 | final String compatibilityType = ...; 26 | 27 | //Validates that computed and provided compatibility type are compatible. 28 | final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.valueOf(compatibilityType); 29 | final Delta.CompatibilityType detectedCompatibilityType = delta.computeCompatibilityType(); 30 | if (detectedCompatibilityType.compareTo(expectedCompatibilityType) > 0) { 31 | //Not compatible. 32 | } 33 | 34 | //Provide version number for previous and current Jar files. 35 | final Version previous = Version.parse(...); 36 | final Version current = Version.parse(...); 37 | 38 | //Validates that current version number is valid based on semantic versioning principles. 39 | final boolean compatible = delta.validate(previous, current); 40 | ``` 41 | 42 | 43 | ## CLI 44 | 45 | This simple command line tool looks at Java JAR files and determine API changes. 46 | 47 | ### Built-in help 48 | 49 | ``` 50 | % java -jar semver.jar --help 51 | Semantic Version validator. 52 | 53 | Usage: semver [options] 54 | 55 | Options: 56 | --base-jar JAR The base jar. 57 | --base-version VERSION Version of the base jar (given with --base-jar). 58 | --check,-c Check the compatibility of two jars. 59 | --diff,-d Show the differences between two jars. 60 | --excludes EXCLUDE;... Semicolon separated list of full qualified class names 61 | or partly qualified class names with wild cards 62 | to be excluded. 63 | --help,-h Show this help and exit. 64 | --includes INCLUDE;... Semicolon separated list of full qualified class names 65 | or partly qualified class names with wild cards 66 | to be included. 67 | --infer,-i Infer the version of the new jar based on the previous 68 | jar. 69 | --new-jar JAR The new jar. 70 | --new-version VERSION Version of the new jar (given with --new-jar). 71 | --validate,-v Validate that the versions of two jars fulfil the 72 | semver specification. 73 | ``` 74 | 75 | ### Diff 76 | 77 | Dump all changes between two JARs on standard output. 78 | 79 | ``` 80 | % java -jar semver.jar --diff --base-jar previousJar --new-jar current.jar 81 | Class org.project.MyClass 82 | Added Class 83 | Class org.project.MyClass2 84 | Added Method method1 85 | Removed Field field1 86 | Changed Field field2 removed: final 87 | ``` 88 | 89 | ### Excludes / Includes 90 | 91 | In- or exclude classes for the validation by specifying a fully qualified 92 | class name or using wild cards. There are two wild cards: `*` and `**`. 93 | `*` is a wild card for an arbitrary number of characters but at most one 94 | folder hierarchy. 95 | `**` is a wild card for an arbitrary number of characters and an arbitrary 96 | number of folder hierarchies. 97 | 98 | ``` 99 | % java -jar semver.jar --excludes **/MyClass; org/**/MyClass; org/**/*Class; 100 | ``` 101 | 102 | ### Check 103 | 104 | Check compatibility type between two JARs. 105 | 106 | ``` 107 | % java -jar semver.jar --check --base-jar previousJar --new-jar current.jar 108 | BACKWARD_COMPATIBLE_IMPLEMENTER 109 | ``` 110 | 111 | ### Infer 112 | 113 | Infer JAR version based on a previously versioned JAR. 114 | 115 | ``` 116 | % java -jar semver.jar --infer --base-version 1.0.0 --base-jar previous.jar --new-jar current.jar 117 | 1.0.1 118 | ``` 119 | 120 | ### Validate 121 | 122 | Validate JAR version based on a previously versioned JAR. 123 | 124 | ``` 125 | % java -jar semver.jar --validate --base-version 1.0.0 --base-jar previous.jar --new-version 1.0.1 --new-jar current.jar 126 | true 127 | ``` 128 | 129 | 130 | ## Maven Enforcer Rule 131 | 132 | The enforcer rule offers a rule for checking project's version against a previously released artifact. 133 | 134 | ### Checking a project's compatibility 135 | 136 | In order to check your project's compatibility, you must add the enforcer rule as a dependency to 137 | the maven-enforcer-plugin and then configure the maven-enforcer-plugin to run the rule: 138 | 139 | ```xml 140 | 141 | ... 142 | 143 | ... 144 | 145 | ... 146 | 147 | maven-enforcer-plugin 148 | 1.0.1 149 | ... 150 | 151 | ... 152 | 153 | org.semver 154 | enforcer-rule 155 | 0.9.33 156 | 157 | ... 158 | 159 | ... 160 | 161 | .... 162 | 163 | check 164 | verify 165 | 166 | enforce 167 | 168 | 169 | 170 | 171 | BACKWARD_COMPATIBLE_IMPLEMENTER 172 | 173 | 174 | 175 | 176 | ... 177 | 178 | ... 179 | 180 | ... 181 | 182 | ... 183 | 184 | ... 185 | 186 | ``` 187 | 188 | Once you have configured your project, maven-enforcer will be able to throw a build error if current version is not backward compatible with last released one. 189 | 190 | You can force strict checking (i.e. compatibility type must exactly match specified one): 191 | 192 | ```xml 193 | 194 | 195 | 196 | ... 197 | true 198 | ... 199 | 200 | 201 | 202 | ``` 203 | 204 | ### Checking a project's version 205 | 206 | In order to check your project's version, you must add the enforcer rule as a dependency to 207 | the maven-enforcer-plugin and then configure the maven-enforcer-plugin to run the rule: 208 | 209 | ```xml 210 | 211 | ... 212 | 213 | ... 214 | 215 | ... 216 | 217 | maven-enforcer-plugin 218 | 1.0.1 219 | ... 220 | 221 | ... 222 | 223 | org.semver 224 | enforcer-rule 225 | 0.9.33 226 | 227 | ... 228 | 229 | ... 230 | 231 | .... 232 | 233 | check 234 | verify 235 | 236 | enforce 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | ... 245 | 246 | ... 247 | 248 | ... 249 | 250 | ... 251 | 252 | ... 253 | 254 | ``` 255 | 256 | Once you have configured your project, maven-enforcer will be able to throw a build error if current version follows semantic versioning principles. 257 | 258 | ## Dumping details 259 | 260 | Dump details of detected changes: 261 | 262 | ```xml 263 | 264 | 265 | 266 | ... 267 | true 268 | ... 269 | 270 | 271 | 272 | ``` 273 | 274 | ### Checking against a well known version 275 | 276 | You can force check with a well known version: 277 | 278 | ```xml 279 | 280 | 281 | 282 | ... 283 | 1.0.0 284 | ... 285 | 286 | 287 | 288 | ``` 289 | 290 | ### Filtering 291 | 292 | Both rules allow to filter classes/packages: 293 | 294 | ```xml 295 | 296 | ... 297 | 298 | org/project/MyClass 299 | org/project/internal 300 | 301 | 302 | org/project/MyClass 303 | org/project/internal 304 | 305 | ... 306 | 307 | ``` 308 | 309 | ## Maven dependency 310 | 311 | ```xml 312 | 313 | org.semver 314 | enforcer-rule 315 | 0.9.33 316 | 317 | ``` 318 | 319 | ## License 320 | 321 | Released under [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0.html). 322 | -------------------------------------------------------------------------------- /api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | api 6 | 7 | API 8 | 9 | 10 | org.semver 11 | parent 12 | 0.9.34-SNAPSHOT 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | com.google.code.findbugs 19 | jsr305 20 | 2.0.0 21 | 22 | 23 | org.ow2.asm 24 | asm 25 | 26 | 27 | org.ow2.asm 28 | asm-commons 29 | 30 | 31 | commons-lang 32 | commons-lang 33 | 1.0 34 | 35 | 36 | de.tototec 37 | de.tototec.cmdoption 38 | 0.2.0 39 | 40 | 41 | junit 42 | junit 43 | 4.10 44 | test 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.ow2.asm 52 | asm 53 | 5.0.3 54 | 55 | 56 | org.ow2.asm 57 | asm-commons 58 | 5.0.3 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | maven-javadoc-plugin 67 | 68 | false 69 | 70 | 71 | 72 | maven-shade-plugin 73 | 1.5 74 | 75 | 76 | package 77 | 78 | shade 79 | 80 | 81 | 82 | 83 | org.semver.Main 84 | 85 | 86 | true 87 | full 88 | semver-${project.version} 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/AbstractDiffHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | /** 20 | * An abstract implementation of DiffHandler which provides utility methods. 21 | * 22 | * @author Antony Riley 23 | */ 24 | public abstract class AbstractDiffHandler implements DiffHandler 25 | { 26 | /** 27 | * Get the java classname given the internal class name internalName. 28 | * 29 | * @return the classname for internalName 30 | */ 31 | protected final String getClassName(String internalName) { 32 | StringBuffer ret = new StringBuffer(internalName.length()); 33 | for (int i = 0; i < internalName.length(); i++) { 34 | char ch = internalName.charAt(i); 35 | switch (ch) { 36 | case '$': 37 | case '/': 38 | ret.append('.'); 39 | break; 40 | default: 41 | ret.append(ch); 42 | } 43 | } 44 | return ret.toString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/AbstractInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | import org.objectweb.asm.Opcodes; 20 | 21 | /** 22 | * An abstract class representing information about a class, method or field. 23 | * 24 | * @author Antony Riley 25 | */ 26 | public abstract class AbstractInfo 27 | { 28 | /** 29 | * The string used to represent a class, method or field with public 30 | * access. 31 | */ 32 | public final String ACCESS_PUBLIC = "public"; 33 | 34 | /** 35 | * The string used to represent a class, method or field with protected 36 | * access. 37 | */ 38 | public final String ACCESS_PROTECTED = "protected"; 39 | 40 | /** 41 | * The string used to represent a class, method or field with package 42 | * private access. 43 | * Package private access is the default access level used by java when 44 | * you do not specify one of public, protected or private. 45 | */ 46 | public final String ACCESS_PACKAGE = "package"; 47 | 48 | /** 49 | * The string used to represent a class, method or field with private 50 | * access. 51 | */ 52 | public final String ACCESS_PRIVATE = "private"; 53 | 54 | /** 55 | * The access flags for this class, method or field. 56 | */ 57 | private final int access; 58 | 59 | /** 60 | * The internal name of this class, method or field. 61 | */ 62 | private final String name; 63 | 64 | /** 65 | * Construct a new AbstractInfo with the specified access and name. 66 | * 67 | * @param access The access flags for this class, method or field. 68 | * @param name The internal name of this class, method or field. 69 | */ 70 | public AbstractInfo(int access, String name) { 71 | this.access = access; 72 | this.name = name; 73 | } 74 | 75 | /** 76 | * Get the descriptor 77 | * 78 | * @return The descriptor. 79 | */ 80 | public abstract String getDesc(); 81 | 82 | /** 83 | * Get the signature 84 | * 85 | * @return The signature. 86 | */ 87 | public abstract String getSignature(); 88 | 89 | /** 90 | * Get the access flags for this class, method or field. 91 | * 92 | * @return the access flags. 93 | */ 94 | public final int getAccess() { 95 | return access; 96 | } 97 | 98 | /** 99 | * Get the internal name of this class, method or field. 100 | * 101 | * @return the name 102 | */ 103 | public final String getName() { 104 | return name; 105 | } 106 | 107 | /** 108 | * Test if this class, method or field is public. 109 | * 110 | * @return true if it is public. 111 | */ 112 | public final boolean isPublic() { 113 | return (access & Opcodes.ACC_PUBLIC) != 0; 114 | } 115 | 116 | /** 117 | * Test if this class, method or field is protected. 118 | * 119 | * @return true if it is protected. 120 | */ 121 | public final boolean isProtected() { 122 | return (access & Opcodes.ACC_PROTECTED) != 0; 123 | } 124 | 125 | /** 126 | * Test if this class, method or field is package private. 127 | * 128 | * @return true if it is package private. 129 | */ 130 | public final boolean isPackagePrivate() { 131 | return (access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | 132 | Opcodes.ACC_PRIVATE)) == 0; 133 | } 134 | 135 | /** 136 | * Test if this class, method or field is private. 137 | * 138 | * @return true if it is private. 139 | */ 140 | public final boolean isPrivate() { 141 | return (access & Opcodes.ACC_PRIVATE) != 0; 142 | } 143 | 144 | /** 145 | * Test if this class, method or field is abstract. 146 | * 147 | * @return true if it is abstract. 148 | */ 149 | public final boolean isAbstract() { 150 | return (access & Opcodes.ACC_ABSTRACT) != 0; 151 | } 152 | 153 | /** 154 | * Test if this class, method or field is annotation 155 | * 156 | * @return true if it is annotation. 157 | */ 158 | public final boolean isAnnotation() { 159 | return (access & Opcodes.ACC_ANNOTATION) != 0; 160 | } 161 | 162 | /** 163 | * Test if this class, method or field is a bridge 164 | * 165 | * @return true if it is a bridge. 166 | */ 167 | public final boolean isBridge() { 168 | return (access & Opcodes.ACC_BRIDGE) != 0; 169 | } 170 | 171 | /** 172 | * Test if this class, method or field is deprecated. 173 | * 174 | * @return true if it is deprecated. 175 | */ 176 | public final boolean isDeprecated() { 177 | return (access & Opcodes.ACC_DEPRECATED) != 0; 178 | } 179 | 180 | /** 181 | * Test if this class, method or field is an enum. 182 | * 183 | * @return true if it is an enum. 184 | */ 185 | public final boolean isEnum() { 186 | return (access & Opcodes.ACC_ENUM) != 0; 187 | } 188 | 189 | /** 190 | * Test if this class, method or field is final. 191 | * 192 | * @return true if it is final. 193 | */ 194 | public final boolean isFinal() { 195 | return (access & Opcodes.ACC_FINAL) != 0; 196 | } 197 | 198 | /** 199 | * Test if this class, method or field is an interface. 200 | * 201 | * @return true if it is an interface. 202 | */ 203 | public final boolean isInterface() { 204 | return (access & Opcodes.ACC_INTERFACE) != 0; 205 | } 206 | 207 | /** 208 | * Test if this class, method or field is native. 209 | * 210 | * @return true if it is native. 211 | */ 212 | public final boolean isNative() { 213 | return (access & Opcodes.ACC_NATIVE) != 0; 214 | } 215 | 216 | /** 217 | * Test if this class, method or field is static. 218 | * 219 | * @return true if it is static. 220 | */ 221 | public final boolean isStatic() { 222 | return (access & Opcodes.ACC_STATIC) != 0; 223 | } 224 | 225 | /** 226 | * Test if this class, method or field is string. 227 | * 228 | * @return true if it is strict. 229 | */ 230 | public final boolean isStrict() { 231 | return (access & Opcodes.ACC_STRICT) != 0; 232 | } 233 | 234 | /** 235 | * Test if this class, method or field is super. 236 | * 237 | * @return true if it is super. 238 | */ 239 | public final boolean isSuper() { 240 | return (access & Opcodes.ACC_SUPER) != 0; 241 | } 242 | 243 | /** 244 | * Test if this class, method or field is synchronized. 245 | * 246 | * @return true if it is synchronized 247 | */ 248 | public final boolean isSynchronized() { 249 | return (access & Opcodes.ACC_SYNCHRONIZED) != 0; 250 | } 251 | 252 | /** 253 | * Test if this class, method or field is synthetic. 254 | * 255 | * @return true if it is synchronized. 256 | */ 257 | public final boolean isSynthetic() { 258 | return (access & Opcodes.ACC_SYNTHETIC) != 0; 259 | } 260 | 261 | /** 262 | * Test if this class or field is transient. 263 | * If this flag is set on a method it means something different. 264 | * 265 | * @return true if it is transient. 266 | */ 267 | public final boolean isTransient() { 268 | return !(this instanceof MethodInfo) && 269 | ((access & Opcodes.ACC_TRANSIENT) != 0); 270 | } 271 | 272 | /** 273 | * Test if this method is varargs. 274 | * If this flag is set on a class or field it means something different. 275 | * Well, it probably shouldn't be set on a class as it would make 276 | * no sense, it only really makes sense on fields and methods. 277 | * 278 | * @return true if it is varargs. 279 | */ 280 | public final boolean isVarargs() { 281 | return (this instanceof MethodInfo) && 282 | ((access & Opcodes.ACC_VARARGS) != 0); 283 | } 284 | 285 | /** 286 | * Test if this class, method or field is volatile. 287 | * 288 | * @return true if it is volatile. 289 | */ 290 | public final boolean isVolatile() { 291 | return (access & Opcodes.ACC_VOLATILE) != 0; 292 | } 293 | 294 | /** 295 | * Retrieve the access level for this class, method or field. 296 | * 297 | * @return the access level 298 | */ 299 | public final String getAccessType() { 300 | if (isPublic()) 301 | return ACCESS_PUBLIC; 302 | if (isProtected()) 303 | return ACCESS_PROTECTED; 304 | if (isPrivate()) 305 | return ACCESS_PRIVATE; 306 | return ACCESS_PACKAGE; 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/ClassInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | import java.util.Map; 19 | 20 | /** 21 | * Information about a class file. 22 | * 23 | * @author Antony Riley 24 | */ 25 | public final class ClassInfo extends AbstractInfo 26 | { 27 | /** 28 | * The classfile version number. 29 | */ 30 | private final int version; 31 | 32 | /** 33 | * The class signature. 34 | */ 35 | private final String signature; 36 | 37 | /** 38 | * The internal classname of the superclass. 39 | */ 40 | private final String supername; 41 | 42 | /** 43 | * An array of names of internal classnames of interfaces implemented 44 | * by the class. 45 | */ 46 | private final String[] interfaces; 47 | 48 | /** 49 | * A map of method signature to MethodInfo, for the methods provided 50 | * by this class. 51 | */ 52 | private final Map methodMap; 53 | 54 | /** 55 | * A map of field signature to FieldInfo, for the fields provided by 56 | * this class. 57 | */ 58 | private final Map fieldMap; 59 | 60 | /** 61 | * Create a new classinfo. 62 | * 63 | * @param version the class file version number. 64 | * @param access the access flags for the class. 65 | * @param name the internal name of the class. 66 | * @param signature the signature of the class. 67 | * @param interfaces an array of internal names of interfaces implemented 68 | * by the class. 69 | * @param methodMap a map of methods provided by this class. 70 | * @param fieldMap a map of fields provided by this class. 71 | */ 72 | public ClassInfo(int version, int access, String name, String signature, 73 | String supername, String[] interfaces, Map methodMap, 74 | Map fieldMap) { 75 | super(access, name); 76 | this.version = version; 77 | this.signature = signature; 78 | this.supername = supername; 79 | this.interfaces = interfaces; 80 | this.methodMap = methodMap; 81 | this.fieldMap = fieldMap; 82 | } 83 | 84 | /** 85 | * Get the class file version. 86 | * 87 | * @return The class file version as specified in the java language spec. 88 | */ 89 | public final int getVersion() { 90 | return version; 91 | } 92 | 93 | @Override 94 | public final String getDesc() { 95 | return null; 96 | } 97 | 98 | @Override 99 | public final String getSignature() { 100 | return signature; 101 | } 102 | 103 | /** 104 | * Get the internal name of the superclass. 105 | * 106 | * @return the internal name of the superclass 107 | */ 108 | public final String getSupername() { 109 | return supername; 110 | } 111 | 112 | /** 113 | * Get the internal names of the interfaces implemented by this class 114 | * 115 | * @return an array of internal names of classes implemented by the class. 116 | */ 117 | public final String[] getInterfaces() { 118 | return interfaces; 119 | } 120 | 121 | /** 122 | * Get the map of method signatures to methods. 123 | * 124 | * @return a map with method signatures as keys, and MethodInfos as values. 125 | */ 126 | public final Map getMethodMap() { 127 | return methodMap; 128 | } 129 | 130 | /** 131 | * Get the map of field signatures to fields. 132 | * 133 | * @return a map with field signatures as keys, and FieldInfos as values. 134 | */ 135 | public final Map getFieldMap() { 136 | return fieldMap; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/ClassInfoVisitor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import org.objectweb.asm.ClassVisitor; 22 | import org.objectweb.asm.FieldVisitor; 23 | import org.objectweb.asm.MethodVisitor; 24 | import org.objectweb.asm.Opcodes; 25 | 26 | /** 27 | * A reusable class which uses the ASM to build up ClassInfo about a 28 | * java class file. 29 | * 30 | * @author Antony Riley 31 | */ 32 | public class ClassInfoVisitor extends ClassVisitor 33 | { 34 | /** 35 | * The class file version. 36 | */ 37 | private int version; 38 | 39 | /** 40 | * The access flags for the class. 41 | */ 42 | 43 | private int access; 44 | 45 | /** 46 | * The internal name of the class. 47 | */ 48 | private String name; 49 | 50 | /** 51 | * The signature of the class 52 | */ 53 | private String signature; 54 | 55 | /** 56 | * The internal name of the superclass. 57 | */ 58 | private String supername; 59 | 60 | /** 61 | * An array of internal names of interfaces implemented by this class. 62 | */ 63 | private String[] interfaces; 64 | 65 | /** 66 | * A map of method signature to a MethodInfo describing the method. 67 | */ 68 | private Map methodMap; 69 | 70 | /** 71 | * A map of field signature to a FieldInfo describing the field. 72 | */ 73 | private Map fieldMap; 74 | 75 | public ClassInfoVisitor() { 76 | super(Opcodes.ASM5); 77 | } 78 | 79 | /** 80 | * Reset this ClassInfoVisitor so that it can be used to visit another 81 | * class. 82 | */ 83 | public void reset() { 84 | methodMap = new HashMap(); 85 | fieldMap = new HashMap(); 86 | } 87 | 88 | /** 89 | * The the classInfo this ClassInfoVisitor has built up about a class 90 | */ 91 | public ClassInfo getClassInfo() { 92 | return new ClassInfo(version, access, name, signature, supername, 93 | interfaces, methodMap, fieldMap); 94 | } 95 | 96 | /** 97 | * Receive notification of information about a class from ASM. 98 | * 99 | * @param version the class file version number. 100 | * @param access the access flags for the class. 101 | * @param name the internal name of the class. 102 | * @param signature the signature of the class. 103 | * @param supername the internal name of the super class. 104 | * @param interfaces the internal names of interfaces implemented. 105 | */ 106 | public void visit(int version, int access, String name, String signature, 107 | String supername, String[] interfaces) { 108 | this.version = version; 109 | this.access = access; 110 | this.name = name; 111 | this.signature = signature; 112 | this.supername = supername; 113 | this.interfaces = interfaces; 114 | } 115 | 116 | @Override 117 | public MethodVisitor visitMethod(int access, String name, String desc, 118 | String signature, String[] exceptions) { 119 | methodMap.put(name + desc, new MethodInfo(access, name, desc, 120 | signature, exceptions)); 121 | return null; 122 | } 123 | 124 | @Override 125 | public FieldVisitor visitField(int access, String name, String desc, 126 | String signature, Object value) { 127 | fieldMap.put(name, 128 | new FieldInfo(access, name, desc, signature, value)); 129 | return null; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/DiffCriteria.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | /** 20 | * An interface for choosing which API differences are interesting. 21 | * 22 | * @author Antony Riley 23 | */ 24 | public interface DiffCriteria 25 | { 26 | /** 27 | * Check if the class described by classinfo is interesting. 28 | * 29 | * @return true if classinfo is interesting, false otherwise. 30 | */ 31 | public boolean validClass(ClassInfo classinfo); 32 | 33 | /** 34 | * Check if the method described by methodinfo is interesting. 35 | * 36 | * @return true if methodinfo is interesting, false otherwise. 37 | */ 38 | public boolean validMethod(MethodInfo methodinfo); 39 | 40 | /** 41 | * Check if the method described by fieldinfo is interesting. 42 | * 43 | * @return true if fieldinfo is interesting, false otherwise. 44 | */ 45 | public boolean validField(FieldInfo fieldinfo); 46 | 47 | /** 48 | * Check if the differences between the class described by infoA and 49 | * the class described by infoB are interesting. 50 | * 51 | * @return true if the changes are interesting, false otherwise. 52 | */ 53 | public boolean differs(ClassInfo infoA, ClassInfo infoB); 54 | 55 | /** 56 | * Check if the differences between the method described by infoA and 57 | * the method described by infoB are interesting. 58 | * 59 | * @return true if the changes are interesting, false otherwise. 60 | */ 61 | public boolean differs(MethodInfo methodinfo, MethodInfo methodinfo_1_); 62 | 63 | /** 64 | * Check if the differences between the field described by infoA and the 65 | * field described by infoB are interesting. 66 | * 67 | * @return true if the changes are interesting, false otherwise. 68 | */ 69 | public boolean differs(FieldInfo fieldinfo, FieldInfo fieldinfo_2_); 70 | } 71 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/DiffException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | /** 20 | * A wrapper exception classes for various exceptions that can happen 21 | * whilst performing a diff. 22 | * 23 | * @author Antony Riley 24 | */ 25 | public class DiffException extends Exception 26 | { 27 | /** 28 | * Create a new DiffException wrapping another exception. 29 | * 30 | * @param toWrap the wrapped exception 31 | */ 32 | public DiffException(Exception toWrap) { 33 | super((Throwable) toWrap); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/DiffHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | /** 20 | * An interface for classes which wish to receive information about 21 | * differences in class files between two different jar file version to 22 | * implement. 23 | * 24 | * @author Antony Riley 25 | */ 26 | public interface DiffHandler 27 | { 28 | /** 29 | * Start a diff between two versions, where string a is the old version 30 | * and string b is the new version. 31 | * 32 | * @param a the name of the old version 33 | * @param b the name of the new version 34 | * @throws DiffException when there is an underlying exception, e.g. 35 | * writing to a file caused an IOException 36 | */ 37 | public void startDiff(String a, String b) 38 | throws DiffException; 39 | 40 | /** 41 | * Start the list of old contents. 42 | * 43 | * @throws DiffException when there is an underlying exception, e.g. 44 | * writing to a file caused an IOException 45 | */ 46 | public void startOldContents() throws DiffException; 47 | 48 | /** 49 | * Start the list of new contents. 50 | * 51 | * @throws DiffException when there is an underlying exception, e.g. 52 | * writing to a file caused an IOException 53 | */ 54 | public void startNewContents() throws DiffException; 55 | 56 | /** 57 | * Add a contained class. 58 | * 59 | * @param info information about a class 60 | * @throws DiffException when there is an underlying exception, e.g. 61 | * writing to a file caused an IOException 62 | */ 63 | public void contains(ClassInfo info) throws DiffException; 64 | 65 | /** 66 | * End the list of old contents. 67 | * 68 | * @throws DiffException when there is an underlying exception, e.g. 69 | * writing to a file caused an IOException 70 | */ 71 | public void endOldContents() throws DiffException; 72 | 73 | /** 74 | * End the list of new contents. 75 | * 76 | * @throws DiffException when there is an underlying exception, e.g. 77 | * writing to a file caused an IOException 78 | */ 79 | public void endNewContents() throws DiffException; 80 | 81 | /** 82 | * Start the list of removed classes. 83 | * 84 | * @throws DiffException when there is an underlying exception, e.g. 85 | * writing to a file caused an IOException 86 | */ 87 | public void startRemoved() throws DiffException; 88 | 89 | /** 90 | * Notification that a class was removed. 91 | * 92 | * @param classinfo information about the class that has been removed. 93 | * @throws DiffException when there is an underlying exception, e.g. 94 | * writing to a file caused an IOException 95 | */ 96 | public void classRemoved(ClassInfo classinfo) throws DiffException; 97 | 98 | /** 99 | * End of list of removed classes. 100 | * 101 | * @throws DiffException when there is an underlying exception, e.g. 102 | * writing to a file caused an IOException 103 | */ 104 | public void endRemoved() throws DiffException; 105 | 106 | /** 107 | * Start of list of added classes. 108 | * 109 | * @throws DiffException when there is an underlying exception, e.g. 110 | * writing to a file caused an IOException 111 | */ 112 | public void startAdded() throws DiffException; 113 | 114 | /** 115 | * Notification that a class was added. 116 | * 117 | * @param classinfo information about the class that has been removed. 118 | * @throws DiffException when there is an underlying exception, e.g. 119 | * writing to a file caused an IOException 120 | */ 121 | public void classAdded(ClassInfo classinfo) throws DiffException; 122 | 123 | /** 124 | * End of list of removed classes. 125 | * 126 | * @throws DiffException when there is an underlying exception, e.g. 127 | * writing to a file caused an IOException 128 | */ 129 | public void endAdded() throws DiffException; 130 | 131 | /** 132 | * Start list of changed classes. 133 | * 134 | * @throws DiffException when there is an underlying exception, e.g. 135 | * writing to a file caused an IOException 136 | */ 137 | public void startChanged() throws DiffException; 138 | 139 | /** 140 | * Start information about class changes for the classname passed. 141 | * 142 | * @throws DiffException when there is an underlying exception, e.g. 143 | * writing to a file caused an IOException 144 | */ 145 | public void startClassChanged(String string) throws DiffException; 146 | 147 | /** 148 | * The field was removed for the current class that has changed. 149 | * 150 | * @param fieldinfo Information about the field removed. 151 | * @throws DiffException when there is an underlying exception, e.g. 152 | * writing to a file caused an IOException 153 | */ 154 | public void fieldRemoved(FieldInfo fieldinfo) throws DiffException; 155 | 156 | /** 157 | * The method was removed for the current class that has changed. 158 | * 159 | * @param methodinfo Information about the method removed. 160 | * @throws DiffException when there is an underlying exception, e.g. 161 | * writing to a file caused an IOException 162 | */ 163 | public void methodRemoved(MethodInfo methodinfo) throws DiffException; 164 | 165 | /** 166 | * The field was added for the current class that has changed. 167 | * 168 | * @param fieldinfo Information about the field added. 169 | * @throws DiffException when there is an underlying exception, e.g. 170 | * writing to a file caused an IOException 171 | */ 172 | public void fieldAdded(FieldInfo fieldinfo) throws DiffException; 173 | 174 | /** 175 | * The method was added for the current class that has changed. 176 | * 177 | * @param methodinfo Information about the method added. 178 | * @throws DiffException when there is an underlying exception, e.g. 179 | * writing to a file caused an IOException 180 | */ 181 | public void methodAdded(MethodInfo methodinfo) throws DiffException; 182 | 183 | /** 184 | * The current class has changed. 185 | * This is called when a class's interfaces or superclass or access 186 | * flags have changed. 187 | * 188 | * @param oldClassinfo Information about the old class. 189 | * @param newClassinfo Information about the new class. 190 | * @throws DiffException when there is an underlying exception, e.g. 191 | * writing to a file caused an IOException 192 | */ 193 | public void classChanged(ClassInfo oldClassinfo, ClassInfo newClassinfo) 194 | throws DiffException; 195 | 196 | /** 197 | * The current class has been deprecated. 198 | * 199 | * @param oldClassinfo Information about the old class. 200 | * @param newClassinfo Information about the new class. 201 | * @throws DiffException when there is an underlying exception, e.g. 202 | * writing to a file caused an IOException 203 | */ 204 | public void classDeprecated(ClassInfo oldClassinfo, ClassInfo newClassinfo) 205 | throws DiffException; 206 | 207 | /** 208 | * A field on the current class has changed. 209 | * 210 | * @param oldFieldinfo Information about the old field. 211 | * @param newFieldinfo Information about the new field. 212 | * @throws DiffException when there is an underlying exception, e.g. 213 | * writing to a file caused an IOException 214 | */ 215 | public void fieldChanged(FieldInfo oldFieldinfo, FieldInfo newFieldinfo) 216 | throws DiffException; 217 | 218 | /** 219 | * A field on the current class has been deprecated. 220 | * 221 | * @param oldFieldinfo Information about the old field. 222 | * @param newFieldinfo Information about the new field. 223 | * @throws DiffException when there is an underlying exception, e.g. 224 | * writing to a file caused an IOException 225 | */ 226 | public void fieldDeprecated(FieldInfo oldFieldinfo, FieldInfo newFieldinfo) 227 | throws DiffException; 228 | 229 | /** 230 | * A method on the current class has changed. 231 | * 232 | * @param oldMethodInfo Information about the old method. 233 | * @param newMethodInfo Information about the new method. 234 | * @throws DiffException when there is an underlying exception, e.g. 235 | * writing to a file caused an IOException 236 | */ 237 | public void methodChanged 238 | (MethodInfo oldMethodInfo, MethodInfo newMethodInfo) throws DiffException; 239 | 240 | /** 241 | * The method has been deprecated. 242 | * 243 | * @param oldMethodInfo Information about the old method. 244 | * @param newMethodInfo Information about the new method. 245 | * @throws DiffException when there is an underlying exception, e.g. 246 | * writing to a file caused an IOException 247 | */ 248 | public void methodDeprecated(MethodInfo oldMethodInfo, 249 | MethodInfo newMethodInfo) throws DiffException; 250 | 251 | /** 252 | * End of changes for the current class. 253 | * 254 | * @throws DiffException when there is an underlying exception, e.g. 255 | * writing to a file caused an IOException 256 | */ 257 | public void endClassChanged() throws DiffException; 258 | 259 | /** 260 | * End of class changes. 261 | * 262 | * @throws DiffException when there is an underlying exception, e.g. 263 | * writing to a file caused an IOException 264 | */ 265 | public void endChanged() throws DiffException; 266 | 267 | /** 268 | * End of the diff. 269 | * 270 | * @throws DiffException when there is an underlying exception, e.g. 271 | * writing to a file caused an IOException 272 | */ 273 | public void endDiff() throws DiffException; 274 | } 275 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/FieldInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | /** 20 | * Information about a field of a class. 21 | * 22 | * @author Antony Riley 23 | */ 24 | public final class FieldInfo extends AbstractInfo 25 | { 26 | /** 27 | * The field descriptor for this field. 28 | */ 29 | private final String desc; 30 | 31 | /** 32 | * The signature for this field. 33 | */ 34 | private final String signature; 35 | 36 | /** 37 | * The initial value of this field. 38 | */ 39 | private final Object value; 40 | 41 | /** 42 | * Create a new FieldInfo 43 | * 44 | * @param access The access flags. 45 | * @param name The name of the field. 46 | * @param desc The field descriptor. 47 | * @param signature The signature of this field. 48 | * @param value The initial value of the field. 49 | */ 50 | public FieldInfo(int access, String name, String desc, String signature, 51 | Object value) { 52 | super(access, name); 53 | this.desc = desc; 54 | this.signature = signature; 55 | this.value = value; 56 | } 57 | 58 | @Override 59 | public final String getDesc() { 60 | return desc; 61 | } 62 | 63 | @Override 64 | public final String getSignature() { 65 | return signature; 66 | } 67 | 68 | /** 69 | * Get the initial value for this fieldinfo 70 | * 71 | * @return The initial value. 72 | */ 73 | public final Object getValue() { 74 | return value; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/MethodInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | /** 20 | * A class to hold information about a method. 21 | * 22 | * @author Antony Riley 23 | */ 24 | public final class MethodInfo extends AbstractInfo 25 | { 26 | /** 27 | * The method descriptor. 28 | */ 29 | private final String desc; 30 | 31 | /** 32 | * The signature of the method. 33 | */ 34 | private final String signature; 35 | 36 | /** 37 | * An array of the exceptions thrown by this method. 38 | */ 39 | private final String[] exceptions; 40 | 41 | /** 42 | * Create a new MethodInfo with the specified parameters. 43 | * 44 | * @param access The access flags for the method. 45 | * @param name The name of the method. 46 | * @param signature The signature of the method. 47 | * @param exceptions The exceptions thrown by the method. 48 | */ 49 | public MethodInfo(int access, String name, String desc, String signature, 50 | String[] exceptions) { 51 | super(access, name); 52 | this.desc = desc; 53 | this.signature = signature; 54 | this.exceptions = exceptions; 55 | } 56 | 57 | @Override 58 | public final String getDesc() { 59 | return desc; 60 | } 61 | 62 | @Override 63 | public final String getSignature() { 64 | return signature; 65 | } 66 | 67 | /** 68 | * Get the array of exceptions which can be thrown by the method. 69 | * 70 | * @return the exceptions as a String[] of internal names. 71 | */ 72 | public final String[] getExceptions() { 73 | return exceptions; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/PublicDiffCriteria.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | import java.util.Arrays; 20 | import java.util.HashSet; 21 | import java.util.Set; 22 | 23 | /** 24 | * A specific type of DiffCriteria which is only true for classes, methods 25 | * and fields which are not synthetic and public. 26 | * 27 | * @author Antony Riley 28 | */ 29 | public class PublicDiffCriteria implements DiffCriteria 30 | { 31 | /** 32 | * Check if a class is valid. 33 | * If the class is not synthetic and public, return true. 34 | * 35 | * @param info Info describing the class. 36 | * @return True if the class meets the criteria, false otherwise. 37 | */ 38 | public boolean validClass(ClassInfo info) { 39 | return !info.isSynthetic() && info.isPublic(); 40 | } 41 | 42 | /** 43 | * Check if a method is valid. 44 | * If the method is not synthetic and public, return true. 45 | * 46 | * @param info Info describing the method. 47 | * @return True if the method meets the criteria, false otherwise. 48 | */ 49 | public boolean validMethod(MethodInfo info) { 50 | return !info.isSynthetic() && info.isPublic(); 51 | } 52 | 53 | /** 54 | * Check if a field is valid. 55 | * If the method is not synthetic and public, return true. 56 | * 57 | * @param info Info describing the field. 58 | * @return True if the field meets the criteria, false otherwise. 59 | */ 60 | public boolean validField(FieldInfo info) { 61 | return !info.isSynthetic() && info.isPublic(); 62 | } 63 | 64 | /** 65 | * Check if there is a change between two versions of a class. 66 | * Returns true if the access flags differ, or if the superclass differs 67 | * or if the implemented interfaces differ. 68 | * 69 | * @param oldInfo Info about the old version of the class. 70 | * @param newInfo Info about the new version of the class. 71 | * @return True if the classes differ, false otherwise. 72 | */ 73 | public boolean differs(ClassInfo oldInfo, ClassInfo newInfo) { 74 | if (Tools.isClassAccessChange(oldInfo.getAccess(), newInfo.getAccess())) 75 | return true; 76 | // Yes classes can have a null supername, e.g. java.lang.Object ! 77 | if(oldInfo.getSupername() == null) { 78 | if(newInfo.getSupername() != null) { 79 | return true; 80 | } 81 | } else if (!oldInfo.getSupername().equals(newInfo.getSupername())) { 82 | return true; 83 | } 84 | final Set oldInterfaces 85 | = new HashSet(Arrays.asList(oldInfo.getInterfaces())); 86 | final Set newInterfaces 87 | = new HashSet(Arrays.asList(newInfo.getInterfaces())); 88 | if (!oldInterfaces.equals(newInterfaces)) 89 | return true; 90 | return false; 91 | } 92 | 93 | /** 94 | * Check if there is a change between two versions of a method. 95 | * Returns true if the access flags differ, or if the thrown 96 | * exceptions differ. 97 | * 98 | * @param oldInfo Info about the old version of the method. 99 | * @param newInfo Info about the new version of the method. 100 | * @return True if the methods differ, false otherwise. 101 | */ 102 | public boolean differs(MethodInfo oldInfo, MethodInfo newInfo) { 103 | if (Tools.isMethodAccessChange(oldInfo.getAccess(), newInfo.getAccess())) 104 | return true; 105 | if (oldInfo.getExceptions() == null 106 | || newInfo.getExceptions() == null) { 107 | if (oldInfo.getExceptions() != newInfo.getExceptions()) 108 | return true; 109 | } else { 110 | final Set oldExceptions 111 | = new HashSet(Arrays.asList(oldInfo.getExceptions())); 112 | final Set newExceptions 113 | = new HashSet(Arrays.asList(newInfo.getExceptions())); 114 | if (!oldExceptions.equals(newExceptions)) 115 | return true; 116 | } 117 | return false; 118 | } 119 | 120 | /** 121 | * Check if there is a change between two versions of a field. 122 | * Returns true if the access flags differ, or if the inital value 123 | * of the field differs. 124 | * 125 | * @param oldInfo Info about the old version of the field. 126 | * @param newInfo Info about the new version of the field. 127 | * @return True if the fields differ, false otherwise. 128 | */ 129 | public boolean differs(FieldInfo oldInfo, FieldInfo newInfo) { 130 | if (Tools.isFieldAccessChange(oldInfo.getAccess(), newInfo.getAccess())) 131 | return true; 132 | if (oldInfo.getValue() == null || newInfo.getValue() == null) { 133 | if (oldInfo.getValue() != newInfo.getValue()) 134 | return true; 135 | } else if (!oldInfo.getValue().equals(newInfo.getValue())) 136 | return true; 137 | return false; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/SimpleDiffCriteria.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | import java.util.Arrays; 20 | import java.util.HashSet; 21 | import java.util.Set; 22 | 23 | /** 24 | * A specific type of DiffCriteria which is only true for classes, methods 25 | * and fields which are not synthetic, and are public or protected. 26 | * 27 | * @author Antony Riley 28 | */ 29 | public class SimpleDiffCriteria implements DiffCriteria 30 | { 31 | /** 32 | * Check if a class is valid. 33 | * If the class is not synthetic and is public or protected, return true. 34 | * 35 | * @param info Info describing the class. 36 | * @return True if the class meets the criteria, false otherwise. 37 | */ 38 | public boolean validClass(ClassInfo info) { 39 | return !info.isSynthetic() && (info.isPublic() || info.isProtected()); 40 | } 41 | 42 | /** 43 | * Check if a method is valid. 44 | * If the method is not synthetic and is public or protected, return true. 45 | * 46 | * @param info Info describing the method. 47 | * @return True if the method meets the criteria, false otherwise. 48 | */ 49 | public boolean validMethod(MethodInfo info) { 50 | return !info.isSynthetic() && (info.isPublic() || info.isProtected()); 51 | } 52 | 53 | /** 54 | * Check if a field is valid. 55 | * If the method is not synthetic and is public or protected, return true. 56 | * 57 | * @param info Info describing the field. 58 | * @return True if the field meets the criteria, false otherwise. 59 | */ 60 | public boolean validField(FieldInfo info) { 61 | return !info.isSynthetic() && (info.isPublic() || info.isProtected()); 62 | } 63 | 64 | /** 65 | * Check if there is a change between two versions of a class. 66 | * Returns true if the access flags differ, or if the superclass differs 67 | * or if the implemented interfaces differ. 68 | * 69 | * @param oldInfo Info about the old version of the class. 70 | * @param newInfo Info about the new version of the class. 71 | * @return True if the classes differ, false otherwise. 72 | */ 73 | public boolean differs(ClassInfo oldInfo, ClassInfo newInfo) { 74 | if (Tools.isClassAccessChange(oldInfo.getAccess(), newInfo.getAccess())) 75 | return true; 76 | // Yes classes can have a null supername, e.g. java.lang.Object ! 77 | if(oldInfo.getSupername() == null) { 78 | if(newInfo.getSupername() != null) { 79 | return true; 80 | } 81 | } else if (!oldInfo.getSupername().equals(newInfo.getSupername())) { 82 | return true; 83 | } 84 | final Set oldInterfaces 85 | = new HashSet(Arrays.asList(oldInfo.getInterfaces())); 86 | final Set newInterfaces 87 | = new HashSet(Arrays.asList(newInfo.getInterfaces())); 88 | if (!oldInterfaces.equals(newInterfaces)) 89 | return true; 90 | return false; 91 | } 92 | 93 | /** 94 | * Check if there is a change between two versions of a method. 95 | * Returns true if the access flags differ, or if the thrown 96 | * exceptions differ. 97 | * 98 | * @param oldInfo Info about the old version of the method. 99 | * @param newInfo Info about the new version of the method. 100 | * @return True if the methods differ, false otherwise. 101 | */ 102 | public boolean differs(MethodInfo oldInfo, MethodInfo newInfo) { 103 | if (Tools.isMethodAccessChange(oldInfo.getAccess(), newInfo.getAccess())) 104 | return true; 105 | if (oldInfo.getExceptions() == null 106 | || newInfo.getExceptions() == null) { 107 | if (oldInfo.getExceptions() != newInfo.getExceptions()) 108 | return true; 109 | } else { 110 | final Set oldExceptions 111 | = new HashSet(Arrays.asList(oldInfo.getExceptions())); 112 | final Set newExceptions 113 | = new HashSet(Arrays.asList(newInfo.getExceptions())); 114 | if (!oldExceptions.equals(newExceptions)) 115 | return true; 116 | } 117 | return false; 118 | } 119 | 120 | /** 121 | * Check if there is a change between two versions of a field. 122 | * Returns true if the access flags differ, or if the inital value 123 | * of the field differs. 124 | * 125 | * @param oldInfo Info about the old version of the field. 126 | * @param newInfo Info about the new version of the field. 127 | * @return True if the fields differ, false otherwise. 128 | */ 129 | public boolean differs(FieldInfo oldInfo, FieldInfo newInfo) { 130 | if (Tools.isFieldAccessChange(oldInfo.getAccess(), newInfo.getAccess())) 131 | return true; 132 | if (oldInfo.getValue() == null || newInfo.getValue() == null) { 133 | if (oldInfo.getValue() != newInfo.getValue()) 134 | return true; 135 | } else if (!oldInfo.getValue().equals(newInfo.getValue())) 136 | return true; 137 | return false; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /api/src/main/java/org/osjava/jardiff/Tools.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.osjava.jardiff; 18 | 19 | import org.objectweb.asm.Opcodes; 20 | 21 | /** 22 | * A set of Tools which do not belong anywhere else in the API at this time. 23 | * This is nasty, but for now, useful. 24 | * 25 | * @author Antony Riley 26 | */ 27 | public final class Tools 28 | { 29 | /** 30 | * Private constructor so this class can't be instantiated. 31 | */ 32 | private Tools() { 33 | /* empty */ 34 | } 35 | 36 | /** 37 | * Get the java class name given an internal class name. 38 | * This method currently replaces all instances of $ and / with . this 39 | * may not be according to the java language spec, and will almost 40 | * certainly fail for some inner classes. 41 | * 42 | * @param internalName The internal name of the class. 43 | * @return The java class name. 44 | */ 45 | public static final String getClassName(String internalName) { 46 | final StringBuffer ret = new StringBuffer(internalName.length()); 47 | for (int i = 0; i < internalName.length(); i++) { 48 | final char ch = internalName.charAt(i); 49 | switch (ch) { 50 | case '$': 51 | case '/': 52 | ret.append('.'); 53 | break; 54 | default: 55 | ret.append(ch); 56 | } 57 | } 58 | return ret.toString(); 59 | } 60 | 61 | private static boolean has(final int value, final int mask) { 62 | return (value & mask) != 0; 63 | } 64 | private static boolean not(final int value, final int mask) { 65 | return (value & mask) == 0; 66 | } 67 | 68 | private static boolean isAccessIncompatible(int oldAccess, int newAccess) { 69 | if (has(newAccess, Opcodes.ACC_PUBLIC)) { 70 | return false; 71 | } else if (has(newAccess, Opcodes.ACC_PROTECTED)) { 72 | return has(oldAccess, Opcodes.ACC_PUBLIC); 73 | } else if (has(newAccess, Opcodes.ACC_PRIVATE)) { 74 | return not(oldAccess, Opcodes.ACC_PRIVATE); 75 | } else { 76 | // new access is package, it is incompatible if old access was public or protected 77 | return has(oldAccess, Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED); 78 | } 79 | } 80 | 81 | /** 82 | * @deprecated Use {@link #isClassAccessChange(int, int)}. 83 | */ 84 | public static boolean isAccessChange(int oldAccess, int newAccess) { 85 | return isClassAccessChange(oldAccess, newAccess); 86 | } 87 | 88 | /** 89 | * Returns whether a class's newAccess is incompatible with oldAccess 90 | * following Java Language Specification, Java SE 7 Edition: 91 | *
    92 | *
  • 13.4.1 abstract Classes
      93 | *
    • If a class that was not declared abstract is changed to be declared abstract, 94 | * then pre-existing binaries that attempt to create new instances of that class 95 | * will throw either an InstantiationError at link time, 96 | * or (if a reflective method is used) an InstantiationException at run time. 97 | * Such changes break backward compatibility!
    • 98 | *
    • Changing a class that is declared abstract to no longer be declared abstract 99 | * does not break compatibility with pre-existing binaries.
    • 100 | *
  • 101 | *
  • 13.4.2 final Classes
      102 | *
    • If a class that was not declared final is changed to be declared final, 103 | * then a VerifyError is thrown if a binary of a pre-existing subclass of this class is loaded, 104 | * because final classes can have no subclasses. 105 | * Such changes break functional backward compatibility!
    • 106 | *
    • Changing a class that is declared final to no longer be declared final 107 | * does not break compatibility with pre-existing binaries.
    • 108 | *
  • 109 | *
110 | * 111 | * @param oldAccess 112 | * @param newAccess 113 | * @return 114 | */ 115 | public static boolean isClassAccessChange(final int oldAccess, final int newAccess) { 116 | if ( not(oldAccess, Opcodes.ACC_ABSTRACT) && has(newAccess, Opcodes.ACC_ABSTRACT) ) { 117 | return true; // 13.4.1 #1 118 | } else if ( not(oldAccess, Opcodes.ACC_FINAL) && has(newAccess, Opcodes.ACC_FINAL) ) { 119 | return true; // 13.4.2 #1 120 | } else { 121 | final int compatibleChanges = Opcodes.ACC_ABSTRACT | // 13.4.1 #2 122 | Opcodes.ACC_FINAL ; // 13.4.2 #2 123 | // FIXME Opcodes.ACC_VOLATILE ? 124 | final int oldAccess2 = oldAccess & ~compatibleChanges; 125 | final int newAccess2 = newAccess & ~compatibleChanges; 126 | return oldAccess2 != newAccess2; 127 | } 128 | } 129 | 130 | /** 131 | * Returns whether a field's newAccess is incompatible with oldAccess 132 | * following Java Language Specification, Java SE 7 Edition: 133 | *
    134 | *
  • 13.4.7 Access to Members and Constructors
      135 | *
    • Changing the declared access of a member or constructor to permit less access 136 | * may break compatibility with pre-existing binaries, causing a linkage error to be thrown when these binaries are resolved. 137 | *
    • 138 | *
    • The binary format is defined so that changing a member or constructor to be more accessible does not cause a 139 | * linkage error when a subclass (already) defines a method to have less access. 140 | *
    • 141 | *
  • 142 | *
  • 13.4.9 final Fields and Constants
      143 | *
    • If a field that was not declared final is changed to be declared final, 144 | * then it can break compatibility with pre-existing binaries that attempt to assign new values to the field.
    • 145 | *
    • Deleting the keyword final or changing the value to which a non-final field is initialized 146 | * does not break compatibility with existing binaries.
    • 147 | *
    • If a field is a constant variable (§4.12.4), 148 | * then deleting the keyword final or changing its value 149 | * will not break compatibility with pre-existing binaries by causing them not to run, 150 | * but they will not see any new value for the usage of the field unless they are recompiled. 151 | * This is true even if the usage itself is not a compile-time constant expression (§15.28). 152 | * Such changes break functional backward compatibility!
    • 153 | *
  • 154 | *
  • 13.4.10 static Fields
      155 | *
    • If a field that is not declared private was not declared static 156 | * and is changed to be declared static, or vice versa, 157 | * then a linkage error, specifically an IncompatibleClassChangeError, 158 | * will result if the field is used by a pre-existing binary which expected a field of the other kind. 159 | * Such changes break backward compatibility!
    • 160 | *
  • 161 | *
  • 13.4.11. transient Fields
      162 | *
    • Adding or deleting a transient modifier of a field 163 | * does not break compatibility with pre-existing binaries.
    • 164 | *
  • 165 | *
  • 13.4.11 volatile Fields (JLS 1.0)
      166 | *
    • If a field that is not declared private was not declared volatile 167 | * and is changed to be declared volatile, or vice versa, then a linkage time error, 168 | * specifically an IncompatibleClassChangeError, may result if the field is used 169 | * by a preexisting binary that expected a field of the opposite volatility. 170 | * Such changes break backward compatibility!
    • 171 | *
  • 172 | *
173 | * 174 | * @param oldAccess 175 | * @param newAccess 176 | * @return 177 | */ 178 | public static boolean isFieldAccessChange(final int oldAccess, final int newAccess) { 179 | if (isAccessIncompatible(oldAccess, newAccess)) { 180 | return true; // 13.4.7 181 | } 182 | if ( not(oldAccess, Opcodes.ACC_FINAL) && has(newAccess, Opcodes.ACC_FINAL) ) { 183 | return true; // 13.4.9 #1 184 | } else { 185 | final int compatibleChanges = Opcodes.ACC_FINAL | // 13.4.9 #2 186 | Opcodes.ACC_TRANSIENT; // 13.4.11 #1 187 | final int accessPermissions = Opcodes.ACC_PUBLIC | 188 | Opcodes.ACC_PROTECTED | 189 | Opcodes.ACC_PRIVATE; 190 | final int oldAccess2 = oldAccess & ~compatibleChanges & ~accessPermissions; 191 | final int newAccess2 = newAccess & ~compatibleChanges & ~accessPermissions; 192 | return oldAccess2 != newAccess2; 193 | } 194 | } 195 | 196 | /** 197 | * Returns whether a method's newAccess is incompatible with oldAccess 198 | * following Java Language Specification, Java SE 7 Edition: 199 | *
    200 | *
  • 13.4.7 Access to Members and Constructors
      201 | *
    • Changing the declared access of a member or constructor to permit less access 202 | * may break compatibility with pre-existing binaries, causing a linkage error to be thrown when these binaries are resolved. 203 | *
    • 204 | *
    • The binary format is defined so that changing a member or constructor to be more accessible does not cause a 205 | * linkage error when a subclass (already) defines a method to have less access. 206 | *
    • 207 | *
  • 208 | *
  • 13.4.16 abstract Methods
      209 | *
    • Changing a method that is declared abstract to no longer be declared abstract 210 | * does not break compatibility with pre-existing binaries.
    • 211 | *
    • Changing a method that is not declared abstract to be declared abstract 212 | * will break compatibility with pre-existing binaries that previously invoked the method, causing an AbstractMethodError.
    • 213 | *
  • 214 | *
  • 13.4.17 final
      215 | *
    • Changing a method that is declared final to no longer be declared final 216 | * does not break compatibility with pre-existing binaries.
    • 217 | *
    • Changing an instance method that is not declared final to be declared final 218 | * may break compatibility with existing binaries that depend on the ability to override the method.
    • 219 | *
    • Changing a class (static) method that is not declared final to be declared final 220 | * does not break compatibility with existing binaries, because the method could not have been overridden.
    • 221 | *
  • 222 | *
  • 13.4.18 native Methods
      223 | *
    • Adding or deleting a native modifier of a method 224 | * does not break compatibility with pre-existing binaries.
    • 225 | *
  • 226 | *
  • 13.4.19 static Methods
      227 | *
    • If a method that is not declared private is also declared static (that is, a class method) 228 | * and is changed to not be declared static (that is, to an instance method), or vice versa, 229 | * then compatibility with pre-existing binaries may be broken, resulting in a linkage time error, 230 | * namely an IncompatibleClassChangeError, if these methods are used by the pre-existing binaries. 231 | * Such changes break functional backward compatibility!
    • 232 | *
  • 233 | *
  • 13.4.20 synchronized Methods
      234 | *
    • Adding or deleting a synchronized modifier of a method 235 | * does not break compatibility with pre-existing binaries.
    • 236 | *
  • 237 | *
  • 13.4.21 Method and Constructor Throws
      238 | *
    • Changes to the throws clause of methods or constructors 239 | * do not break compatibility with pre-existing binaries; these clauses are checked only at compile time.
    • 240 | *
  • 241 | *
242 | * 243 | * @param oldAccess 244 | * @param newAccess 245 | * @return 246 | */ 247 | public static boolean isMethodAccessChange(final int oldAccess, final int newAccess) { 248 | if (isAccessIncompatible(oldAccess, newAccess)) { 249 | return true; // 13.4.7 250 | } 251 | if ( not(oldAccess, Opcodes.ACC_ABSTRACT) && has(newAccess, Opcodes.ACC_ABSTRACT) ) { 252 | return true; // 13.4.16 #2 253 | } else if ( not(oldAccess, Opcodes.ACC_FINAL) && not(oldAccess, Opcodes.ACC_STATIC) && 254 | has(newAccess, Opcodes.ACC_FINAL) ) { 255 | return true; // 13.4.17 #2 excluding and #3 256 | } else { 257 | final int compatibleChanges = Opcodes.ACC_ABSTRACT | // 13.4.16 #1 258 | Opcodes.ACC_FINAL | // 13.4.17 #1 259 | Opcodes.ACC_NATIVE | // 13.4.18 #1 260 | Opcodes.ACC_SYNCHRONIZED; // 13.4.20 #1 261 | final int accessPermissions = Opcodes.ACC_PUBLIC | 262 | Opcodes.ACC_PROTECTED | 263 | Opcodes.ACC_PRIVATE; 264 | final int oldAccess2 = oldAccess & ~compatibleChanges & ~accessPermissions; 265 | final int newAccess2 = newAccess & ~compatibleChanges & ~accessPermissions; 266 | return oldAccess2 != newAccess2; 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /api/src/main/java/org/semver/Comparer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver; 18 | 19 | import javax.annotation.concurrent.NotThreadSafe; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.util.Set; 23 | 24 | import org.osjava.jardiff.DiffCriteria; 25 | import org.osjava.jardiff.DiffException; 26 | import org.osjava.jardiff.JarDiff; 27 | import org.semver.jardiff.DifferenceAccumulatingHandler; 28 | 29 | /** 30 | * 31 | * Allows to compare content of JARs. 32 | * 33 | */ 34 | @NotThreadSafe 35 | public class Comparer { 36 | 37 | private final DiffCriteria diffCriteria; 38 | private final File previousJAR; 39 | private final File currentJAR; 40 | private final Set includes; 41 | private final boolean includesAreRegExp; 42 | private final Set excludes; 43 | private final boolean excludesAreRegExp; 44 | 45 | public Comparer(final DiffCriteria diffCriteria, final File previousJAR, final File currentJAR, 46 | final Set includes, final Set excludes) { 47 | this(diffCriteria, previousJAR, currentJAR, includes, false, excludes, false); 48 | } 49 | 50 | public Comparer(final DiffCriteria diffCriteria, final File previousJAR, final File currentJAR, 51 | final Set includes, final boolean includesAreRegExp, final Set excludes, final boolean excludesAreRegExp) { 52 | if (!previousJAR.isFile()) { 53 | throw new IllegalArgumentException("<"+previousJAR+"> is not a valid file"); 54 | } 55 | if (!currentJAR.isFile()) { 56 | throw new IllegalArgumentException("<"+currentJAR+"> is not a valid file"); 57 | } 58 | 59 | this.diffCriteria = diffCriteria; 60 | this.previousJAR = previousJAR; 61 | this.currentJAR = currentJAR; 62 | this.includes = includes; 63 | this.includesAreRegExp = includesAreRegExp; 64 | this.excludes = excludes; 65 | this.excludesAreRegExp = excludesAreRegExp; 66 | } 67 | 68 | /** 69 | * @return all {@link Difference} between both JARs 70 | * @throws IOException 71 | */ 72 | public final Delta diff() throws IOException { 73 | try { 74 | final JarDiff jarDiff = new JarDiff(); 75 | jarDiff.loadOldClasses(this.previousJAR); 76 | jarDiff.loadNewClasses(this.currentJAR); 77 | final DifferenceAccumulatingHandler handler = new DifferenceAccumulatingHandler(this.includes, this.includesAreRegExp, this.excludes, this.excludesAreRegExp); 78 | jarDiff.diff(handler, diffCriteria); 79 | return handler.getDelta(); 80 | } catch (DiffException e) { 81 | throw new RuntimeException(e); 82 | } 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /api/src/main/java/org/semver/Delta.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver; 18 | 19 | import java.util.Collections; 20 | import java.util.Set; 21 | import javax.annotation.Nonnull; 22 | import javax.annotation.concurrent.Immutable; 23 | 24 | import org.osjava.jardiff.AbstractInfo; 25 | 26 | /** 27 | * 28 | * Encapsulates differences between two sets of classes. 29 | *
30 | * Provides convenient methods to validate that chosen {@link Version} are correct. 31 | * 32 | */ 33 | @Immutable 34 | public final class Delta { 35 | 36 | /** 37 | * Library compatibility type. From most compatible to less compatible. 38 | */ 39 | public enum CompatibilityType { 40 | 41 | BACKWARD_COMPATIBLE_IMPLEMENTER, 42 | 43 | BACKWARD_COMPATIBLE_USER, 44 | 45 | NON_BACKWARD_COMPATIBLE 46 | } 47 | 48 | @Immutable 49 | public static class Difference implements Comparable { 50 | 51 | private final String className; 52 | private final AbstractInfo info; 53 | 54 | public Difference(@Nonnull final String className, @Nonnull final AbstractInfo info) { 55 | if (className == null) { 56 | throw new IllegalArgumentException("null className"); 57 | } 58 | if (info == null) { 59 | throw new IllegalArgumentException("null info"); 60 | } 61 | 62 | this.className = className; 63 | this.info = info; 64 | } 65 | 66 | @Nonnull 67 | public String getClassName() { 68 | return this.className; 69 | } 70 | 71 | @Nonnull 72 | public AbstractInfo getInfo() { 73 | return info; 74 | } 75 | 76 | @Override 77 | public int compareTo(final Difference other) { 78 | return getClassName().compareTo(other.getClassName()); 79 | } 80 | 81 | } 82 | 83 | @Immutable 84 | public static class Add extends Difference { 85 | 86 | public Add(@Nonnull final String className, @Nonnull final AbstractInfo info) { 87 | super(className, info); 88 | } 89 | 90 | } 91 | 92 | @Immutable 93 | public static class Change extends Difference { 94 | 95 | private final AbstractInfo modifiedInfo; 96 | 97 | public Change(@Nonnull final String className, @Nonnull final AbstractInfo info, @Nonnull final AbstractInfo modifiedInfo) { 98 | super(className, info); 99 | 100 | this.modifiedInfo = modifiedInfo; 101 | } 102 | 103 | public AbstractInfo getModifiedInfo() { 104 | return this.modifiedInfo; 105 | } 106 | 107 | } 108 | 109 | @Immutable 110 | public static class Deprecate extends Difference { 111 | 112 | private final AbstractInfo modifiedInfo; 113 | 114 | public Deprecate(@Nonnull final String className, 115 | @Nonnull final AbstractInfo info, 116 | @Nonnull final AbstractInfo modifiedInfo) { 117 | super(className, info); 118 | 119 | this.modifiedInfo = modifiedInfo; 120 | } 121 | 122 | public AbstractInfo getModifiedInfo() { 123 | return this.modifiedInfo; 124 | } 125 | } 126 | 127 | @Immutable 128 | public static class Remove extends Difference { 129 | 130 | public Remove(@Nonnull final String className, @Nonnull final AbstractInfo info) { 131 | super(className, info); 132 | } 133 | 134 | } 135 | 136 | private final Set differences; 137 | 138 | public Delta(@Nonnull final Set differences) { 139 | this.differences = Collections.unmodifiableSet(differences); 140 | } 141 | 142 | @Nonnull 143 | public final Set getDifferences() { 144 | return this.differences; 145 | } 146 | 147 | /** 148 | * @param differences 149 | * @return {@link CompatibilityType} based on specified {@link Difference} 150 | */ 151 | @Nonnull 152 | public final CompatibilityType computeCompatibilityType() { 153 | 154 | if (contains(this.differences, Change.class) || 155 | contains(this.differences, Remove.class)) { 156 | return CompatibilityType.NON_BACKWARD_COMPATIBLE; 157 | } else if (contains(this.differences, Add.class) || 158 | contains(this.differences, Deprecate.class)) { 159 | return CompatibilityType.BACKWARD_COMPATIBLE_USER; 160 | } else { 161 | return CompatibilityType.BACKWARD_COMPATIBLE_IMPLEMENTER; 162 | } 163 | } 164 | 165 | protected final boolean contains(final Set differences, final Class type) { 166 | for (final Difference difference : differences) { 167 | if (type.isInstance(difference)) { 168 | return true; 169 | } 170 | } 171 | return false; 172 | } 173 | 174 | /** 175 | * 176 | * Infers next {@link Version} depending on provided {@link CompatibilityType}. 177 | * 178 | * @param version 179 | * @param compatibilityType 180 | * @return 181 | */ 182 | @Nonnull 183 | public static Version inferNextVersion(@Nonnull final Version version, @Nonnull final CompatibilityType compatibilityType) { 184 | if (version == null) { 185 | throw new IllegalArgumentException("null version"); 186 | } 187 | if (compatibilityType == null) { 188 | throw new IllegalArgumentException("null compatibilityType"); 189 | } 190 | 191 | switch (compatibilityType) { 192 | case BACKWARD_COMPATIBLE_IMPLEMENTER: 193 | return version.next(Version.Element.PATCH); 194 | case BACKWARD_COMPATIBLE_USER: 195 | return version.next(Version.Element.MINOR); 196 | case NON_BACKWARD_COMPATIBLE: 197 | return version.next(Version.Element.MAJOR); 198 | default: 199 | throw new IllegalArgumentException("Unknown type <"+compatibilityType+">"); 200 | } 201 | } 202 | 203 | /** 204 | * @param previous 205 | * @return an inferred {@link Version} for current JAR based on previous JAR content/version. 206 | * @throws IOException 207 | */ 208 | @Nonnull 209 | public final Version infer(@Nonnull final Version previous) { 210 | if (previous == null) { 211 | throw new IllegalArgumentException("null previous"); 212 | } 213 | if (previous.isInDevelopment()) { 214 | throw new IllegalArgumentException("Cannot infer for in development version <"+previous+">"); 215 | } 216 | 217 | final CompatibilityType compatibilityType = computeCompatibilityType(); 218 | return inferNextVersion(previous, compatibilityType); 219 | } 220 | 221 | /** 222 | * @param previous 223 | * @param current 224 | * @return true if {@link Version} provided for current JAR is compatible with previous JAR content/version. 225 | * @throws IOException 226 | */ 227 | public final boolean validate(@Nonnull final Version previous, @Nonnull final Version current) { 228 | if (previous == null) { 229 | throw new IllegalArgumentException("null previous"); 230 | } 231 | if (current == null) { 232 | throw new IllegalArgumentException("null current"); 233 | } 234 | if (current.compareTo(previous) <= 0) { 235 | throw new IllegalArgumentException("Current version <"+previous+"> must be more recent than previous version <"+current+">."); 236 | } 237 | //When in development public API is not considered stable 238 | if (current.isInDevelopment()) { 239 | return true; 240 | } 241 | 242 | //Current version must be superior or equals to inferred version 243 | final Version inferredVersion = infer(previous); 244 | // if the current version is a pre-release then the corresponding release need to be superior or equal 245 | return current.toReleaseVersion().compareTo(inferredVersion) >= 0; 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /api/src/main/java/org/semver/Dumper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver; 18 | 19 | import java.io.PrintStream; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.Iterator; 24 | import java.util.LinkedList; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Set; 28 | 29 | import org.osjava.jardiff.AbstractInfo; 30 | import org.osjava.jardiff.ClassInfo; 31 | import org.semver.Delta.Difference; 32 | import org.semver.Delta.Change; 33 | 34 | /** 35 | * 36 | * Helper methods to dump {@link Delta}. 37 | * 38 | */ 39 | public class Dumper { 40 | 41 | private Dumper() { 42 | } 43 | 44 | protected static String extractActionType(final Difference difference) { 45 | final String actionType = difference.getClass().getSimpleName(); 46 | return actionType.endsWith("e")?actionType+"d":actionType+"ed"; 47 | } 48 | 49 | protected static String extractInfoType(final AbstractInfo info) { 50 | final String simpleClassName = info.getClass().getSimpleName(); 51 | return simpleClassName.substring(0, simpleClassName.indexOf("Info")); 52 | } 53 | 54 | protected static String extractDetails(final Difference difference) { 55 | if (difference instanceof Change) { 56 | final Change change = (Change) difference; 57 | return extractDetails(difference.getInfo())+", access "+extractAccessDetails(difference.getInfo(), change.getModifiedInfo()); 58 | } else { 59 | return extractDetails(difference.getInfo())+", access "+extractAccessDetails(difference.getInfo()); 60 | } 61 | } 62 | 63 | protected static String extractDetails(final AbstractInfo info) { 64 | final StringBuilder builder = new StringBuilder(); 65 | if (!(info instanceof ClassInfo)) { 66 | builder.append(info.getName()); 67 | if( null != info.getSignature() ) { 68 | builder.append(", sig ").append(info.getSignature()); 69 | } 70 | if( null != info.getDesc() ) { 71 | builder.append(", desc ").append(info.getDesc()); 72 | } 73 | } 74 | return builder.toString(); 75 | } 76 | 77 | protected static void accumulateAccessDetails(final String access, final boolean previousAccess, final boolean currentAccess, final List added, final List removed) { 78 | if (previousAccess != currentAccess) { 79 | if (previousAccess) { 80 | removed.add(access); 81 | } else { 82 | added.add(access); 83 | } 84 | } 85 | } 86 | 87 | protected static String extractAccessDetails(final AbstractInfo previousInfo, final AbstractInfo currentInfo) { 88 | final List added = new LinkedList(); 89 | final List removed = new LinkedList(); 90 | accumulateAccessDetails("abstract", previousInfo.isAbstract(), currentInfo.isAbstract(), added, removed); 91 | accumulateAccessDetails("annotation", previousInfo.isAnnotation(), currentInfo.isAnnotation(), added, removed); 92 | accumulateAccessDetails("bridge", previousInfo.isBridge(), currentInfo.isBridge(), added, removed); 93 | accumulateAccessDetails("enum", previousInfo.isEnum(), currentInfo.isEnum(), added, removed); 94 | accumulateAccessDetails("final", previousInfo.isFinal(), currentInfo.isFinal(), added, removed); 95 | accumulateAccessDetails("interface", previousInfo.isInterface(), currentInfo.isInterface(), added, removed); 96 | accumulateAccessDetails("native", previousInfo.isNative(), currentInfo.isNative(), added, removed); 97 | accumulateAccessDetails("package-private", previousInfo.isPackagePrivate(), currentInfo.isPackagePrivate(), added, removed); 98 | accumulateAccessDetails("private", previousInfo.isPrivate(), currentInfo.isPrivate(), added, removed); 99 | accumulateAccessDetails("protected", previousInfo.isProtected(), currentInfo.isProtected(), added, removed); 100 | accumulateAccessDetails("public", previousInfo.isPublic(), currentInfo.isPublic(), added, removed); 101 | accumulateAccessDetails("static", previousInfo.isStatic(), currentInfo.isStatic(), added, removed); 102 | accumulateAccessDetails("strict", previousInfo.isStrict(), currentInfo.isStrict(), added, removed); 103 | accumulateAccessDetails("super", previousInfo.isSuper(), currentInfo.isSuper(), added, removed); 104 | accumulateAccessDetails("synchronized", previousInfo.isSynchronized(), currentInfo.isSynchronized(), added, removed); 105 | accumulateAccessDetails("synthetic", previousInfo.isSynthetic(), currentInfo.isSynthetic(), added, removed); 106 | accumulateAccessDetails("transcient", previousInfo.isTransient(), currentInfo.isTransient(), added, removed); 107 | accumulateAccessDetails("varargs", previousInfo.isVarargs(), currentInfo.isVarargs(), added, removed); 108 | accumulateAccessDetails("volatile", previousInfo.isVolatile(), currentInfo.isVolatile(), added, removed); 109 | final StringBuilder details = new StringBuilder(); 110 | if (!added.isEmpty()) { 111 | details.append("added: "); 112 | for (final String access : added) { 113 | details.append(access).append(" "); 114 | } 115 | } 116 | if (!removed.isEmpty()) { 117 | details.append("removed: "); 118 | for (final String access : removed) { 119 | details.append(access).append(" "); 120 | } 121 | } 122 | return details.toString().trim(); 123 | } 124 | 125 | protected static void accumulateAccessDetails(final String access, final boolean hasAccess, final List accessList) { 126 | if (hasAccess) { 127 | accessList.add(access); 128 | } 129 | } 130 | 131 | protected static String extractAccessDetails(final AbstractInfo info) { 132 | final List accessList = new LinkedList(); 133 | accumulateAccessDetails("abstract", info.isAbstract(), accessList); 134 | accumulateAccessDetails("annotation", info.isAnnotation(), accessList); 135 | accumulateAccessDetails("bridge", info.isBridge(), accessList); 136 | accumulateAccessDetails("enum", info.isEnum(), accessList); 137 | accumulateAccessDetails("final", info.isFinal(), accessList); 138 | accumulateAccessDetails("interface", info.isInterface(), accessList); 139 | accumulateAccessDetails("native", info.isNative(), accessList); 140 | accumulateAccessDetails("package-private", info.isPackagePrivate(), accessList); 141 | accumulateAccessDetails("private", info.isPrivate(), accessList); 142 | accumulateAccessDetails("protected", info.isProtected(), accessList); 143 | accumulateAccessDetails("public", info.isPublic(), accessList); 144 | accumulateAccessDetails("static", info.isStatic(), accessList); 145 | accumulateAccessDetails("strict", info.isStrict(), accessList); 146 | accumulateAccessDetails("super", info.isSuper(), accessList); 147 | accumulateAccessDetails("synchronized", info.isSynchronized(), accessList); 148 | accumulateAccessDetails("synthetic", info.isSynthetic(), accessList); 149 | accumulateAccessDetails("transcient", info.isTransient(), accessList); 150 | accumulateAccessDetails("varargs", info.isVarargs(), accessList); 151 | accumulateAccessDetails("volatile", info.isVolatile(), accessList); 152 | final StringBuilder details = new StringBuilder(); 153 | if (!accessList.isEmpty()) { 154 | for (final String access : accessList) { 155 | details.append(access).append(" "); 156 | } 157 | } 158 | return details.toString().trim(); 159 | } 160 | 161 | /** 162 | * 163 | * Dumps on {@link System#out} all differences, sorted by class name. 164 | * 165 | * @param delta the delta to be dumped 166 | */ 167 | public static void dump(final Delta delta) { 168 | dump(delta, System.out); 169 | } 170 | 171 | /** 172 | * Dumps on out all differences, sorted by class name. 173 | * 174 | * @param delta the delta to be dumped 175 | * @param out 176 | */ 177 | public static void dump(final Delta delta, final PrintStream out) { 178 | final List sortedDifferences = new LinkedList(delta.getDifferences()); 179 | Collections.sort(sortedDifferences); 180 | dump(sortedDifferences, out); 181 | } 182 | 183 | /** 184 | * Dumps on out all of the given sorted differences. 185 | * 186 | * @param sortedDifferences the sorted differences to be dumped 187 | * @param out the print output stream 188 | */ 189 | public static void dump(final List sortedDifferences, final PrintStream out) { 190 | String currentClassName = ""; 191 | for (final Difference difference : sortedDifferences) { 192 | if (!currentClassName.equals(difference.getClassName())) { 193 | out.println("Class "+difference.getClassName()); 194 | } 195 | out.println(" "+extractActionType(difference)+" "+extractInfoType(difference.getInfo())+" "+extractDetails(difference)); 196 | currentClassName = difference.getClassName(); 197 | } 198 | } 199 | 200 | /** 201 | * Dumps on out all differences separated by its type in the order 202 | * remove, change, deprecate and add. 203 | *

204 | * Prepends statistics per class regarding difference type. 205 | *

206 | * @param delta the delta to be dumped 207 | * @param iwidth the integer width for formated integer counter 208 | * @param out the print output stream 209 | */ 210 | public static void dumpFullStats(final Delta delta, final int iwidth, final PrintStream out) { 211 | final Set diffs = delta.getDifferences(); 212 | 213 | final List diffsAdd = new ArrayList(); 214 | final List diffsChange = new ArrayList(); 215 | final List diffsDeprecate = new ArrayList(); 216 | final List diffsRemove = new ArrayList(); 217 | final Map className2DiffCount = new HashMap(); 218 | 219 | int maxClassNameLen = 0; 220 | 221 | for(final Iterator iter = diffs.iterator(); iter.hasNext(); ) { 222 | final Difference diff = iter.next(); 223 | final String className = diff.getClassName(); 224 | maxClassNameLen = Math.max(maxClassNameLen, className.length()); 225 | 226 | DiffCount dc = className2DiffCount.get(className); 227 | if( null == dc ) { 228 | dc = new DiffCount(className); 229 | className2DiffCount.put(className, dc); 230 | } 231 | 232 | if( diff instanceof Delta.Add ) { 233 | diffsAdd.add(diff); 234 | dc.additions++; 235 | } else if( diff instanceof Delta.Change ) { 236 | diffsChange.add(diff); 237 | dc.changes++; 238 | } else if( diff instanceof Delta.Deprecate ) { 239 | diffsDeprecate.add(diff); 240 | dc.deprecates++; 241 | } else if( diff instanceof Delta.Remove ) { 242 | diffsRemove.add(diff); 243 | dc.removes++; 244 | } 245 | } 246 | Collections.sort(diffsAdd); 247 | Collections.sort(diffsChange); 248 | Collections.sort(diffsDeprecate); 249 | Collections.sort(diffsRemove); 250 | 251 | final List classNames = new ArrayList(className2DiffCount.keySet()); 252 | Collections.sort(classNames); 253 | 254 | System.err.println("Summary: "+diffs.size()+" differences in "+classNames.size()+" classes:"); 255 | System.err.println(" Remove "+diffsRemove.size()+ 256 | ", Change "+diffsChange.size()+ 257 | ", Deprecate "+diffsDeprecate.size()+ 258 | ", Add "+diffsAdd.size()); 259 | System.err.printf("%n"); 260 | 261 | int iterI = 0; 262 | for(final Iterator iter = classNames.iterator(); iter.hasNext(); iterI++) { 263 | final String className = iter.next(); 264 | final DiffCount dc = className2DiffCount.get(className); 265 | System.err.printf("%"+iwidth+"d/%"+iwidth+"d: %-"+maxClassNameLen+"s: %s%n", iterI, classNames.size(), className, dc.format(iwidth)); 266 | } 267 | 268 | System.err.printf("%n%nRemoves%n%n"); 269 | dump(diffsRemove, System.err); 270 | 271 | System.err.printf("%n%nChanges%n%n"); 272 | dump(diffsChange, System.err); 273 | 274 | System.err.printf("%n%nDeprecates%n%n"); 275 | dump(diffsDeprecate, System.err); 276 | 277 | System.err.printf("%n%nAdditions%n%n"); 278 | dump(diffsAdd, System.err); 279 | System.err.printf("%n%n"); 280 | } 281 | 282 | static class DiffCount { 283 | public DiffCount(String name) { this.name = name; } 284 | public final String name; 285 | public int removes; 286 | public int changes; 287 | public int deprecates; 288 | public int additions; 289 | public String toString() { return name+": Remove "+removes+", Change "+changes+", Deprecate "+deprecates+", Add "+additions; } 290 | public String format(final int iwidth) { 291 | return String.format("Remove %"+iwidth+"d, Change %"+iwidth+"d, Deprecate %"+iwidth+"d, Add %"+iwidth+"d", 292 | removes, changes, deprecates, additions); 293 | } 294 | } 295 | 296 | } 297 | -------------------------------------------------------------------------------- /api/src/main/java/org/semver/Main.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.util.Arrays; 22 | import java.util.LinkedHashSet; 23 | import java.util.Set; 24 | 25 | import de.tototec.cmdoption.CmdOption; 26 | import de.tototec.cmdoption.CmdlineParser; 27 | import de.tototec.cmdoption.CmdlineParserException; 28 | import org.osjava.jardiff.DiffCriteria; 29 | import org.osjava.jardiff.PublicDiffCriteria; 30 | import org.osjava.jardiff.SimpleDiffCriteria; 31 | 32 | /** 33 | * 34 | * CLI interface. 35 | * 36 | */ 37 | public class Main { 38 | 39 | static class Config { 40 | @CmdOption(names = { "--help", "-h" }, description = "Show this help and exit.", isHelp = true) 41 | boolean help; 42 | 43 | @CmdOption(names = { "--diff", "-d" }, conflictsWith = { "--check", "--infer", "--validate" }, description = "Show the differences between two jars.") 44 | public boolean diff; 45 | 46 | @CmdOption(names = { "--check", "-c" }, conflictsWith = { "--diff", "--infer", "--validate" }, description = "Check the compatibility of two jars.") 47 | public boolean check; 48 | 49 | @CmdOption(names = {"--publicOnly", "-p"}, description = "Checks public members only") 50 | public boolean publicOnly; 51 | 52 | @CmdOption(names = { "--infer", "-i" }, requires = { "--base-version" }, conflictsWith = { "--diff", "--check", 53 | "--validate" }, description = "Infer the version of the new jar based on the previous jar.") 54 | public boolean infer; 55 | 56 | @CmdOption(names = { "--validate", "-v" }, requires = { "--base-version", "--new-version" }, conflictsWith = { 57 | "--diff", "--check", "--infer" }, description = "Validate that the versions of two jars fulfil the semver specification.") 58 | public boolean validate; 59 | 60 | @CmdOption(names = { "--base-jar" }, args = { "JAR" }, minCount = 1, description = "The base jar.") 61 | public String baseJar; 62 | 63 | @CmdOption(names = { "--new-jar" }, args = { "JAR" }, minCount = 1, description = "The new jar.") 64 | public String newJar; 65 | 66 | final Set includes = new LinkedHashSet(); 67 | 68 | @CmdOption(names = { "--includes" }, args = { "INCLUDE;..." }, description = "Semicolon separated list of full qualified class names to be included.") 69 | public void setIncludes(String includes) { 70 | if (includes != null) { 71 | this.includes.addAll(Arrays.asList(includes.split(";"))); 72 | } 73 | } 74 | 75 | final Set excludes = new LinkedHashSet(); 76 | 77 | @CmdOption(names = { "--excludes" }, args = { "EXCLUDE;..." }, description = "Semicolon separated list of full qualified class names to be excluded.") 78 | public void setExcludes(String excludes) { 79 | if (excludes != null) { 80 | this.excludes.addAll(Arrays.asList(excludes.split(";"))); 81 | } 82 | } 83 | 84 | @CmdOption(names = { "--base-version" }, args = { "VERSION" }, description = "Version of the base jar (given with --base-jar).") 85 | public String baseVersion; 86 | 87 | @CmdOption(names = { "--new-version" }, args = { "VERSION" }, description = "Version of the new jar (given with --new-jar).") 88 | public String newVersion; 89 | } 90 | 91 | public static void main(final String[] args) throws IOException { 92 | Config config = new Config(); 93 | CmdlineParser cmdlineParser = new CmdlineParser(config); 94 | // Load translations of command line descriptions 95 | cmdlineParser.setResourceBundle(Main.class.getPackage().getName() + ".Messages", Main.class.getClassLoader()); 96 | cmdlineParser.setProgramName("semver"); 97 | cmdlineParser.setAboutLine("Semantic Version validator."); 98 | try { 99 | cmdlineParser.parse(args); 100 | } catch (CmdlineParserException e) { 101 | System.err.println("Error: " + e.getLocalizedMessage() + "\nRun semver --help for help."); 102 | System.exit(1); 103 | } 104 | 105 | if (config.help) { 106 | cmdlineParser.usage(); 107 | System.exit(0); 108 | } 109 | 110 | final DiffCriteria diffCriteria = config.publicOnly ? new PublicDiffCriteria() : new SimpleDiffCriteria(); 111 | final Comparer comparer = new Comparer(diffCriteria, new File(config.baseJar), new File(config.newJar), 112 | config.includes, config.excludes); 113 | final Delta delta = comparer.diff(); 114 | 115 | if (config.diff) { 116 | Dumper.dump(delta); 117 | } 118 | 119 | if (config.check) { 120 | System.out.println(delta.computeCompatibilityType()); 121 | } 122 | 123 | if (config.infer) { 124 | System.out.println(delta.infer(Version.parse(config.baseVersion))); 125 | } 126 | 127 | if (config.validate) { 128 | System.out.println(delta.validate(Version.parse(config.baseVersion), Version.parse(config.newVersion))); 129 | } 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /api/src/main/java/org/semver/Version.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver; 18 | 19 | import java.util.regex.Matcher; 20 | import java.util.regex.Pattern; 21 | 22 | import javax.annotation.Nonnegative; 23 | import javax.annotation.Nonnull; 24 | import javax.annotation.Nullable; 25 | import javax.annotation.concurrent.Immutable; 26 | 27 | import org.apache.commons.lang.StringUtils; 28 | 29 | /** 30 | * 31 | * Version following semantic defined by Semantic Versioning document. 32 | * 33 | */ 34 | @Immutable 35 | public final class Version implements Comparable { 36 | 37 | /** 38 | * {@link Version} element. From most meaningful to less meaningful. 39 | */ 40 | public enum Element { 41 | MAJOR, MINOR, PATCH, SPECIAL; 42 | } 43 | 44 | private static final String FORMAT = "(\\d+)\\.(\\d+)(?:\\.)?(\\d*)(\\.|-|\\+)?([0-9A-Za-z-.]*)?"; 45 | private static final Pattern PATTERN = Pattern.compile(Version.FORMAT); 46 | 47 | private static final Pattern DIGITS_ONLY = Pattern.compile("\\d+"); 48 | 49 | private static final String SNAPSHOT_VERSION_SUFFIX = "SNAPSHOT"; 50 | 51 | private final int major; 52 | private final int minor; 53 | private final int patch; 54 | private final String separator; 55 | private final Special special; 56 | 57 | public Version(@Nonnegative final int major, @Nonnegative final int minor, @Nonnegative final int patch) { 58 | this(major, minor, patch, null, null); 59 | } 60 | 61 | public Version(@Nonnegative final int major, @Nonnegative final int minor, @Nonnegative final int patch, @Nullable final String separator, @Nullable final String special) { 62 | if (major < 0) { 63 | throw new IllegalArgumentException(Element.MAJOR+" must be positive"); 64 | } 65 | if (minor < 0) { 66 | throw new IllegalArgumentException(Element.MINOR+" must be positive"); 67 | } 68 | if (patch < 0) { 69 | throw new IllegalArgumentException(Element.PATCH+" must be positive"); 70 | } 71 | 72 | this.major = major; 73 | this.minor = minor; 74 | this.patch = patch; 75 | this.separator = separator; 76 | this.special = parseSpecial(special); 77 | } 78 | 79 | private Special parseSpecial(String specialString) { 80 | if (specialString == null) { 81 | return null; 82 | } 83 | Special special = new Special(specialString); 84 | if (special.ids.length == 0) { 85 | return null; 86 | } 87 | return special; 88 | } 89 | 90 | /** 91 | * 92 | * Creates a Version from a string representation. Must match Version#FORMAT. 93 | * 94 | * @param version 95 | * @return 96 | */ 97 | public static Version parse(@Nonnull final String version) { 98 | final Matcher matcher = Version.PATTERN.matcher(version); 99 | if (!matcher.matches()) { 100 | throw new IllegalArgumentException("<"+version+"> does not match format "+Version.FORMAT); 101 | } 102 | 103 | final int major = Integer.valueOf(matcher.group(1)); 104 | final int minor = Integer.valueOf(matcher.group(2)); 105 | final int patch; 106 | final String patchMatch = matcher.group(3); 107 | if (StringUtils.isNotEmpty(patchMatch)) { 108 | patch = Integer.valueOf(patchMatch); 109 | } else { 110 | patch = 0; 111 | } 112 | final String separator = matcher.group(4); 113 | final String special = matcher.group(5); 114 | return new Version(major, minor, patch, separator, "".equals(special) ? null : special); 115 | } 116 | 117 | /** 118 | * @param type 119 | * @return next {@link Version} regarding specified {@link Version.Element} 120 | */ 121 | public Version next(@Nonnull final Version.Element element) { 122 | if (element == null) { 123 | throw new IllegalArgumentException("null element"); 124 | } 125 | 126 | switch (element) { 127 | case MAJOR: 128 | if (special == null || this.minor != 0 || this.patch != 0) { 129 | return new Version(this.major + 1, 0, 0); 130 | } else { 131 | return new Version(this.major, 0, 0); 132 | } 133 | case MINOR: 134 | if (special == null || this.patch != 0) { 135 | return new Version(this.major, this.minor + 1, 0); 136 | } else { 137 | return new Version(this.major, this.minor, 0); 138 | } 139 | case PATCH: 140 | if (special == null) { 141 | return new Version(this.major, this.minor, this.patch + 1); 142 | } else { 143 | return new Version(this.major, this.minor, this.patch); 144 | } 145 | default: 146 | throw new IllegalArgumentException("Unknown element <"+element+">"); 147 | } 148 | } 149 | 150 | /** 151 | * if this is a pre-release version, returns the corresponding release 152 | * return the same version if already a release 153 | * @return a release version 154 | */ 155 | public Version toReleaseVersion() { 156 | return new Version(major, minor, patch); 157 | } 158 | 159 | public boolean isInDevelopment() { 160 | return this.major == 0; 161 | } 162 | 163 | public boolean isStable() { 164 | return !isInDevelopment(); 165 | } 166 | 167 | public boolean isSnapshot() { 168 | return this.special != null && this.special.isSnapshot(); 169 | } 170 | 171 | /** 172 | * @param version version to check with 173 | * @return {@code true}, if supplied version is compatible with this version, {@code false} - otherwise 174 | */ 175 | public boolean isCompatible(Version version) { 176 | return version != null && this.major == version.major; 177 | } 178 | 179 | @Override 180 | public int hashCode() { 181 | int hash = 5; 182 | hash = 43 * hash + this.major; 183 | hash = 43 * hash + this.minor; 184 | hash = 43 * hash + this.patch; 185 | hash = 43 * hash + (this.special != null ? this.special.hashCode() : 0); 186 | return hash; 187 | } 188 | 189 | @Override 190 | public boolean equals(@Nullable final Object object) { 191 | if (!(object instanceof Version)) { 192 | return false; 193 | } 194 | 195 | final Version other = (Version) object; 196 | if (other.major != this.major || other.minor != this.minor || other.patch != this.patch) { 197 | return false; 198 | } 199 | return (this.special == null) ? other.special == null : this.special.equals(other.special); 200 | } 201 | 202 | 203 | private static SpecialId parseSpecialId(String id) { 204 | Matcher matcher = DIGITS_ONLY.matcher(id); 205 | if (matcher.matches()) { 206 | return new IntId(Integer.parseInt(id)); 207 | } else { 208 | return new StringId(id); 209 | } 210 | } 211 | 212 | abstract private static class SpecialId implements Comparable { 213 | 214 | abstract public boolean isSnapshot(); 215 | 216 | abstract public int compareTo(IntId other); 217 | abstract public int compareTo(StringId other); 218 | } 219 | 220 | private static class StringId extends SpecialId { 221 | private final String id; 222 | private StringId(String id) { 223 | this.id = id; 224 | } 225 | @Override 226 | public boolean isSnapshot() { 227 | return id.endsWith(SNAPSHOT_VERSION_SUFFIX); 228 | } 229 | 230 | @Override 231 | public int compareTo(SpecialId other) { 232 | return - other.compareTo(this); 233 | } 234 | 235 | @Override 236 | public String toString() { 237 | return id; 238 | } 239 | @Override 240 | public int compareTo(IntId other) { 241 | // Numeric identifiers always have lower precedence than non-numeric identifiers. 242 | return 1; 243 | } 244 | @Override 245 | public int compareTo(StringId other) { 246 | return id.compareTo(other.id); 247 | } 248 | } 249 | 250 | private static class IntId extends SpecialId { 251 | private final int id; 252 | public IntId(int id) { 253 | this.id = id; 254 | } 255 | @Override 256 | public boolean isSnapshot() { 257 | return false; 258 | } 259 | 260 | @Override 261 | public String toString() { 262 | return String.valueOf(id); 263 | } 264 | @Override 265 | public int compareTo(SpecialId other) { 266 | return - other.compareTo(this); 267 | } 268 | 269 | @Override 270 | public int compareTo(IntId other) { 271 | return id - other.id; 272 | } 273 | @Override 274 | public int compareTo(StringId other) { 275 | //Numeric identifiers always have lower precedence than non-numeric identifiers. 276 | return -1; 277 | } 278 | } 279 | 280 | private static class Special implements Comparable { 281 | private final SpecialId[] ids; 282 | Special(String s) { 283 | String[] split = s.split("\\."); 284 | ids = new SpecialId[split.length]; 285 | for (int i = 0; i < split.length; i++) { 286 | ids[i] = parseSpecialId(split[i]); 287 | } 288 | } 289 | 290 | public SpecialId last() { 291 | return ids[ids.length - 1]; 292 | } 293 | 294 | public boolean isSnapshot() { 295 | return last().isSnapshot(); 296 | } 297 | 298 | @Override 299 | public int compareTo(Special other) { 300 | int min = Math.min(other.ids.length, ids.length); 301 | for (int i = 0; i < min; i++) { 302 | int c = ids[i].compareTo(other.ids[i]); 303 | if (c != 0) { 304 | return c; 305 | } 306 | } 307 | int max = Math.max(other.ids.length, ids.length); 308 | if (max != min) { 309 | if (ids.length > other.ids.length) { 310 | return 1; 311 | } else { 312 | return -1; 313 | } 314 | } 315 | return 0; 316 | } 317 | 318 | @Override 319 | public String toString() { 320 | final StringBuilder builder = new StringBuilder(); 321 | for (int i = 0; i < ids.length; i++) { 322 | SpecialId s = ids[i]; 323 | if (i != 0) { 324 | builder.append("."); 325 | } 326 | builder.append(s); 327 | } 328 | return builder.toString(); 329 | } 330 | } 331 | 332 | @Override 333 | public int compareTo(final Version other) { 334 | if (equals(other)) { 335 | return 0; 336 | } 337 | 338 | if (this.major < other.major) { 339 | return -1; 340 | } else if (this.major == other.major) { 341 | if (this.minor < other.minor) { 342 | return -1; 343 | } else if (this.minor == other.minor) { 344 | if (this.patch < other.patch) { 345 | return -1; 346 | } else if (this.patch == other.patch) { 347 | if (this.special != null && other.special != null) { 348 | return this.special.compareTo(other.special); 349 | } else if (other.special != null) { 350 | return 1; 351 | } else if (this.special != null) { 352 | return -1; 353 | } // else handled by previous equals check 354 | } 355 | } 356 | } 357 | return 1; //if this (major, minor or patch) is > than other 358 | } 359 | 360 | @Override 361 | public String toString() { 362 | final StringBuilder builder = new StringBuilder(); 363 | builder.append(this.major).append(".").append(this.minor).append(".").append(this.patch); 364 | if (this.separator != null) { 365 | builder.append(this.separator); 366 | } 367 | if (this.special != null) { 368 | builder.append(this.special); 369 | } 370 | return builder.toString(); 371 | } 372 | 373 | } 374 | -------------------------------------------------------------------------------- /api/src/main/java/org/semver/jardiff/DifferenceAccumulatingHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver.jardiff; 18 | 19 | import java.util.Collections; 20 | import java.util.HashSet; 21 | import java.util.Set; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | import javax.annotation.Nonnull; 26 | 27 | import org.osjava.jardiff.AbstractDiffHandler; 28 | import org.osjava.jardiff.ClassInfo; 29 | import org.osjava.jardiff.DiffException; 30 | import org.osjava.jardiff.FieldInfo; 31 | import org.osjava.jardiff.MethodInfo; 32 | import org.semver.Delta; 33 | import org.semver.Delta.Add; 34 | import org.semver.Delta.Change; 35 | import org.semver.Delta.Deprecate; 36 | import org.semver.Delta.Difference; 37 | import org.semver.Delta.Remove; 38 | 39 | /** 40 | * 41 | * {@link org.osjava.jardiff.DiffHandler} implementation accumulating {@link Difference}. 42 | * 43 | */ 44 | public final class DifferenceAccumulatingHandler extends AbstractDiffHandler { 45 | private String currentClassName; 46 | private final Set includes; 47 | private final boolean includesAreRegExp; 48 | private final Set excludes; 49 | private final boolean excludesAreRegExp; 50 | private final Set differences = new HashSet(); 51 | 52 | public DifferenceAccumulatingHandler() { 53 | this(Collections.emptySet(), Collections.emptySet()); 54 | } 55 | 56 | public DifferenceAccumulatingHandler(@Nonnull final Set includes, @Nonnull final Set excludes) { 57 | this(includes, false, excludes, false); 58 | } 59 | public DifferenceAccumulatingHandler(@Nonnull final Set includes, final boolean includesAreRegExp, 60 | @Nonnull final Set excludes, final boolean excludesAreRegExp) { 61 | this.includes = includes; 62 | this.includesAreRegExp = includesAreRegExp; 63 | this.excludes = excludes; 64 | this.excludesAreRegExp = excludesAreRegExp; 65 | } 66 | 67 | public String getCurrentClassName() { 68 | return this.currentClassName; 69 | } 70 | 71 | @Override 72 | public void startDiff(final String previous, final String current) throws DiffException { 73 | } 74 | 75 | @Override 76 | public void endDiff() throws DiffException { 77 | } 78 | 79 | @Override 80 | public void startOldContents() throws DiffException { 81 | } 82 | 83 | @Override 84 | public void endOldContents() throws DiffException { 85 | } 86 | 87 | @Override 88 | public void startNewContents() throws DiffException { 89 | } 90 | 91 | @Override 92 | public void endNewContents() throws DiffException { 93 | } 94 | 95 | @Override 96 | public void contains(final ClassInfo classInfo) throws DiffException { 97 | } 98 | 99 | @Override 100 | public void startAdded() throws DiffException { 101 | } 102 | 103 | @Override 104 | public void classAdded(final ClassInfo classInfo) throws DiffException { 105 | if (!isClassConsidered(classInfo.getName())) { 106 | return; 107 | } 108 | 109 | this.differences.add(new Add(getClassName(classInfo.getName()), classInfo)); 110 | } 111 | 112 | @Override 113 | public void fieldAdded(final FieldInfo fieldInfo) throws DiffException { 114 | if (!isClassConsidered(getCurrentClassName())) { 115 | return; 116 | } 117 | 118 | this.differences.add(new Add(getCurrentClassName(), fieldInfo)); 119 | } 120 | 121 | @Override 122 | public void methodAdded(final MethodInfo methodInfo) throws DiffException { 123 | if (!isClassConsidered(getCurrentClassName())) { 124 | return; 125 | } 126 | 127 | this.differences.add(new Add(getCurrentClassName(), methodInfo)); 128 | } 129 | 130 | @Override 131 | public void endAdded() throws DiffException { 132 | } 133 | 134 | @Override 135 | public void startChanged() throws DiffException { 136 | } 137 | 138 | @Override 139 | public void startClassChanged(final String className) throws DiffException { 140 | this.currentClassName = getClassName(className); 141 | } 142 | 143 | @Override 144 | public void classChanged(final ClassInfo oldClassInfo, final ClassInfo newClassInfo) throws DiffException { 145 | if (!isClassConsidered(oldClassInfo.getName())) { 146 | return; 147 | } 148 | 149 | this.differences.add(new Change(getClassName(oldClassInfo.getName()), oldClassInfo, newClassInfo)); 150 | } 151 | 152 | @Override 153 | public void classDeprecated(final ClassInfo oldClassInfo, final ClassInfo newClassInfo) throws DiffException { 154 | if (!isClassConsidered(oldClassInfo.getName())) { 155 | return; 156 | } 157 | 158 | this.differences.add(new Deprecate(getClassName(oldClassInfo.getName()), oldClassInfo, newClassInfo)); 159 | } 160 | 161 | @Override 162 | public void fieldChanged(final FieldInfo oldFieldInfo, final FieldInfo newFieldInfo) throws DiffException { 163 | if (!isClassConsidered(getCurrentClassName())) { 164 | return; 165 | } 166 | 167 | this.differences.add(new Change(getCurrentClassName(), oldFieldInfo, newFieldInfo)); 168 | } 169 | 170 | @Override 171 | public void fieldDeprecated(final FieldInfo oldFieldInfo, final FieldInfo newFieldInfo) throws DiffException { 172 | if (!isClassConsidered(getCurrentClassName())) { 173 | return; 174 | } 175 | 176 | this.differences.add(new Deprecate(getCurrentClassName(), oldFieldInfo, newFieldInfo)); 177 | } 178 | 179 | @Override 180 | public void methodChanged(final MethodInfo oldMethodInfo, final MethodInfo newMethodInfo) throws DiffException { 181 | if (!isClassConsidered(getCurrentClassName())) { 182 | return; 183 | } 184 | 185 | this.differences.add(new Change(getCurrentClassName(), oldMethodInfo, newMethodInfo)); 186 | } 187 | 188 | @Override 189 | public void methodDeprecated(final MethodInfo oldMethodInfo, final MethodInfo newMethodInfo) throws DiffException { 190 | if (!isClassConsidered(getCurrentClassName())) { 191 | return; 192 | } 193 | 194 | this.differences.add(new Deprecate(getCurrentClassName(), oldMethodInfo, newMethodInfo)); 195 | } 196 | 197 | @Override 198 | public void endClassChanged() throws DiffException { 199 | } 200 | 201 | @Override 202 | public void endChanged() throws DiffException { 203 | } 204 | 205 | @Override 206 | public void startRemoved() throws DiffException { 207 | } 208 | 209 | @Override 210 | public void classRemoved(final ClassInfo classInfo) throws DiffException { 211 | if (!isClassConsidered(classInfo.getName())) { 212 | return; 213 | } 214 | 215 | this.differences.add(new Remove(getClassName(classInfo.getName()), classInfo)); 216 | } 217 | 218 | @Override 219 | public void fieldRemoved(final FieldInfo fieldInfo) throws DiffException { 220 | if (!isClassConsidered(getCurrentClassName())) { 221 | return; 222 | } 223 | 224 | this.differences.add(new Remove(getCurrentClassName(), fieldInfo)); 225 | } 226 | 227 | @Override 228 | public void methodRemoved(final MethodInfo methodInfo) throws DiffException { 229 | if (!isClassConsidered(getCurrentClassName())) { 230 | return; 231 | } 232 | 233 | this.differences.add(new Remove(getCurrentClassName(), methodInfo)); 234 | } 235 | 236 | @Override 237 | public void endRemoved() throws DiffException { 238 | } 239 | 240 | /** 241 | * 242 | * Is considered a class whose package: 243 | * * is included 244 | * * is not excluded 245 | * 246 | * If includes are provided then package must be defined here. 247 | * 248 | * @return 249 | */ 250 | protected boolean isClassConsidered( final String className ) { 251 | // Fix case where class names are reported with '.' 252 | final String fixedClassName = className.replace('.', '/'); 253 | for ( String exclude : this.excludes ) { 254 | final Pattern excludePattern; 255 | if( !excludesAreRegExp ) { 256 | if ( exclude.contains( "/**/" ) ) { 257 | exclude = exclude.replaceAll( "/\\*\\*/", "{0,1}**/" ); 258 | } 259 | if ( exclude.contains( "/*/" ) ) { 260 | exclude = exclude.replaceAll( "/\\*/", "{0,1}*/{0,1}" ); 261 | } 262 | excludePattern = simplifyRegularExpression( exclude, false ); 263 | } else { 264 | excludePattern = Pattern.compile( exclude ); 265 | } 266 | final Matcher excludeMatcher = excludePattern.matcher( fixedClassName ); 267 | 268 | while ( excludeMatcher.find() ) { 269 | return false; 270 | } 271 | } 272 | if ( !this.includes.isEmpty() ) { 273 | for ( String include : this.includes ) { 274 | final Pattern includePattern; 275 | if( !includesAreRegExp ) { 276 | if ( include.contains( "/**/" ) ) { 277 | include = include.replaceAll( "/\\*\\*/", "{0,1}**/" ); 278 | } 279 | if ( include.contains( "/*/" ) ) { 280 | include = include.replaceAll( "/\\*/", "{0,1}*/{0,1}" ); 281 | } 282 | includePattern = simplifyRegularExpression( include, false ); 283 | } else { 284 | includePattern = Pattern.compile( include ); 285 | } 286 | final Matcher includeMatcher = includePattern.matcher( fixedClassName ); 287 | 288 | while ( includeMatcher.find() ) { 289 | return true; 290 | } 291 | } 292 | return false; 293 | } 294 | return true; 295 | } 296 | 297 | /** 298 | * 299 | * Simplifies the given regular expression by the following pattern:
300 | * All substrings not containing "{0,1}", "*" and "?" get surrounded by "\\Q" and "\\E". Then all occurrences of 301 | * "**" are replaced by ".*", "*" with "[^/]*" and all occurrences of "?" are replaced by "." In the end a "$" will 302 | * be appended. 303 | * 304 | * @param regEx the regular expression which is in a simple form. 305 | * @return the simple regular expression converted to a normal regular expression. 306 | */ 307 | private static Pattern simplifyRegularExpression( final String regEx, final boolean caseSensitive ) { 308 | final StringBuilder strBuild = new StringBuilder(); 309 | final Pattern p = Pattern.compile( "\\{0,1\\}|\\*|\\?|[[^*^?^{^}]|^]+", Pattern.CASE_INSENSITIVE ); 310 | final Matcher m = p.matcher( regEx ); 311 | 312 | while ( m.find() ) { 313 | final String token = m.group(); 314 | if ( token.equals( "*" ) || token.equals( "?" ) ) { //$NON-NLS-1$ //$NON-NLS-2$ 315 | strBuild.append( token ); 316 | } else if ( token.equals( "{0,1}" ) ) { 317 | strBuild.append( "/" ); 318 | strBuild.append( token ); 319 | } else { 320 | // Surround all tokens that are not "*" or "?" with "\\Q" and \\E" 321 | strBuild.append( "\\Q" ).append( token ).append( "\\E" ); //$NON-NLS-1$ //$NON-NLS-2$ 322 | } 323 | } 324 | // Replace all "*" and "?" with .* and .+ 325 | strBuild.append( "$" ); 326 | String result = strBuild.toString(); 327 | result = result.replaceAll( "(? EMPTY_DIFFERENCES = Collections.emptySet(); 42 | 43 | @Test 44 | public void inferVersion() { 45 | final int major = 1; 46 | final int minor = 2; 47 | final int patch = 3; 48 | final Version version = new Version(major, minor, patch); 49 | 50 | assertEquals(version.next(MAJOR), inferNextVersion(version, NON_BACKWARD_COMPATIBLE)); 51 | assertEquals(version.next(MINOR), inferNextVersion(version, BACKWARD_COMPATIBLE_USER)); 52 | assertEquals(version.next(PATCH), inferNextVersion(version, BACKWARD_COMPATIBLE_IMPLEMENTER)); 53 | } 54 | 55 | @Test(expected=IllegalArgumentException.class) 56 | public void shouldInferWithNullVersionFail() { 57 | inferNextVersion(null, BACKWARD_COMPATIBLE_IMPLEMENTER); 58 | } 59 | 60 | @Test(expected=IllegalArgumentException.class) 61 | public void shouldInferWithNullCompatibilityTypeFail() { 62 | inferNextVersion(new Version(1, 0, 0), null); 63 | } 64 | 65 | @Test(expected=IllegalArgumentException.class) 66 | public void shouldNullVersionNotBeInferable() { 67 | new Delta(EMPTY_DIFFERENCES).infer(null); 68 | } 69 | 70 | @Test(expected=IllegalArgumentException.class) 71 | public void shouldDevelopmentVersionNotBeInferable() { 72 | new Delta(EMPTY_DIFFERENCES).infer(new Version(0, 0, 0)); 73 | } 74 | 75 | @Test 76 | public void shouldEmptyDeltaBeImplementerBackwardCompatible() { 77 | final int major = 1; 78 | final int minor = 2; 79 | final int patch = 3; 80 | final Version version = new Version(major, minor, patch); 81 | 82 | final Version inferedVersion = new Delta(EMPTY_DIFFERENCES).infer(version); 83 | 84 | assertEquals(new Version(major, minor, patch+1), inferedVersion); 85 | } 86 | 87 | @Test 88 | public void shouldDeltaWithAddsBeUserBackwardCompatible() { 89 | final int major = 1; 90 | final int minor = 2; 91 | final int patch = 3; 92 | final Version version = new Version(major, minor, patch); 93 | 94 | final Version inferedVersion = new Delta(Collections.singleton(new Delta.Add("class", new FieldInfo(0, "", "", "", null)))).infer(version); 95 | 96 | assertEquals(new Version(major, minor+1, 0), inferedVersion); 97 | } 98 | 99 | @Test 100 | public void shouldDeltaWithChangesBeNonBackwardCompatible() { 101 | final int major = 1; 102 | final int minor = 2; 103 | final int patch = 3; 104 | final Version version = new Version(major, minor, patch); 105 | 106 | final Version inferedVersion = new Delta(Collections.singleton(new Delta.Change("class", new FieldInfo(0, "", "", "", null), new FieldInfo(0, "", "", "", null)))).infer(version); 107 | 108 | assertEquals(new Version(major+1, 0, 0), inferedVersion); 109 | } 110 | 111 | @Test 112 | public void shouldDeltaWithRemovesBeNonBackwardCompatible() { 113 | final int major = 1; 114 | final int minor = 2; 115 | final int patch = 3; 116 | final Version version = new Version(major, minor, patch); 117 | 118 | final Version inferedVersion = new Delta(Collections.singleton(new Delta.Remove("class", new FieldInfo(0, "", "", "", null)))).infer(version); 119 | 120 | assertEquals(new Version(major+1, 0, 0), inferedVersion); 121 | } 122 | 123 | @Test(expected=IllegalArgumentException.class) 124 | public void shouldValidateWithNullPreviousVersionFail() { 125 | new Delta(EMPTY_DIFFERENCES).validate(null, new Version(1, 0, 0)); 126 | } 127 | 128 | @Test(expected=IllegalArgumentException.class) 129 | public void shouldValidateWithNullCurrentVersionFail() { 130 | new Delta(EMPTY_DIFFERENCES).validate(new Version(1, 0, 0), null); 131 | } 132 | 133 | @Test 134 | public void shouldValidateWithCurrentVersionInDevelopmentSucceed() { 135 | validate(EMPTY_DIFFERENCES, new Version(0, 0, 0), new Version(0, 0, 1), true); 136 | } 137 | 138 | @Test(expected=IllegalArgumentException.class) 139 | public void shouldValidateWithPreviousVersionNextCurrentVersionFail() { 140 | new Delta(EMPTY_DIFFERENCES).validate(new Version(1, 1, 0), new Version(1, 0, 0)); 141 | } 142 | 143 | @Test(expected=IllegalArgumentException.class) 144 | public void shouldValidateWithPreviousVersionEqualsCurrentVersionFail() { 145 | new Delta(EMPTY_DIFFERENCES).validate(new Version(1, 0, 0), new Version(1, 0, 0)); 146 | } 147 | 148 | @Test 149 | public void shouldValidateWithCorrectVersionsSucceed() { 150 | validate(EMPTY_DIFFERENCES, new Version(1, 1, 0), new Version(1, 1, 1), true); 151 | } 152 | 153 | @Test 154 | public void shouldValidateWithCorrectPreVersionsSucceed() { 155 | validate(EMPTY_DIFFERENCES, new Version(1, 1, 0, "-", "rc1"), new Version(1, 1, 0, "-", "rc2"), true); 156 | } 157 | 158 | @Test 159 | public void shouldValidateWithIncorrectVersionFail() { 160 | validate(Collections.singleton(new Delta.Remove("class", new FieldInfo(0, "", "", "", null))), new Version(1, 1, 0), new Version(1, 1, 1), false); 161 | } 162 | 163 | @Test 164 | public void upgradeMinorVersionOnClassDeprecated() { 165 | validate(singleton(new Delta.Deprecate("class", new ClassInfo(1, 0, "", "", "", null, null, null), new ClassInfo(1, 0, "", "", "", null, null, null))), new Version(1, 1, 0), new Version(1, 2, 0), true); 166 | } 167 | 168 | @Test 169 | public void upgradeMinorVersionOnFieldDeprecated() { 170 | validate(singleton(new Delta.Deprecate("class", new FieldInfo(0, "", "", "", null), new FieldInfo(0, "", "", "", null))), new Version(1, 1, 0), new Version(1, 2, 0), true); 171 | } 172 | 173 | @Test 174 | public void upgradeMinorVersionOnMethodDeprecated() { 175 | validate(singleton(new Delta.Deprecate("class", new MethodInfo(0, "", "", "", null), new MethodInfo(0, "", "", "", null))), new Version(1, 1, 0), new Version(1, 2, 0), true); 176 | } 177 | 178 | private void validate(Set differences, Version previous, Version current, boolean valid) { 179 | assertEquals( 180 | "accept differences " + differences + " when changing version from " + previous + " to " + current, 181 | valid, 182 | new Delta(differences).validate(previous, current)); 183 | } 184 | } -------------------------------------------------------------------------------- /api/src/test/java/org/semver/VersionTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.junit.Assert; 23 | import org.junit.Test; 24 | 25 | public class VersionTest { 26 | 27 | @Test 28 | public void shouldNegativVersionBeRejected() { 29 | try { 30 | new Version(-1, 0, 0); 31 | Assert.fail(); 32 | } catch (IllegalArgumentException e) { 33 | } 34 | try { 35 | new Version(0, -1, 0); 36 | Assert.fail(); 37 | } catch (IllegalArgumentException e) { 38 | } 39 | try { 40 | new Version(0, 0, -1); 41 | Assert.fail(); 42 | } catch (IllegalArgumentException e) { 43 | } 44 | } 45 | 46 | @Test 47 | public void shouldValidVersionBeParsed() { 48 | Version.parse("1.2"); 49 | Version.parse("1.2.3"); 50 | Version.parse("10.20.30"); 51 | Version.parse("1.2.3beta"); 52 | Version.parse("1.2.3.DEV"); 53 | Version.parse("1.2.3.DEV-SNAPSHOT"); 54 | Version.parse("1.2-SNAPSHOT"); 55 | Version.parse("1.2.3-SNAPSHOT"); 56 | Version.parse("1.2.3-RC-SNAPSHOT"); 57 | Version.parse("1.2-RC-SNAPSHOT"); 58 | } 59 | 60 | 61 | 62 | @Test(expected=IllegalArgumentException.class) 63 | public void shouldInvalidVersion1NotBeParsed() { 64 | Version.parse("invalid"); 65 | } 66 | 67 | @Test(expected=IllegalArgumentException.class) 68 | public void shouldInvalidVersion2NotBeParsed() { 69 | Version.parse("a.2.3"); 70 | } 71 | 72 | @Test 73 | public void shouldMajorBeCorrectlyIncremented() { 74 | Assert.assertEquals(Version.parse("10.0.0"), Version.parse("9.0.0").next(Version.Element.MAJOR)); 75 | } 76 | 77 | @Test 78 | public void shouldMinorBeCorrectlyIncremented() { 79 | Assert.assertEquals(Version.parse("0.10.0"), Version.parse("0.9.0").next(Version.Element.MINOR)); 80 | } 81 | 82 | @Test 83 | public void shouldPatchBeCorrectlyIncremented() { 84 | Assert.assertEquals(Version.parse("0.0.10"), Version.parse("0.0.9").next(Version.Element.PATCH)); 85 | } 86 | 87 | @Test 88 | public void shouldDevelopmentBeInDevelopment() { 89 | Assert.assertTrue(Version.parse("0.1.1").isInDevelopment()); 90 | Assert.assertFalse(Version.parse("1.1.1").isInDevelopment()); 91 | } 92 | 93 | @Test 94 | public void shouldStableVersionBeStable() { 95 | Assert.assertTrue(Version.parse("1.1.1").isStable()); 96 | Assert.assertFalse(Version.parse("0.1.1").isStable()); 97 | } 98 | 99 | @Test 100 | public void shouldBeSnapshotVersion() { 101 | Assert.assertTrue(Version.parse("1.5.30-SNAPSHOT").isSnapshot()); 102 | } 103 | 104 | @Test 105 | public void shouldBeCompatible() { 106 | Assert.assertTrue(Version.parse("1.0.0").isCompatible(Version.parse("1.2.3-SNAPSHOT"))); 107 | Assert.assertTrue(Version.parse("1.0.0").isCompatible(Version.parse("1.0.1"))); 108 | Assert.assertTrue(Version.parse("1.0.0").isCompatible(Version.parse("1.1.0"))); 109 | } 110 | 111 | @Test 112 | public void shouldBeIncompatible() { 113 | Assert.assertFalse(Version.parse("0.0.1-SNAPSHOT").isCompatible(null)); 114 | Assert.assertFalse(Version.parse("1.0.1").isCompatible(Version.parse("2.0.0"))); 115 | Assert.assertFalse(Version.parse("1.1.0-rc3").isCompatible(Version.parse("3.1.0-SNAPSHOT"))); 116 | } 117 | 118 | @Test 119 | public void isNewer() { 120 | Assert.assertTrue(Version.parse("3.2.3").compareTo(Version.parse("3.2-M1-SNAPSHOT")) > 0); 121 | Assert.assertTrue(Version.parse("1.0.0").compareTo(Version.parse("0.0.0")) > 0); 122 | Assert.assertTrue(Version.parse("0.0.0").compareTo(Version.parse("1.0.0")) < 0); 123 | Assert.assertTrue(Version.parse("1.1.0").compareTo(Version.parse("1.0.0")) > 0); 124 | Assert.assertTrue(Version.parse("1.0.0").compareTo(Version.parse("1.1.0")) < 0); 125 | Assert.assertTrue(Version.parse("1.0.1").compareTo(Version.parse("1.0.0")) > 0); 126 | Assert.assertTrue(Version.parse("1.0.0").compareTo(Version.parse("1.0.1")) < 0); 127 | Assert.assertTrue(Version.parse("1.0.0Beta").compareTo(Version.parse("1.0.0Alpha")) > 0); 128 | Assert.assertFalse(Version.parse("0.0.0").compareTo(Version.parse("0.0.0")) > 0); 129 | Assert.assertFalse(Version.parse("0.0.0").compareTo(Version.parse("0.0.1")) > 0); 130 | // based on http://semver.org/ 131 | // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0. 132 | String[] versions = { "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-alpha.beta", "1.0.0-beta", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0" }; 133 | assertTotalOrder(versions); 134 | } 135 | 136 | private void assertTotalOrder(String[] versions) { 137 | List problems = new ArrayList(); 138 | for (int i = 0; i < versions.length - 1; i++) { 139 | Version v1 = Version.parse(versions[i]); 140 | for (int j = (i + 1); j < versions.length; j++) { 141 | Version v2 = Version.parse(versions[j]); 142 | int compare = v1.compareTo(v2); 143 | if (compare >= 0 ) { 144 | problems.add(v1 + ( compare == 0 ? " = " : " > ") + v2); 145 | } 146 | } 147 | } 148 | if (problems.size() > 0) { 149 | Assert.fail("incorrect comparisons: " + problems); 150 | } 151 | } 152 | 153 | @Test 154 | public void next() { 155 | final int major = 1; 156 | final int minor = 2; 157 | final int patch = 3; 158 | final Version version = new Version(major, minor, patch); 159 | 160 | Assert.assertEquals(version.next(Version.Element.MAJOR), new Version(major+1, 0, 0)); 161 | Assert.assertEquals(version.next(Version.Element.MINOR), new Version(major, minor+1, 0)); 162 | Assert.assertEquals(version.next(Version.Element.PATCH), new Version(major, minor, patch+1)); 163 | } 164 | 165 | @Test 166 | public void nextFromPre() { 167 | final Version version1 = new Version(1, 0, 0, "-", "rc1"); 168 | Assert.assertEquals(new Version(1, 0, 0), version1.next(Version.Element.MAJOR)); 169 | Assert.assertEquals(new Version(1, 0, 0), version1.next(Version.Element.MINOR)); 170 | Assert.assertEquals(new Version(1, 0, 0), version1.next(Version.Element.PATCH)); 171 | 172 | final Version version2 = new Version(1, 1, 0, "-", "rc1"); 173 | Assert.assertEquals(new Version(2, 0, 0), version2.next(Version.Element.MAJOR)); 174 | Assert.assertEquals(new Version(1, 1, 0), version2.next(Version.Element.MINOR)); 175 | Assert.assertEquals(new Version(1, 1, 0), version2.next(Version.Element.PATCH)); 176 | 177 | final Version version3 = new Version(1, 1, 1, "-", "rc1"); 178 | Assert.assertEquals(new Version(2, 0, 0), version3.next(Version.Element.MAJOR)); 179 | Assert.assertEquals(new Version(1, 2, 0), version3.next(Version.Element.MINOR)); 180 | Assert.assertEquals(new Version(1, 1, 1), version3.next(Version.Element.PATCH)); 181 | } 182 | 183 | 184 | @Test(expected=IllegalArgumentException.class) 185 | public void shouldNextWithNullComparisonTypeFail() { 186 | final int major = 1; 187 | final int minor = 2; 188 | final int patch = 3; 189 | final Version version = new Version(major, minor, patch); 190 | 191 | version.next(null); 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /api/src/test/java/org/semver/jardiff/ClassInheritanceTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver.jardiff; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import junit.framework.Assert; 22 | import org.junit.Test; 23 | import org.objectweb.asm.ClassReader; 24 | import org.osjava.jardiff.ClassInfo; 25 | import org.osjava.jardiff.JarDiff; 26 | import org.osjava.jardiff.SimpleDiffCriteria; 27 | import org.semver.Delta; 28 | import org.semver.Delta.Change; 29 | 30 | public class ClassInheritanceTest { 31 | 32 | public static abstract class InheritanceRoot { 33 | public abstract void aMethod(); 34 | } 35 | 36 | public static class DirectDescendant extends InheritanceRoot { 37 | @Override 38 | public void aMethod() {} 39 | } 40 | 41 | public static class ClassA extends InheritanceRoot { 42 | @Override 43 | public void aMethod() {} 44 | } 45 | 46 | public static class ClassB extends DirectDescendant { 47 | } 48 | 49 | @Test 50 | public void shouldInheritedMethodMatchImplementedMethod() throws Exception { 51 | /** 52 | * The situation we are testing is as follows: 53 | * Abstract class InheritanceRoot is initially implemented directly by ClassA. 54 | * ClassA is later modified to extend another implementation of InheritanceRoot 55 | * and the methods required by InheritanceRoot are now removed from ClassA directly, 56 | * and instead inherited from the new parent, DirectDescendant. For the purposes of 57 | * this test, this new ClassA is represented by ClassB (as we can't have the same 58 | * class declared twice in a test -- in real life, this would both be ClassA's, 59 | * in different jars). 60 | */ 61 | Map oldClassInfoMap = new HashMap(); 62 | Map newClassInfoMap = new HashMap(); 63 | JarDiff jd = new JarDiff(); 64 | addClassInfo(oldClassInfoMap, ClassA.class, jd); 65 | addClassInfo(oldClassInfoMap, DirectDescendant.class, jd); 66 | addClassInfo(oldClassInfoMap, InheritanceRoot.class, jd); 67 | addClassInfo(newClassInfoMap, ClassB.class, jd); 68 | addClassInfo(newClassInfoMap, DirectDescendant.class, jd); 69 | addClassInfo(newClassInfoMap, InheritanceRoot.class, jd); 70 | 71 | // Make B look like A 72 | ClassInfo a = oldClassInfoMap.get("org/semver/jardiff/ClassInheritanceTest$ClassA"); 73 | ClassInfo b = newClassInfoMap.get("org/semver/jardiff/ClassInheritanceTest$ClassB"); 74 | newClassInfoMap.put(a.getName(), new ClassInfo(b.getVersion(), b.getAccess(), a.getName(), 75 | b.getSignature(), b.getSupername(), b.getInterfaces(), 76 | b.getMethodMap(), b.getFieldMap())); 77 | newClassInfoMap.remove(b.getName()); 78 | DifferenceAccumulatingHandler handler = new DifferenceAccumulatingHandler(); 79 | jd.diff(handler, new SimpleDiffCriteria(), 80 | "0.1.0", "0.2.0", oldClassInfoMap, newClassInfoMap); 81 | 82 | for (Delta.Difference d: handler.getDelta().getDifferences()) { 83 | System.err.println(d.getClassName() + " : " + d.getClass().getName() 84 | + " : " + d.getInfo().getName() + " : " + d.getInfo().getAccessType()); 85 | if (d instanceof Change) { 86 | System.err.println(" : " + ((Change) d).getModifiedInfo().getName()); 87 | } 88 | } 89 | Assert.assertEquals("differences found", 1, handler.getDelta().getDifferences().size()); 90 | } 91 | 92 | private void addClassInfo(Map classMap, Class klass, JarDiff jd) throws Exception { 93 | ClassInfo classInfo = jd.loadClassInfo(new ClassReader(klass.getName())); 94 | classMap.put(classInfo.getName(), classInfo); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /api/src/test/java/org/semver/jardiff/DeprecateDetectionTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver.jardiff; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | import java.util.Set; 21 | 22 | import junit.framework.Assert; 23 | import org.junit.Test; 24 | import org.objectweb.asm.ClassReader; 25 | import org.osjava.jardiff.ClassInfo; 26 | import org.osjava.jardiff.JarDiff; 27 | import org.osjava.jardiff.SimpleDiffCriteria; 28 | import org.semver.Delta.Deprecate; 29 | import org.semver.Delta.Difference; 30 | import org.semver.Dumper; 31 | 32 | public class DeprecateDetectionTest { 33 | 34 | public static abstract class InheritanceRoot { 35 | public abstract void aMethod(); 36 | } 37 | 38 | public static class DirectDescendant extends InheritanceRoot { 39 | @Override 40 | public void aMethod() {} 41 | } 42 | 43 | public static class ClassA extends InheritanceRoot { 44 | @Override 45 | public void aMethod() {} 46 | 47 | public int aField = 0; 48 | } 49 | 50 | public static class ClassB extends DirectDescendant { 51 | @Override 52 | @Deprecated 53 | public void aMethod() {} 54 | 55 | @Deprecated 56 | public int aField = 0; 57 | } 58 | 59 | @Test 60 | public void shouldInheritedMethodMatchImplementedMethod() throws Exception { 61 | /** 62 | * The situation we are testing is as follows: 63 | * Abstract class InheritanceRoot is initially implemented directly by ClassA. 64 | * ClassA is later modified to extend another implementation of InheritanceRoot 65 | * and the methods required by InheritanceRoot are now removed from ClassA directly, 66 | * and instead inherited from the new parent, DirectDescendant. For the purposes of 67 | * this test, this new ClassA is represented by ClassB (as we can't have the same 68 | * class declared twice in a test -- in real life, this would both be ClassA's, 69 | * in different jars). 70 | */ 71 | Map oldClassInfoMap = new HashMap(); 72 | Map newClassInfoMap = new HashMap(); 73 | JarDiff jd = new JarDiff(); 74 | addClassInfo(oldClassInfoMap, ClassA.class, jd); 75 | addClassInfo(oldClassInfoMap, DirectDescendant.class, jd); 76 | addClassInfo(oldClassInfoMap, InheritanceRoot.class, jd); 77 | addClassInfo(newClassInfoMap, ClassB.class, jd); 78 | addClassInfo(newClassInfoMap, DirectDescendant.class, jd); 79 | addClassInfo(newClassInfoMap, InheritanceRoot.class, jd); 80 | 81 | // Make B look like A 82 | newClassInfoMap.put("org/semver/jardiff/DeprecateDetectionTest$ClassA", 83 | newClassInfoMap.get("org/semver/jardiff/DeprecateDetectionTest$ClassB")); 84 | newClassInfoMap.remove("org/semver/jardiff/DeprecateDetectionTest$ClassB"); 85 | DifferenceAccumulatingHandler handler = new DifferenceAccumulatingHandler(); 86 | jd.diff(handler, new SimpleDiffCriteria(), 87 | "0.1.0", "0.2.0", oldClassInfoMap, newClassInfoMap); 88 | 89 | Dumper.dump(handler.getDelta()); 90 | 91 | Set differences = handler.getDelta().getDifferences(); 92 | Assert.assertEquals("differences found", 3, differences.size()); 93 | // Naive search for Deprecate. 94 | boolean hasDeprecate = false; 95 | for (Difference d : differences) { 96 | if (d instanceof Deprecate) 97 | hasDeprecate = true; 98 | } 99 | Assert.assertTrue("No Delta.Deprecate found", hasDeprecate); 100 | } 101 | 102 | private void addClassInfo(Map classMap, Class klass, JarDiff jd) throws Exception { 103 | ClassInfo classInfo = jd.loadClassInfo(new ClassReader(klass.getName())); 104 | classMap.put(classInfo.getName(), classInfo); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /api/src/test/java/org/semver/jardiff/DifferenceAccumulatingHandlerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver.jardiff; 18 | 19 | 20 | import java.util.ArrayList; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Set; 24 | 25 | import org.junit.Assert; 26 | import org.junit.Test; 27 | 28 | 29 | public class DifferenceAccumulatingHandlerTest { 30 | 31 | @Test 32 | public void shouldClassBeNotConsideredWithTwoPlaceholdersBeforeAndBehind() { 33 | 34 | List inclusions = new ArrayList(); 35 | Set inclusionSet = new HashSet( inclusions ); 36 | List exclusions = new ArrayList(); 37 | exclusions.add( "**/java/**" ); 38 | Set exclusionSet = new HashSet( exclusions ); 39 | 40 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 41 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 42 | } 43 | 44 | @Test 45 | public void shouldClassBeConsideredWithOnePlaceholderBeforeAndBehind() { 46 | 47 | List inclusions = new ArrayList(); 48 | Set inclusionSet = new HashSet( inclusions ); 49 | List exclusions = new ArrayList(); 50 | exclusions.add( "*/java/*" ); 51 | Set exclusionSet = new HashSet( exclusions ); 52 | 53 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 54 | exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 55 | } 56 | 57 | @Test 58 | public void shouldClassBeNotConsideredWithTwoPlaceholderAfter() { 59 | 60 | List inclusions = new ArrayList(); 61 | Set inclusionSet = new HashSet( inclusions ); 62 | List exclusions = new ArrayList(); 63 | exclusions.add( "java/**" ); 64 | Set exclusionSet = new HashSet( exclusions ); 65 | 66 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 67 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 68 | } 69 | 70 | @Test 71 | public void shouldClassBeConsideredWithTwoPlaceholderBefore() { 72 | 73 | List inclusions = new ArrayList(); 74 | Set inclusionSet = new HashSet( inclusions ); 75 | List exclusions = new ArrayList(); 76 | exclusions.add( "**/java" ); 77 | Set exclusionSet = new HashSet( exclusions ); 78 | 79 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 80 | exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 81 | } 82 | 83 | @Test 84 | public void shouldClassBeConsideredWithOnePlaceholderAfter() { 85 | 86 | List inclusions = new ArrayList(); 87 | Set inclusionSet = new HashSet( inclusions ); 88 | List exclusions = new ArrayList(); 89 | exclusions.add( "java/*" ); 90 | Set exclusionSet = new HashSet( exclusions ); 91 | 92 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 93 | exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 94 | } 95 | 96 | @Test 97 | public void shouldClassBeConsideredWithOnePlaceholderBefore() { 98 | 99 | List inclusions = new ArrayList(); 100 | Set inclusionSet = new HashSet( inclusions ); 101 | List exclusions = new ArrayList(); 102 | exclusions.add( "*/java" ); 103 | Set exclusionSet = new HashSet( exclusions ); 104 | 105 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 106 | exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 107 | } 108 | 109 | @Test 110 | public void shouldClassBeNotConsideredWithTwoPlaceholderInside() { 111 | 112 | List inclusions = new ArrayList(); 113 | Set inclusionSet = new HashSet( inclusions ); 114 | List exclusions = new ArrayList(); 115 | exclusions.add( "de/**/java/**" ); 116 | Set exclusionSet = new HashSet( exclusions ); 117 | 118 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 119 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 120 | } 121 | 122 | @Test 123 | public void shouldClassBeNotConsideredWithOnePlaceholderInside() { 124 | 125 | List inclusions = new ArrayList(); 126 | Set inclusionSet = new HashSet( inclusions ); 127 | List exclusions = new ArrayList(); 128 | exclusions.add( "de/*/java/**" ); 129 | Set exclusionSet = new HashSet( exclusions ); 130 | 131 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 132 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 133 | } 134 | 135 | @Test 136 | public void shouldClassBeConsideredWithOnePlaceholderInside() { 137 | 138 | List inclusions = new ArrayList(); 139 | Set inclusionSet = new HashSet( inclusions ); 140 | List exclusions = new ArrayList(); 141 | exclusions.add( "de/*/classImpl" ); 142 | Set exclusionSet = new HashSet( exclusions ); 143 | 144 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 145 | exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 146 | } 147 | 148 | @Test 149 | public void shouldClassBeConsideredWithTwoPlaceholderInsideAndSpecificEnd() { 150 | 151 | List inclusions = new ArrayList(); 152 | Set inclusionSet = new HashSet( inclusions ); 153 | List exclusions = new ArrayList(); 154 | exclusions.add( "java/**/Impl" ); 155 | Set exclusionSet = new HashSet( exclusions ); 156 | 157 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 158 | exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 159 | } 160 | 161 | @Test 162 | public void shouldClassNotBeConsideredWithOnePlaceholderInsideAndSpecificEnd() { 163 | 164 | List inclusions = new ArrayList(); 165 | Set inclusionSet = new HashSet( inclusions ); 166 | List exclusions = new ArrayList(); 167 | exclusions.add( "java/*/*Impl" ); 168 | Set exclusionSet = new HashSet( exclusions ); 169 | 170 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 171 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 172 | } 173 | 174 | @Test 175 | public void shouldClassBeConsideredWithOnePlaceholderInsideAndSpecificEnd() { 176 | 177 | List inclusions = new ArrayList(); 178 | Set inclusionSet = new HashSet( inclusions ); 179 | List exclusions = new ArrayList(); 180 | exclusions.add( "test/*/*Impl" ); 181 | Set exclusionSet = new HashSet( exclusions ); 182 | 183 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 184 | exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl" ) ); 185 | } 186 | 187 | @Test 188 | public void shouldClassBeConsideredWithTwoPlaceholderInsidePlusHashAndUnspecificEnd3() { 189 | 190 | List inclusions = new ArrayList(); 191 | Set inclusionSet = new HashSet( inclusions ); 192 | List exclusions = new ArrayList(); 193 | exclusions.add( "test/*/*Impl/*" ); 194 | Set exclusionSet = new HashSet( exclusions ); 195 | 196 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 197 | exclusionSet ).isClassConsidered( "de/test/java/regex/Impl2/code" ) ); 198 | } 199 | 200 | @Test 201 | public void shouldClassNotBeConsideredWithTwoPlaceholderInsidePlusHashAndUnspecificEnd2() { 202 | 203 | List inclusions = new ArrayList(); 204 | Set inclusionSet = new HashSet( inclusions ); 205 | List exclusions = new ArrayList(); 206 | exclusions.add( "java/*/*Impl/*" ); 207 | Set exclusionSet = new HashSet( exclusions ); 208 | 209 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 210 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl/code" ) ); 211 | } 212 | 213 | @Test 214 | public void shouldClassBeConsideredWithTwoPlaceholderInsidePlusHashAndUnspecificEnd() { 215 | 216 | List inclusions = new ArrayList(); 217 | Set inclusionSet = new HashSet( inclusions ); 218 | List exclusions = new ArrayList(); 219 | exclusions.add( "test/*/*Impl/*" ); 220 | Set exclusionSet = new HashSet( exclusions ); 221 | 222 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 223 | exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl/code/Implem" ) ); 224 | } 225 | 226 | @Test 227 | public void shouldClassNotBeConsideredWithTwoPlaceholderInsidePlusHashAndUnspecificEnd() { 228 | 229 | List inclusions = new ArrayList(); 230 | Set inclusionSet = new HashSet( inclusions ); 231 | List exclusions = new ArrayList(); 232 | exclusions.add( "java/*/*Impl/*" ); 233 | Set exclusionSet = new HashSet( exclusions ); 234 | 235 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 236 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl/code" ) ); 237 | } 238 | 239 | @Test 240 | public void shouldClassNotBeConsideredWithOnePlaceholderInsideAndUnspecificEnd() { 241 | 242 | List inclusions = new ArrayList(); 243 | Set inclusionSet = new HashSet( inclusions ); 244 | List exclusions = new ArrayList(); 245 | exclusions.add( "java/*/*Impl*" ); 246 | Set exclusionSet = new HashSet( exclusions ); 247 | 248 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 249 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/classImpl2" ) ); 250 | } 251 | 252 | @Test 253 | public void shouldClassNotBeConsideredWithTwoPlaceholderInsideAndUnspecificEnd() { 254 | 255 | List inclusions = new ArrayList(); 256 | Set inclusionSet = new HashSet( inclusions ); 257 | List exclusions = new ArrayList(); 258 | exclusions.add( "java/**/Impl*" ); 259 | Set exclusionSet = new HashSet( exclusions ); 260 | 261 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 262 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/Impl" ) ); 263 | } 264 | 265 | @Test 266 | public void shouldClassNotBeConsideredWithTwoPlaceholderInsideAndUnspecificEndWithNoUseOfPlaceholders() { 267 | 268 | List inclusions = new ArrayList(); 269 | Set inclusionSet = new HashSet( inclusions ); 270 | List exclusions = new ArrayList(); 271 | exclusions.add( "regex/**/Impl*" ); 272 | Set exclusionSet = new HashSet( exclusions ); 273 | 274 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 275 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/Impl" ) ); 276 | } 277 | 278 | @Test 279 | public void shouldClassBeConsideredWithTwoPlaceholderInsideAndUnspecificEndWithNoUseOfPlaceholders() { 280 | 281 | List inclusions = new ArrayList(); 282 | Set inclusionSet = new HashSet( inclusions ); 283 | List exclusions = new ArrayList(); 284 | exclusions.add( "regex/**/Impl*" ); 285 | Set exclusionSet = new HashSet( exclusions ); 286 | 287 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( inclusionSet, 288 | exclusionSet ).isClassConsidered( "de/test/java/regex/test" ) ); 289 | } 290 | 291 | @Test 292 | public void shouldClassNotBeConsideredWithTwoPlaceholderInsideAndUnspecificEndWith() { 293 | 294 | List inclusions = new ArrayList(); 295 | Set inclusionSet = new HashSet( inclusions ); 296 | List exclusions = new ArrayList(); 297 | exclusions.add( "test/**/Impl*" ); 298 | Set exclusionSet = new HashSet( exclusions ); 299 | 300 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 301 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/Impl" ) ); 302 | } 303 | 304 | @Test 305 | public void shouldClassNotBeConsideredWhenNotMatchingAnyDefinedInclude() { 306 | 307 | List inclusions = new ArrayList(); 308 | inclusions.add( "test/java/regex/code/*" ); 309 | Set inclusionSet = new HashSet( inclusions ); 310 | List exclusions = new ArrayList(); 311 | Set exclusionSet = new HashSet( exclusions ); 312 | 313 | Assert.assertEquals( "Class should not be considered: ", false, new DifferenceAccumulatingHandler( 314 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/Impl" ) ); 315 | } 316 | 317 | @Test 318 | public void shouldClassBeConsideredWhenMatchingDefinedInclude() { 319 | 320 | List inclusions = new ArrayList(); 321 | inclusions.add( "test/java/regex/code/*" ); 322 | Set inclusionSet = new HashSet( inclusions ); 323 | List exclusions = new ArrayList(); 324 | Set exclusionSet = new HashSet( exclusions ); 325 | 326 | Assert.assertEquals( "Class should be considered: ", true, new DifferenceAccumulatingHandler( 327 | inclusionSet, exclusionSet ).isClassConsidered( "de/test/java/regex/code/Impl" ) ); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /enforcer-rule/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | enforcer-rule 6 | 7 | Enforcer Rule 8 | 9 | 10 | org.semver 11 | parent 12 | 0.9.34-SNAPSHOT 13 | ../pom.xml 14 | 15 | 16 | 17 | 1.0.1 18 | 2.2.1 19 | 20 | 21 | 22 | 23 | org.semver 24 | api 25 | ${project.version} 26 | 27 | 28 | org.apache.maven.enforcer 29 | enforcer-api 30 | ${api.version} 31 | 32 | 33 | org.apache.maven 34 | maven-project 35 | ${maven.version} 36 | 37 | 38 | org.apache.maven 39 | maven-core 40 | ${maven.version} 41 | 42 | 43 | org.apache.maven 44 | maven-artifact 45 | ${maven.version} 46 | 47 | 48 | org.apache.maven 49 | maven-plugin-api 50 | ${maven.version} 51 | 52 | 53 | 54 | 55 | 56 | 57 | maven-javadoc-plugin 58 | 59 | false 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /enforcer-rule/src/main/java/org/semver/enforcer/AbstractEnforcerRule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver.enforcer; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.Collections; 24 | import java.util.HashSet; 25 | import java.util.Iterator; 26 | import java.util.List; 27 | import java.util.Set; 28 | 29 | import org.apache.maven.artifact.Artifact; 30 | import org.apache.maven.artifact.factory.ArtifactFactory; 31 | import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; 32 | import org.apache.maven.artifact.metadata.ArtifactMetadataSource; 33 | import org.apache.maven.artifact.repository.ArtifactRepository; 34 | import org.apache.maven.artifact.resolver.ArtifactResolver; 35 | import org.apache.maven.artifact.versioning.ArtifactVersion; 36 | import org.apache.maven.artifact.versioning.DefaultArtifactVersion; 37 | import org.apache.maven.enforcer.rule.api.EnforcerRule; 38 | import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 39 | import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; 40 | import org.apache.maven.project.MavenProject; 41 | import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; 42 | import org.osjava.jardiff.DiffCriteria; 43 | import org.osjava.jardiff.PublicDiffCriteria; 44 | import org.osjava.jardiff.SimpleDiffCriteria; 45 | import org.semver.Comparer; 46 | import org.semver.Delta; 47 | import org.semver.Dumper; 48 | import org.semver.Version; 49 | 50 | /** 51 | * Abstract {@link EnforcerRule} implementation providing facilities for compatibility checking. 52 | */ 53 | public abstract class AbstractEnforcerRule implements EnforcerRule { 54 | 55 | private static final String JAR_ARTIFACT_TYPE = "jar"; 56 | private static final String BUNDLE_ARTIFACT_TYPE = "bundle"; 57 | 58 | /** 59 | * Version number of artifact to be checked. 60 | * 61 | * @parameter 62 | */ 63 | private String previousVersion; 64 | 65 | /** 66 | * Class names to be included. 67 | * 68 | * @parameter 69 | */ 70 | private String[] includes; 71 | 72 | /** 73 | * Class names to be excluded. 74 | * 75 | * @parameter 76 | */ 77 | private String[] excludes; 78 | 79 | /** 80 | * Dump change details. 81 | * 82 | * @parameter 83 | */ 84 | private boolean dumpDetails = false; 85 | 86 | /** 87 | * Check public members only 88 | * 89 | * @parameter 90 | */ 91 | private boolean publicOnly = false; 92 | 93 | private Set extractFilters(final String[] filtersAsStringArray) { 94 | if (filtersAsStringArray == null) { 95 | return Collections.emptySet(); 96 | } 97 | return new HashSet(Arrays.asList(filtersAsStringArray)); 98 | } 99 | 100 | @Override 101 | public void execute(final EnforcerRuleHelper helper) throws EnforcerRuleException { 102 | final MavenProject project = getMavenProject(helper); 103 | if (shouldSkipRuleExecution(project)) { 104 | helper.getLog().debug("Skipping non "+AbstractEnforcerRule.JAR_ARTIFACT_TYPE+ 105 | " or " + BUNDLE_ARTIFACT_TYPE + " artifact."); 106 | return; 107 | } 108 | 109 | final Artifact previousArtifact; 110 | final Artifact currentArtifact = validateArtifact(project.getArtifact()); 111 | final Version current = Version.parse(currentArtifact.getVersion()); 112 | try { 113 | final ArtifactRepository localRepository = (ArtifactRepository) helper.evaluate("${localRepository}"); 114 | final String version; 115 | 116 | if (this.previousVersion != null) { 117 | version = this.previousVersion; 118 | helper.getLog().info("Version specified as <"+version+">"); 119 | } else { 120 | final ArtifactMetadataSource artifactMetadataSource = (ArtifactMetadataSource) helper.getComponent(ArtifactMetadataSource.class); 121 | final List availableVersions = getAvailableReleasedVersions(artifactMetadataSource, project, localRepository); 122 | final List availablePreviousVersions = filterNonPreviousVersions(availableVersions, current); 123 | if (availablePreviousVersions.isEmpty()) { 124 | helper.getLog().warn("No previously released version. Backward compatibility check not performed."); 125 | return; 126 | } 127 | version = availablePreviousVersions.iterator().next().toString(); 128 | helper.getLog().info("Version deduced as <"+version+"> (among all availables: "+availablePreviousVersions+")"); 129 | } 130 | 131 | final ArtifactFactory artifactFactory = (ArtifactFactory) helper.getComponent(ArtifactFactory.class); 132 | previousArtifact = artifactFactory.createArtifact(project.getGroupId(), project.getArtifactId(), version, null, project.getArtifact().getType()); 133 | final ArtifactResolver resolver = (ArtifactResolver) helper.getComponent(ArtifactResolver.class ); 134 | resolver.resolve(previousArtifact, project.getRemoteArtifactRepositories(), localRepository); 135 | validateArtifact(previousArtifact); 136 | } catch (Exception e) { 137 | helper.getLog().warn("Exception while accessing artifacts; skipping check.", e); 138 | return; 139 | } 140 | 141 | final Version previous = Version.parse(previousArtifact.getVersion()); 142 | final File previousJar = previousArtifact.getFile(); 143 | final File currentJar = currentArtifact.getFile(); 144 | compareJars(helper, previous, previousJar, current, currentJar); 145 | } 146 | 147 | protected abstract void enforce(final EnforcerRuleHelper helper, final Delta delta, final Version previous, final Version current) throws EnforcerRuleException; 148 | 149 | protected final void fail(final Delta delta, final String message) throws EnforcerRuleException { 150 | if (this.dumpDetails) { 151 | Dumper.dump(delta); 152 | } 153 | throw new EnforcerRuleException(message); 154 | } 155 | 156 | /** 157 | * @param artifactMetadataSource 158 | * @param project 159 | * @param localRepository 160 | * @return all available versions from most recent to oldest 161 | * @throws ArtifactMetadataRetrievalException 162 | */ 163 | protected final List getAvailableReleasedVersions(final ArtifactMetadataSource artifactMetadataSource, final MavenProject project, final ArtifactRepository localRepository) throws ArtifactMetadataRetrievalException { 164 | final List availableVersions = artifactMetadataSource.retrieveAvailableVersions(project.getArtifact(), localRepository, project.getRemoteArtifactRepositories()); 165 | availableVersions.remove(new DefaultArtifactVersion(project.getArtifact().getVersion())); 166 | for (final Iterator iterator = availableVersions.iterator(); iterator.hasNext();) { 167 | final ArtifactVersion artifactVersion = iterator.next(); 168 | if (Version.parse(artifactVersion.toString()).isSnapshot()) { 169 | iterator.remove(); 170 | } 171 | } 172 | //TODO proper sorting based on Version 173 | Collections.sort(availableVersions); 174 | Collections.reverse(availableVersions); 175 | return availableVersions; 176 | } 177 | 178 | protected final List filterNonPreviousVersions(final List availableVersions, final Version version) { 179 | final List versions = new ArrayList(); 180 | for (final ArtifactVersion artifactVersion : availableVersions) { 181 | Version parsedVersion = Version.parse(artifactVersion.toString()); 182 | if (version.isCompatible(parsedVersion) && version.compareTo(parsedVersion) > 0) { 183 | versions.add(artifactVersion); 184 | } 185 | } 186 | return versions; 187 | } 188 | 189 | private static MavenProject getMavenProject(EnforcerRuleHelper helper) throws EnforcerRuleException { 190 | final MavenProject project; 191 | try { 192 | project = (MavenProject) helper.evaluate("${project}"); 193 | } catch (ExpressionEvaluationException e) { 194 | throw new EnforcerRuleException("Failed to access ${project} variable", e); 195 | } 196 | return project; 197 | } 198 | 199 | private static boolean shouldSkipRuleExecution(MavenProject project) { 200 | return !AbstractEnforcerRule.JAR_ARTIFACT_TYPE.equals(project.getArtifact().getType()) && 201 | !AbstractEnforcerRule.BUNDLE_ARTIFACT_TYPE.equals(project.getArtifact().getType()); 202 | } 203 | 204 | private void compareJars(final EnforcerRuleHelper helper, final Version previous, final File previousJar, final Version current, 205 | final File currentJar) throws EnforcerRuleException { 206 | helper.getLog().info("Using <" + previousJar + "> as previous JAR"); 207 | helper.getLog().info("Using <" + currentJar + "> as current JAR"); 208 | try { 209 | final DiffCriteria diffCriteria = publicOnly ? new PublicDiffCriteria() : new SimpleDiffCriteria(); 210 | final Comparer comparer = 211 | new Comparer(diffCriteria, previousJar, currentJar, extractFilters(this.includes), extractFilters(this.excludes)); 212 | final Delta delta = comparer.diff(); 213 | enforce(helper, delta, previous, current); 214 | } catch (IOException e) { 215 | throw new EnforcerRuleException("Exception while checking compatibility: " + e.toString(), e); 216 | } 217 | } 218 | 219 | /** 220 | * Validates that specified {@link Artifact} is a file. 221 | * @param artifact 222 | */ 223 | private static Artifact validateArtifact(final Artifact artifact) { 224 | if (!artifact.getFile().isFile()) { 225 | throw new IllegalArgumentException("<"+artifact.getFile()+"> is not a file"); 226 | } 227 | return artifact; 228 | } 229 | 230 | @Override 231 | public boolean isCacheable() { 232 | return false; 233 | } 234 | 235 | @Override 236 | public boolean isResultValid(final EnforcerRule cachedRule) { 237 | return false; 238 | } 239 | 240 | @Override 241 | public String getCacheId() { 242 | return "0"; 243 | } 244 | 245 | } 246 | -------------------------------------------------------------------------------- /enforcer-rule/src/main/java/org/semver/enforcer/RequireBackwardCompatibility.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver.enforcer; 18 | 19 | import org.apache.maven.artifact.Artifact; 20 | import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 21 | import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; 22 | import org.semver.Delta; 23 | import org.semver.Version; 24 | 25 | /** 26 | * 27 | * Checks {@link Version} for current {@link Artifact} compared to a previous {@link Artifact}. 28 | *
29 | * Fails if current version is not backward compatible with previous one. {@link Delta.CompatibilityType} level can be specified. 30 | * 31 | */ 32 | public final class RequireBackwardCompatibility extends AbstractEnforcerRule { 33 | 34 | /** 35 | * Compatibility type expected. Must be one of {@link Delta.CompatibilityType} enum values. 36 | * 37 | * @parameter 38 | */ 39 | private String compatibilityType; 40 | 41 | private boolean strictChecking = false; 42 | 43 | @Override 44 | protected void enforce(final EnforcerRuleHelper helper, final Delta delta, final Version previous, final Version current) throws EnforcerRuleException { 45 | if (this.compatibilityType == null) { 46 | throw new IllegalArgumentException("A value for compatibilityType attribute must be provided."); 47 | } 48 | 49 | final Delta.CompatibilityType expectedCompatibilityType; 50 | try { 51 | expectedCompatibilityType = Delta.CompatibilityType.valueOf(this.compatibilityType); 52 | } catch (IllegalStateException e) { 53 | throw new EnforcerRuleException("Compatibility type value must be one of "+Delta.CompatibilityType.values()); 54 | } 55 | 56 | final Delta.CompatibilityType detectedCompatibilityType = delta.computeCompatibilityType(); 57 | if (this.strictChecking) { 58 | if (detectedCompatibilityType != expectedCompatibilityType) { 59 | fail(delta, "Current codebase is not strictly backward compatible ("+this.compatibilityType+") with version <"+previous+">. Compatibility type has been detected as <"+detectedCompatibilityType+">"); 60 | } 61 | } else { 62 | if (expectedCompatibilityType == Delta.CompatibilityType.NON_BACKWARD_COMPATIBLE) { 63 | helper.getLog().warn("Rule will never fail as compatibility type "+Delta.CompatibilityType.NON_BACKWARD_COMPATIBLE+" is used with non-strict checking."); 64 | } 65 | 66 | if (detectedCompatibilityType.compareTo(expectedCompatibilityType) > 0) { 67 | fail(delta, "Current codebase is not backward compatible ("+this.compatibilityType+") with version <"+previous+">. Compatibility type has been detected as <"+detectedCompatibilityType+">"); 68 | } 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /enforcer-rule/src/main/java/org/semver/enforcer/RequireSemanticVersioningConformance.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2014 Julien Eluard and contributors 3 | * This project includes software developed by Julien Eluard: https://github.com/jeluard/ 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.semver.enforcer; 18 | 19 | import org.apache.maven.artifact.Artifact; 20 | import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 21 | import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; 22 | import org.semver.Delta; 23 | import org.semver.Version; 24 | 25 | /** 26 | * 27 | * Checks {@link Version} for current {@link Artifact} compared to a previous {@link Artifact}. 28 | *
29 | * Fails if {@link Version} semantic is not respected. 30 | * 31 | */ 32 | public final class RequireSemanticVersioningConformance extends AbstractEnforcerRule { 33 | 34 | @Override 35 | protected void enforce(final EnforcerRuleHelper helper, final Delta delta, final Version previous, final Version current) throws EnforcerRuleException { 36 | final boolean compatible = delta.validate(previous, current); 37 | if (!compatible) { 38 | fail(delta, "Current codebase is incompatible with version <"+previous+">. Version should be at least <"+delta.infer(previous)+">."); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.semver 6 | example 7 | 0.1.1-SNAPSHOT 8 | 9 | 10 | 11 | 12 | maven-enforcer-plugin 13 | 1.0.1 14 | 15 | 16 | org.semver 17 | enforcer-rule 18 | 0.9.17 19 | 20 | 21 | 22 | 23 | check 24 | verify 25 | 26 | enforce 27 | 28 | 29 | 30 | 31 | BACKWARD_COMPATIBLE_IMPLEMENTER 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.semver 6 | parent 7 | 0.9.34-SNAPSHOT 8 | pom 9 | 10 | Semantic Versioning 11 | 2010 12 | 13 | 14 | A library to automatically validate if your project's version number follows Semantic Versioning principles 15 | 16 | 17 | 18 | com.github.jeluard 19 | oss-parent 20 | 9 21 | 22 | 23 | 24 | api 25 | enforcer-rule 26 | 27 | 28 | 29 | scm:git:git@github.com:jeluard/semantic-versioning 30 | scm:git:git@github.com:jeluard/semantic-versioning 31 | scm:git:git@github.com:jeluard/semantic-versioning 32 | HEAD 33 | 34 | 35 | 36 | 37 | lefou 38 | Tobias Roeser 39 | 40 | 41 | 42 | 43 | --------------------------------------------------------------------------------