commit 96abad9b8c27057319d330f5e063418b6ca955a6 Author: Fabian Krüger Date: Thu Dec 14 01:26:14 2023 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2974073 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +*.tmp +*.swp +**/*.log +*.iml +**/target/ +build +!**/src/main/**/build/ +!**/src/test/**/build/ +bin/ +internal/ +out/ +.settings/ +.idea/ +.gradle/ +.DS_Store +.classpath +.project +.factorypath +pom.xml.versionsBackup +**/src/generated/java/META-INF +**.java-version +**/.rewrite-cache \ No newline at end of file diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..b901097 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..2cc7d4a Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..10f4009 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# +# Copyright 2021 - 2022 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e1ddf06 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,44 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open +and welcoming community, we pledge to respect all people who contribute through reporting +issues, posting feature requests, updating documentation, submitting pull requests or +patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for +everyone, regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, body size, race, ethnicity, age, +religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic addresses, + without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors +that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to fairly and +consistently applying these principles to every aspect of managing this project. Project +maintainers who do not follow or enforce the Code of Conduct may be permanently removed +from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an +individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by +contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will +be reviewed and investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. Maintainers are obligated to maintain confidentiality +with regard to the reporter of an incident. + +This Code of Conduct is adapted from the +[Contributor Covenant](https://contributor-covenant.org), version 1.3.0, available at +[contributor-covenant.org/version/1/3/0/](https://contributor-covenant.org/version/1/3/0/) \ No newline at end of file diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc new file mode 100644 index 0000000..d801a2c --- /dev/null +++ b/CONTRIBUTING.adoc @@ -0,0 +1,45 @@ += Contributing to Spring Boot Migrator + +Spring Boot Migrator is released under the Apache 2.0 license. +If you would like to contribute something, or want to hack on the code this document should help you get started. + + + +== Code of Conduct +This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.md[code of conduct]. +By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. + + + +== Using GitHub Issues +We use GitHub issues to track bugs and enhancements. + +If you are reporting a bug, please help to speed up problem diagnosis by providing as much information as possible. +Ideally, that would include a small sample project that reproduces the problem. + + +== Reporting Security Vulnerabilities +If you think you have found a security vulnerability in Spring Boot please *DO NOT* disclose it publicly until we've had a chance to fix it. +Please don't report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly. + + + +== Sign the Contributor License Agreement +Before we accept a non-trivial patch or pull request we will need you to https://cla.pivotal.io/sign/spring[sign the Contributor License Agreement]. +Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. +Active contributors might be asked to join the core team, and given the ability to merge pull requests. + + + +== Code Conventions and Housekeeping +None of these is essential for a pull request, but they will all help. They can also be +added after the original pull request but before a merge. + +* Make sure all new `.java` files have a Javadoc class comment with at least an `@author` tag identifying you, and preferably at least a paragraph on what the class is for. +* Add the ASF license header comment to all new `.java` files (copy from existing files in the project). + +The license headers can be generated using the license plugin: `mvn license:format` +* Add yourself as an `@author` to the `.java` files that you modify substantially (more than cosmetic changes). +* Add some Javadocs. +* A few unit tests would help a lot as well -- someone has to do it. +* If no-one else is using your branch, please rebase it against the current main branch (or other target branch in the project). +* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions]. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..2083ace --- /dev/null +++ b/README.adoc @@ -0,0 +1,38 @@ += Spring Rewrite Commons +:partials_dir: spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials +:project-version: 0.1.0-SNAPSHOTS +:projectVersion: {project-version} +:docs: https://docs.spring.io/spring-rewrite-commons/docs/current-SNAPSHOT/reference/html/ + +[quote] +____ +Spring Rewrite Commons provides a set of components to parse a Java project to https://github.com/openrewrite[OpenRewrite,window=_blank] LST and apply recipes outside a build tool plugin. +____ + + +== Get started + +=== Add Dependency + +**Maven** +include::{partials_dir}/maven-dependency-snippet.adoc[] + +**Gradle** +include::{partials_dir}/gradle-dependency-snippet.adoc[] + +=== Implement a Recipe Launcher + +include::{partials_dir}/example-application-code.adoc[] + + +== Reference documentation + +Find the reference documentation link:{docs}[here]. + +== Contributing + +https://help.github.com/articles/creating-a-pull-request[Pull requests] are welcome. Note, that we expect everyone to follow the https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct]. + +== License +Spring Rewrite Commons is Open Source software released under the +https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license]. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..0efd1c7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,6 @@ +# Security Policy + +## Reporting a Vulnerability + +If you think you have found a security vulnerability, please **DO NOT** disclose it publicly until we’ve had a chance to fix it. +Please don’t report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly. \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..bbf07c8 --- /dev/null +++ b/mvnw @@ -0,0 +1,307 @@ +#!/bin/sh +# +# Copyright 2021 - 2022 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" \ No newline at end of file diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..8d4a214 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..03e1f42 --- /dev/null +++ b/pom.xml @@ -0,0 +1,326 @@ + + + 4.0.0 + + org.springframework.rewrite + spring-rewrite-commons + 0.1.0-SNAPSHOT + pom + + Spring Rewrite Commons + Run OpenRewrite recipes without a build tool plugin + https://github.com/spring-projects/spring-rewrite-commons + + + spring-rewrite-commons-launcher + spring-rewrite-commons-docs + spring-rewrite-commons-examples + + + + VMware Inc. + https://spring.io + + + + 17 + 17 + UTF-8 + + + 3.1.3 + 8.5.1 + 5.3.2 + 2.3.1 + + + 3.9.1 + 1.9.13 + 3.5.3 + 1.8 + 3.2.0 + 2.1.0 + + + 0.0.4 + 1.6.2 + 1.5.1 + 0.0.6 + + + 3.11.0 + 3.0.0-M7 + 3.4.1 + 3.3.0 + 3.6.0 + 3.1.1 + 2.16.2 + 0.0.39 + 4.3 + 3.6.1 + 2.2.3 + 2021 + + + + + fkrueger + Fabian Krüger + fkrueger at vmware.com + VMware + http://www.spring.io + + lead + + + + + + https://github.com/spring-projects/rewrite-commons + scm:git:https://github.com/spring-projects/spring-rewrite-commons + scm:git:https://github.com/spring-projects/spring-rewrite-commons.git + spring-rewrite-commons-0.1.0 + + + Github Issues + https://github.com/spring-projects/spring-rewrite-commons/issues + + + Github Actions + https://github.com/spring-projects/spring-rewrite-commons/actions + + + + spring-snapshots + https://repo.spring.io/libs-snapshot-local + default + + false + + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + accessibility,html,reference,syntax + package + true + public + author + true + false + false + *.acme.*:*.example.* + + + + io.spring.javaformat + spring-javaformat-maven-plugin + ${spring-javaformat-maven-plugin.version} + + + validate + true + + validate + + + + + + + + + + io.spring.javaformat + spring-javaformat-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + -parameters + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.junit.jupiter + junit-jupiter-engine + 5.4.0 + + + + + org.codehaus.mojo + versions-maven-plugin + ${versions-maven-plugin.version} + + + com.mycila + license-maven-plugin + ${license-maven-plugin.version} + + + validate + + check + + + + + + the original author or authors. + + 2023 + 2021 + + + + + +Copyright ${inceptionYear} - ${year} the original author or authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + + + **/internal/** + **/demo/** + **/.sdkmanrc + **/*.adoc + **/*.puml + **/.rewrite*/** + **/src/main/resources/banner.txt + **/testcode/** + **/test-code/** + **/pom.xml + **/*.properties + **/*.yaml + **/*.yml + **/*.map + **/*.html + **/*.xhtml + **/*.jsp + **/*.js + **/*.css + **/*.txt + **/*.xjb + **/*.ftl + **/*.xsd + **/*.xml + **/*.sh + **/generated/** + **/Dockerfile + **/META-INF/** + + + + + + + + + + functional-tests + + spring-rewrite-commons-functional-tests + + + + artifactory + + true + + + + + org.jfrog.buildinfo + artifactory-maven-plugin + ${artifactory-maven-plugin.version} + false + + + deploy-to-artifactory + + publish + + + + https://repo.spring.io + + ${env.ARTIFACTORY_USERNAME} + + ${env.ARTIFACTORY_PASSWORD} + libs-milestone-local + libs-snapshot-local + + + CI build for sbm-support-rewrite ${project.version} + + + + + + + + + + spring-milestone + https://repo.spring.io/milestone + + false + + + + spring-snapshot + https://repo.spring.io/snapshot + + false + + + + + + \ No newline at end of file diff --git a/spring-rewrite-commons-docs/.gitignore b/spring-rewrite-commons-docs/.gitignore new file mode 100644 index 0000000..d16f369 --- /dev/null +++ b/spring-rewrite-commons-docs/.gitignore @@ -0,0 +1,4 @@ +node +node_modules +package-lock.json +package.json \ No newline at end of file diff --git a/spring-rewrite-commons-docs/pom.xml b/spring-rewrite-commons-docs/pom.xml new file mode 100644 index 0000000..74ad76e --- /dev/null +++ b/spring-rewrite-commons-docs/pom.xml @@ -0,0 +1,179 @@ + + + 4.0.0 + + + org.springframework.rewrite + spring-rewrite-commons + 0.1.0-SNAPSHOT + + + spring-rewrite-commons-docs + + Docs + Spring Rewrite Commons documentation + + + + + io.spring.maven.antora + antora-maven-plugin + ${io.spring.maven.antora-version} + true + + src/main/antora/antora-playbook.yml + + @antora/atlas-extension@1.0.0-alpha.1 + @antora/collector-extension@1.0.0-alpha.3 + @asciidoctor/tabs@1.0.0-beta.3 + @springio/antora-extensions@1.5.0 + @springio/asciidoctor-extensions@1.0.0-alpha.9 + @djencks/asciidoctor-mathjax@0.0.9 + + + + + io.spring.maven.antora + antora-component-version-maven-plugin + ${io.spring.maven.antora-version} + + + + antora-component-version + + + + + + org.asciidoctor + asciidoctor-maven-plugin + ${asciidoctor-maven-plugin.version} + + + org.asciidoctor + asciidoctorj-pdf + ${asciidoctorj-pdf.version} + + + org.asciidoctor + asciidoctorj-epub3 + ${asciidoctorj-epub.version} + + + io.spring.asciidoctor.backends + spring-asciidoctor-backends + ${spring-asciidoctor-backends.version} + + + + ${project.basedir}/src/main/asciidoc + ${project.build.directory}/asciidoc + + + + generate-html + site + + process-asciidoc + + + spring-html + book + + ${project.build.directory}/asciidoc + shared + css/ + site.css + true + font + + highlight.js + js/highlight + github + + - + ${project.name} + ${project.version} + ${project.version} + ${project.version} + + + + + + generate-pdf + site + + process-asciidoc + + + pdf + book + + font + + + + + coderay + ${project.version} + ${project.version} + + + + + generate-epub + site + + process-asciidoc + + + epub3 + book + + font + + + + + coderay + ${project.version} + ${project.version} + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + src/assembly/docs.xml + + ${project.artifactId}-${project.version} + true + + + + create-distribution + site + + single + + + + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + + + \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/assembly/docs.xml b/spring-rewrite-commons-docs/src/assembly/docs.xml new file mode 100644 index 0000000..723f34a --- /dev/null +++ b/spring-rewrite-commons-docs/src/assembly/docs.xml @@ -0,0 +1,38 @@ + + docs + + zip + + false + + + target/asciidoc + reference/html + + css/*.* + images/*.* + img/*.* + js/**/*.* + *.html + + + + ../target/site/apidocs + api + + + + + target/asciidoc/index-single.pdf + spring-rewrite-commons-reference.pdf + reference/pdf + + + target/asciidoc/index-single.epub + spring-rewrite-commons-reference.epub + reference/epub + + + diff --git a/spring-rewrite-commons-docs/src/javadoc/overview.html b/spring-rewrite-commons-docs/src/javadoc/overview.html new file mode 100644 index 0000000..6d56e96 --- /dev/null +++ b/spring-rewrite-commons-docs/src/javadoc/overview.html @@ -0,0 +1,17 @@ + + +

+ This document is the API specification for Spring AI +

+
+

+ For further API reference and developer documentation, see the + + Spring AI reference documentation. + That documentation contains more detailed, developer-targeted + descriptions, with conceptual overviews, definitions of terms, + and working code examples. +

+
+ + diff --git a/spring-rewrite-commons-docs/src/main/antora/antora-playbook.yml b/spring-rewrite-commons-docs/src/main/antora/antora-playbook.yml new file mode 100644 index 0000000..6c5138e --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/antora-playbook.yml @@ -0,0 +1,39 @@ +# PACKAGES antora@3.2.0-alpha.2 @antora/atlas-extension:1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @springio/antora-extensions@1.1.0-alpha.2 @asciidoctor/tabs@1.0.0-alpha.12 @opendevise/antora-release-line-extension@1.0.0-alpha.2 +# +# The purpose of this Antora playbook is to build the docs in the current branch. +antora: + extensions: + - '@antora/collector-extension' + - require: '@springio/antora-extensions/root-component-extension' + root_component_name: 'ai' +site: + title: Spring AI Reference + url: https://docs.spring.io/spring-rewrite-commons/reference +content: + sources: + - url: ./../../../.. + branches: HEAD + start_path: spring-rewrite-commons-docs/src/main/antora + worktrees: true +asciidoc: + attributes: + page-pagination: '' + hide-uri-scheme: '@' + tabs-sync-option: '@' + chomp: 'all' + stem: 'asciimath' + extensions: + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' + - '@djencks/asciidoctor-mathjax' + sourcemap: true +urls: + latest_version_segment: '' +runtime: + log: + failure_level: warn + format: pretty +ui: + bundle: + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.6/ui-bundle.zip + snapshot: true \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/antora.yml b/spring-rewrite-commons-docs/src/main/antora/antora.yml new file mode 100644 index 0000000..4c64ba9 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/antora.yml @@ -0,0 +1,12 @@ +name: spring-rewrite-commons +version: true +title: Spring Rewrite Commons +nav: + - modules/ROOT/nav.adoc +ext: + collector: + - run: + command: mvnw process-resources + local: true + scan: + dir: spring-rewrite-commons-docs/target/classes/antora-resources \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/lawofcosines.png b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/lawofcosines.png new file mode 100644 index 0000000..46ceadd Binary files /dev/null and b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/lawofcosines.png differ diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/pythagorean-triangle.png b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/pythagorean-triangle.png new file mode 100644 index 0000000..4013db1 Binary files /dev/null and b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/pythagorean-triangle.png differ diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/vector_2d_coordinates.png b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/vector_2d_coordinates.png new file mode 100644 index 0000000..9bc5018 Binary files /dev/null and b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/vector_2d_coordinates.png differ diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/vector_similarity.png b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/vector_similarity.png new file mode 100644 index 0000000..9b8a01b Binary files /dev/null and b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/images/vector_similarity.png differ diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/nav.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/nav.adoc new file mode 100644 index 0000000..9d9b0e4 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/nav.adoc @@ -0,0 +1,8 @@ +* xref:index.adoc[Overview] +* xref:getting-started.adoc[Getting Started] +* xref:concepts.adoc[Concepts] +* xref:components.adoc[Components] +* xref:testing.adoc[Testing] +* xref:contributing.adoc[Contributing] +* Appendices +** xref:properties.adoc[] diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/components.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/components.adoc new file mode 100644 index 0000000..4db1d19 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/components.adoc @@ -0,0 +1,123 @@ += Components + +The following components can be used to parse a project, run recipes and write changes back to the filesystem. +These components are provided as Spring beans and can be injected into other Spring beans that require them. + +== Injecting Spring Rewrite Commons Components +Spring Rewrite Commons offers components as Spring beans that and can be https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html[injected,window=_blank] into existing components. + +[source, java] +.... +@Autowired +private RewriteProjectParser parser; +.... + +== ProjectScanner +Scan a given path to a list of ``https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/Resource.html[Resource,window=_blank]``s using filter definitions provided through xref:properties.adoc[application properties]. + +[source, java] +.... +Path baseDir = ... +List resources = scanner.scan(baseDir); +.... + + + + +== RewriteExecutionContext +OpenRewrite's `ExecutionContext` gets initialized during parsing. +This `ExecutionContext` is required to run recipes and for the inner workings of OpenRewrite. + +== RecipeDiscovery +Discover OpenRewrite recipes on classpath. +These can be custom recipes or recipes provided through https://docs.openrewrite.org/recipes[OpenRewrite's Recipe Catalog] + +=== Access the RecipeDiscovery +The `RecipeDiscovery` component is provided as Spring Bean and can be injected as such, e.g. by using `@Autowired`. + +[source,java] +.... +@Autowired +private RecipeDiscovery discovery; +.... + +=== Discover recipes by name +A single OpenRewrite recipe can be discovered by name. + +[source,java] +.... +Optional recipe = discovery.findRecipeByName("recipe name"); +.... + +=== Discover all recipes +All available OpenRewrite recipes can be discovered. + +[source,java] +.... +List recipes = discovery.findAllRecipes(); +.... + +== ProjectResourceSet +Abstraction of OpenRewrite SourceFiles that allows execution of recipes against the SourceFiles while changes are transparently synchronized with the initial list of SourceFiles. + +=== Creating a ProjectResourceSet +`ProjectResourceSet` is not a Spring bean itself. +The `ProjectResourceSetFactory` is provided as Spring bean and can be injected + +[source, java] +.... +@Autowired +ProjectResourceSetFactory resourceSetFactory; +.... + +and used to create `ProjectResourceSet` instances + +[source, java] +.... +ProjectResourceSet resourceSet = resourceSetFactory.create(baseDir, lst); +.... + +=== Applying recipes + +The `ProjectResourceSet` can be used to sequentially apply OpenRewrite recipes. The results from each recipe run is transparently merged back into the initial LST. + +[source, java] +.... +ProjectResourceSet rs = projectResourceSetFactory.create(baseDir, lst); + +rs.apply(firstRecipe); +rs.apply(secondRecipe); +rs.apply(thirdRecipe); +.... + +== ProjectResourceSetSerializer +`ProjectResourceSetSerializer` helps to synchronize the modified in-memory representation of the scanned project to the filesystem, effectively applying the changes to the codebase (add, update, delete). + +[source, java] +.... +projectResourceSetSerializer.writeChanges(projectResourceSet); +.... + + + + +== Listen to ParserEvents + +``ParserEvent``s get published during parsing. +The events can be used to provide progress information to users. +This is especially useful when parsing large projects where parsing can take some time. + + +* `StartedParsingProjectEvent` - Gets published when the parsing started +* `ParsedResourceEvent` - Gets published after every parsed pom or Java file +* `SuccessfullyParsedProjectEvent` - Gets published when the parsing was successful + +[source,java] +..... +@EventListener(ParsedResourceEvent.class) +public void onParsedResourceEvent(ParsedResourceEvent event) { + Parser.Input input = event.input(); + SourceFile sourceFile = event.sourceFile(); + log("parsed %s to %s".formatted(input.getRelativePath(), sourceFile.getClass().getName())); +} +..... \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/concepts.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/concepts.adoc new file mode 100644 index 0000000..f58431a --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/concepts.adoc @@ -0,0 +1,61 @@ +[[concepts]] += Spring Rewrite Commons Concepts + +This sections describes the important concepts of Spring Rewrite Commons and how they relate to concepts from OpenRewrite. +Running OpenRewrite recipes requires a given project to be parsed to create the LST for it. + +== Parsing a Project +Spring Rewrite Commons aims to yield the same parsing result (LST) as OpenRewrite's `rewrite-maven-plugin`. +This allows access to the LST from outside the execution of `rewrite-maven-plugin`. + +=== RewriteProjectParser +The `RewriteProjectParser` component is a Spring bean and offers the API to parse projects to OpenRewrite LST. + +==== Parse a Path +When the application exists on the local filesystem, the path to the project is enough to parse the project to its LST. + +[source, java] +.... +Path baseDir = ... +RewriteProjectParsingResult result = parser.parse(baseDir); +List lst = result.sourceFiles(); +.... + +==== Parse a List of Resources +It is also possible to provide a list of ``https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/Resource.html[Resource]``s and their baseDir. +This allows to provide synthetic resources that only exist in-memory which is helpful to test migration recipes without disk IO. + +[source, java] +.... +Path baseDir = ... +List resources = ... +List lst = parser.parse(baseDir, List resources); +List lst = result.sourceFiles(); +.... + + +== Provided Scoped Beans +OpenRewrite initializes some while during parsing a project. +These objects provide information required to execute recipes. +Spring Rewrite Commons provides access to these objects through scoped beans. +The lifetime of these beans starts with the successful parse of a project, and ends with the next parse or when the application is closed. + +=== ExecutionContext +OpenRewrite has the concept of an `ExecutionContext`. +The `ExecutionContext` holds information required during recipe execution. +This means the exact same instance needs to be provided to recipe runs. +Spring Rewrite Commons provides the right instance as scoped Spring bean `RewriteExecutionContext`. +This bean can be injected to other Spring Beans in an application and provides access to the `ExecutionContext`. + +[source, java] +.... +@Component +public class MyComponent { + @Autowired + private ExecutionContext executionContext; + + public void runRecipe() { + Recipe recipe = new SomeOpenRewriteRecipe(); + } +} +.... diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/contributing.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/contributing.adoc new file mode 100644 index 0000000..1ef4521 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/contributing.adoc @@ -0,0 +1,30 @@ +== Contributing +You want to contribute to the Spring Boot Migrator project? + +Great! All contributions are welcome. + +If you have not previously done so, please sign the https://cla.pivotal.io/sign/spring[Contributor License Agreement]. You will be reminded automatically when you submit the pull request. + +Please also see link:../../CONTRIBUTING.adoc[CONTRIBUTING.adoc] for further information. + +All files need to have a license header. Please use this copyright text: + +[source, text] +.... +Copyright 2022 the original author or authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +.... + +You can configure license headers under `IntelliJ IDEA/Copyright/Copyright Profile`. +See https://www.jetbrains.com/help/idea/copyright.html#configure-copyright-profiles diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc new file mode 100644 index 0000000..929f231 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc @@ -0,0 +1,28 @@ +[[getting-started]] += Getting Started + +This section offers quick guidance to developers on how to get started using Spring Rewrite Commons. + + +//// +== Create a Simple Spring Boot application +Spring Rewrite Commons is meant to be used in Spring Boot applications. +To create a blank Boot application, go to https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.2.0&packaging=jar&jvmVersion=17&groupId=com.example&artifactId=spring-rewrite-commons-example&name=spring-rewrite-commons-example&description=Demo%20project%20for%20Spring%20Rewrite%20Commons&packageName=com.example.rewrite[start.spring.io,window=_blank] and press "generate" to download a basic Boot application. +//// + + +== Add Repository +include::partials/snapshots-repository.adoc[] + +== Add dependency +The dependency to Spring Rewrite Commons must be added to the build file. + +include::partials/dependency-code.adoc[] + +== Example Application +This dummy code of a Spring application shows how to use the components provided by Spring Rewrite Commons to parse +an application and write back the changes to the filesystem. + +include::partials/example-application-code.adoc[] + +Read more about the xref:components.adoc[available components]. diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/glossary.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/glossary.adoc new file mode 100644 index 0000000..6a990b6 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/glossary.adoc @@ -0,0 +1,3 @@ +[[appendix]] +[[glossary]] += Glossary diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/index.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000..a601980 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/index.adoc @@ -0,0 +1,58 @@ +[[introduction]] + += Spring Rewrite Commons +Fabian Krüger +:revnumber: {projectVersion} +:revdate: {localdate} +:toc: left +:iconfont-fontawesome: + +== Overview +Spring Rewrite Commons provides abstractions and parsers for https://github.com/openrewrite/[OpenRewrite]. +The project can parse Java projects using Maven and apply OpenRewrite recipes without the need of a build tool plugin. +It aims to become the foundation for Spring Boot applications using OpenRewrite. + +https://github.com/openrewrite/[OpenRewrite] is a code refactoring, remediation, and modernization automation tool providing a rich https://docs.openrewrite.org/recipes[catalog of recipes] for tasks like automated code transformations. + +These are the main components offered by Spring Rewrite Commons: + +* `xref:concepts.adoc#_rewriteprojectparser[RewriteProjectParser]` - Parse a project to an OpenRewrite https://docs.openrewrite.org/concepts-explanations/lossless-semantic-trees[Lossless Semantic Tree] (LST). +* `xref:concepts.adoc#_recipediscovery[RewriteRecipeDiscovery]` - Discover recipes on the classpath. +* `xref:concepts.adoc#_projectresourceset[ProjectResourceSet]` - Encapsulates the LST to run https://github.com/openrewrite/[OpenRewrite] recipes sequentially. +* `xref:concepts.adoc#_projectresourcesetserializer[ProjectResourceSetSerializer]` - Serialize the current state of the `ProjectResourceSet` to the filesystem. +* Some helpers for xref:testing.adoc[testíng] also exist + + +== Using Spring Rewrite Commons + +include::partials/snapshots-repository.adoc[] + +Then the dependency can be retrieved. + +include::partials/dependency-code.adoc[] + + +== Examples +Some working examples exist to demo what can be built using Spring Rewrite Commons. + +* file://spring-rewrite-commons-examples/boot-3-upgrade-atomic[Atomically upgrade a Spring Boot application] +* file://spring-rewrite-commons-examples/boot-3-upgrade-iterative[Iteratively upgrade a Spring Boot application using PRs] +* file://spring-rewrite-commons-examples/list-applicable-recipes-example[Find applicable recipes by doing a dry-run] + +All examples can be found file://spring-rewrite-commons-examples[here]. + +== Limitations +The project has currently some limitations. + +. Only JDK 17 supported and required +. No Gradle support. + + OpenRewrite provides a build tool plugin for Gradle projects. + This project currently only parses Maven projects. +. No Kotlin support. + +OpenRewrite can parse other languages than Java, especially Kotlin. This is not yet supported. +. Inherited version will just be read from direct parent. Meaning a version from a parent with distance > 1 will be empty (`null`) +. Maven profiles are currently ignored and 'default' profile is used +. Styles are not supported + +OpenRewrite styles are currently not supported. +. Migrating projects for Maven plugins might fail + +Maven plugin projects (modules) are currently ignored when building the reactor order. \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/dependency-code.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/dependency-code.adoc new file mode 100644 index 0000000..415be50 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/dependency-code.adoc @@ -0,0 +1,10 @@ +[tabs] +====== +Maven:: ++ +include::maven-dependency-snippet.adoc[] + +Gradle:: ++ +include::gradle-dependency-snippet.adoc[] +====== \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/example-application-code.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/example-application-code.adoc new file mode 100644 index 0000000..71780dd --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/example-application-code.adoc @@ -0,0 +1,55 @@ +[source,java] +.... +package com.acme.example; + +import org.openrewrite.Recipe; +import org.openrewrite.SourceFile; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.rewrite.parsers.RewriteProjectParser; +import org.springframework.rewrite.parsers.RewriteProjectParsingResult; +import org.springframework.rewrite.project.resource.ProjectResourceSet; +import org.springframework.rewrite.project.resource.ProjectResourceSetFactory; +import org.springframework.rewrite.project.resource.ProjectResourceSetSerializer; +import org.springframework.rewrite.recipes.RewriteRecipeDiscovery; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; +import java.util.List; + +@Component +public class MyMigrationApplication { + + @Autowired <1> + private RewriteProjectParser parser; + + @Autowired + private RewriteRecipeDiscovery discovery; <2> + + @Autowired + private ProjectResourceSetFactory resourceSetFactory; + + @Autowired + private ProjectResourceSetSerializer serializer; + + + public void migrateToBoot3_1() { + Path baseDir = Path.of("."); <2> + String recipeName = "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1"; + Recipe boot3Upgrade = discovery.getByName(recipeName); <3> + + RewriteProjectParsingResult result = parser.parse(baseDir); <4> + List lst = result.sourceFiles(); <5> + ProjectResourceSet resourceSet = resourceSetFactory.create(baseDir, lst); <6> + resourceSet.apply(boot3Upgrade); <7> + serializer.writeChanges(resourceSet); <8> + } +} +.... +<1> All components are Spring beans and can be injected as such. +<2> The path of the project that should be migrated. +<3> `RewriteRecipeDiscovery` is used to discover an OpenRewrite recipe by name. +<4> `RewriteProjectParser` parses a given project to OpenRewrite LST. +<5> The result contains the list of ``SourceFile``s (the LST). +<6> `ProjectResourceSetFactory` can be used to create a `ProjectResourceSet`. +<7> The recipe is applied to the `ProjectResourceSet` which wraps the LST. +<8> `ProjectResourceSetSerializer` is used to serialize the changes to disk. \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/gradle-dependency-snippet.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/gradle-dependency-snippet.adoc new file mode 100644 index 0000000..5894fd7 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/gradle-dependency-snippet.adoc @@ -0,0 +1,4 @@ +[source,groovy,indent=0,subs="verbatim,quotes,attributes",role="secondary"s] +---- +compile 'org.springframework.rewrite:spring-rewrite-commons-launcher:{projectVersion}' +---- \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/maven-dependency-snippet.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/maven-dependency-snippet.adoc new file mode 100644 index 0000000..2ddafff --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/maven-dependency-snippet.adoc @@ -0,0 +1,8 @@ +[source,xml,indent=0,subs="verbatim,quotes,attributes",role="primary"] +---- + + org.springframwork.rewrite + spring-rewrite-commons-launcher + {project-version} + +---- \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/snapshots-repository.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/snapshots-repository.adoc new file mode 100644 index 0000000..569f375 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/partials/snapshots-repository.adoc @@ -0,0 +1,30 @@ +Currently only SNAPSHOT releases are available from the https://repo.spring.io[Spring Repository,window=_blank]. +The repository information must be added to the project build file. + +[tabs] +====== +Maven:: ++ +[source,xml,indent=0,subs="verbatim,quotes",role="primary"] +..... + + + spring-snapshot + https://repo.spring.io/snapshot + + false + + + +..... + +Gradle:: ++ +[source,groovy,indent=0,subs="verbatim,quotes",role="secondary"] +---- +repositories { + mavenCentral() + maven { url "http://repo.spring.io/libs-snapshot" } +} +---- +====== \ No newline at end of file diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/properties.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/properties.adoc new file mode 100644 index 0000000..9609720 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/properties.adoc @@ -0,0 +1,44 @@ += Appendix + +== Appendix A: Spring Rewrite Commons Configuration Properties + +|=== +|Property |Default Value |Description + +| `parser.pomCacheEnabled` +| `false` +| Set to `true` to use a composite cache of `RocksdbMavenPomCache` and `InMemoryMavenPomCache`. Otherwise, use OpenRewrite's `InMemoryMavenPomCache`. + +| `parser.pomCacheDirectory` +| `~/.rewrite-cache` +| Defines the cache dir for `RocksdbMavenPomCache` when `parser.pomCacheEnabled` is `true`. + +| `parser.skipMavenParsing` +| `false` +| + +| `parser.plainTextMasks` +| `*.txt` +| + +| `parser.sizeThresholdMb` +| `10` +| + +| `parser.runPerSubmodule` +| `false` +| + +| `parser.failOnInvalidActiveRecipes` +| `true` +| + +| `parser.activeProfiles` +| `default` +| + +| `parser.ignoredPathPatterns` +| `**.idea/**,**.git/**,**/target/**,target/**` +| + +|=== diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/providers/huggingface/index.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/providers/huggingface/index.adoc new file mode 100644 index 0000000..06860b9 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/providers/huggingface/index.adoc @@ -0,0 +1,14 @@ +[[hugging-face]] += Hugging Face + +One of the easiest ways you can get access to many machine learning and artificial intelligence models is by using the https://en.wikipedia.org/wiki/Hugging_Face[Hugging Face's] https://huggingface.co/inference-endpoints[Inference Endpoints]. + +Hugging Face Hub is a platform that provides a collaborative environment for creating and sharing tens of thousands of Open Source ML/AI models, data sets, and demo applications. + +Inference Endpoints let you deploy AI Models on dedicated infrastructure with a pay-as-you-go billing model. +You can use infrastructure provided by Amazon Web Services, Microsoft Azure, and Google Cloud Platform. +Hugging Face lets you run the models on your own machine, but it is quite common to not have enough CPU/GPU resources to run the larger, more AI-focused models. + +It provides access to Meta's recent (August 2023) Llama 2 and CodeLlama 2 models and provides the https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard[Open LLM Leaderboard], where you can quickly discover high quality models. + +While Hugging Face has a free hosting tier, which is very useful for quickly evaluating if a specific ML/AI Model fits your needs, they do not let you access many of those models on the free tier by using the https://huggingface.co/docs/text-generation-inference/main/en/index[Text Generation Interface API]. If you want to end up on production anyway, with a stable API, pay a few cents to try out a reliable solution. Prices are as low as $0.06 per CPU core/hr and $0.6 per GPU/hr. diff --git a/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/testing.adoc b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/testing.adoc new file mode 100644 index 0000000..44129a8 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/modules/ROOT/pages/testing.adoc @@ -0,0 +1,10 @@ += Testing + +The `RewriteProjectParser` + +== Test Helper + +=== TestProjectHelper +=== ParserParityTestHelper +=== ParserExecutionHelper +=== RewriteMavenProjectParser diff --git a/spring-rewrite-commons-docs/src/main/antora/resources/antora-resources/antora.yml b/spring-rewrite-commons-docs/src/main/antora/resources/antora-resources/antora.yml new file mode 100644 index 0000000..aae94d4 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/antora/resources/antora-resources/antora.yml @@ -0,0 +1,7 @@ +version: ${antora-component.version} +prerelease: ${antora-component.prerelease} +asciidoc: + attributes: + attribute-missing: 'warn' + version: ${project.version} + projectversion: ${project.version} diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/aiclient.adoc b/spring-rewrite-commons-docs/src/main/asciidoc/aiclient.adoc new file mode 100644 index 0000000..d418dca --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/aiclient.adoc @@ -0,0 +1,15 @@ +:toc: left +:toclevels: 4 + +[[configureClient]] +== Configuring an `AiClient` + +include::attributes.adoc[] + +TBD + +[[usingAiClient]] +=== Using AiClient + +TBD + diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/attributes.adoc b/spring-rewrite-commons-docs/src/main/asciidoc/attributes.adoc new file mode 100644 index 0000000..e8007df --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/attributes.adoc @@ -0,0 +1 @@ +:ai-asciidoc: ./ diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/domain.adoc b/spring-rewrite-commons-docs/src/main/asciidoc/domain.adoc new file mode 100644 index 0000000..a3c1d8b --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/domain.adoc @@ -0,0 +1,19 @@ +:toc: left +:toclevels: 4 + +[[domainLanguageOfAi]] +== The Domain Language of AI + +include::attributes.adoc[] + +TBD + + +=== Prompt + +TBD + + +==== AiClient + +TBD diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/footer/index-footer.adoc b/spring-rewrite-commons-docs/src/main/asciidoc/footer/index-footer.adoc new file mode 100644 index 0000000..5351a73 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/footer/index-footer.adoc @@ -0,0 +1,9 @@ +''' +Mark Pollack + +Copyright © 2023 VMware, Inc. All Rights Reserved. + +Copies of this document may be made for your own use and for +distribution to others, provided that you do not charge any fee for such +copies and further provided that each copy contains this Copyright +Notice, whether distributed in print or electronically. diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/glossary.adoc b/spring-rewrite-commons-docs/src/main/asciidoc/glossary.adoc new file mode 100644 index 0000000..55835e7 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/glossary.adoc @@ -0,0 +1,6 @@ +[[glossary]] +[appendix] +== Glossary + +[glossary] +=== Spring AI Glossary diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/header/index-header.adoc b/spring-rewrite-commons-docs/src/main/asciidoc/header/index-header.adoc new file mode 100644 index 0000000..0fcf3f2 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/header/index-header.adoc @@ -0,0 +1 @@ += Spring AI - Reference Documentation diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/index-single.adoc b/spring-rewrite-commons-docs/src/main/asciidoc/index-single.adoc new file mode 100644 index 0000000..893e64f --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/index-single.adoc @@ -0,0 +1,20 @@ +:doctype: book +:toc: left +:toclevels: 4 +:sectnums: + +include::attributes.adoc[] + +include::header/index-header.adoc[] + +include::toggle.adoc[] + +include::spring-ai-intro.adoc[] + +include::domain.adoc[] + +include::prompt.adoc[] + +include::aiclient.adoc[] + +include::glossary.adoc[] diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/index.adoc b/spring-rewrite-commons-docs/src/main/asciidoc/index.adoc new file mode 100644 index 0000000..85a4295 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/index.adoc @@ -0,0 +1,27 @@ +include::attributes.adoc[] + +include::header/index-header.adoc[] + +// ====================================================================================== + +This documentation is also available +as a link:index-single.html[single HTML file] and as link:../pdf/spring-ai-reference.pdf[PDF] +and link:../epub/spring-ai-reference.epub[EPUB] documents. + +The reference documentation is divided into several sections: + +[horizontal] +<> :: Background, usage + scenarios, and general guidelines. +<> :: Core concepts and abstractions +of the AI domain language. +<> :: Prompt creation. +<> :: AiClient configuration and execution. + +The following appendices are available: + +[horizontal] +<> :: Glossary of common terms, concepts, and vocabulary of +the AI domain. + +include::footer/index-footer.adoc[] diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/js/DocumentToggle.js b/spring-rewrite-commons-docs/src/main/asciidoc/js/DocumentToggle.js new file mode 100644 index 0000000..c2941f3 --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/js/DocumentToggle.js @@ -0,0 +1,76 @@ +$(document).ready(function(){ + + var BATCH_LANGUAGES = ["java", "xml", "both"]; + var $xmlButton = $("#xmlButton"); + var $javaButton = $("#javaButton"); + var $bothButton = $("#bothButton"); + + var $xmlContent = $("*.xmlContent"); + var $xmlContentAll = $("*.xmlContent > *"); + + var $javaContent = $("*.javaContent"); + var $javaContentAll = $("*.javaContent > *"); + + // Initial cookie handler. This part remembers the + // reader's choice and sets the toggle accordingly. + var lang = window.localStorage.getItem("docToggle"); + if (BATCH_LANGUAGES.indexOf(lang) === -1) { + lang = "java"; + $javaButton.prop("checked", true); + setJava(); + } else { + if (lang === "xml") { + $xmlButton.prop("checked", true); + setXml(); + } + if (lang === "java") { + $javaButton.prop("checked", true); + setJava(); + } + if (lang === "both") { + $javaButton.prop("checked", true); + setBoth(); + } + } + + // Click handlers + $xmlButton.on("click", function() { + setXml(); + }); + $javaButton.on("click", function() { + setJava(); + }); + $bothButton.on("click", function() { + setBoth(); + }); + + // Functions to do the work of handling the reader's choice, whether through a click + // or through a cookie. 3652 days is 10 years, give or take a leap day. + function setXml() { + $xmlContent.show(); + $javaContent.hide(); + $javaContentAll.addClass("js-toc-ignore"); + $xmlContentAll.removeClass("js-toc-ignore"); + window.dispatchEvent(new Event("tocRefresh")); + window.localStorage.setItem('docToggle', 'xml'); + } + + function setJava() { + $javaContent.show(); + $xmlContent.hide(); + $xmlContentAll.addClass("js-toc-ignore"); + $javaContentAll.removeClass("js-toc-ignore"); + window.dispatchEvent(new Event("tocRefresh")); + window.localStorage.setItem('docToggle', 'java'); + } + + function setBoth() { + $javaContent.show(); + $xmlContent.show(); + $javaContentAll.removeClass("js-toc-ignore"); + $xmlContentAll.removeClass("js-toc-ignore"); + window.dispatchEvent(new Event("tocRefresh")); + window.localStorage.setItem('docToggle', 'both'); + } + +}); diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/js/Redirect.js b/spring-rewrite-commons-docs/src/main/asciidoc/js/Redirect.js new file mode 100644 index 0000000..e69de29 diff --git a/spring-rewrite-commons-docs/src/main/asciidoc/js/jquery-3.2.1.min.js b/spring-rewrite-commons-docs/src/main/asciidoc/js/jquery-3.2.1.min.js new file mode 100644 index 0000000..644d35e --- /dev/null +++ b/spring-rewrite-commons-docs/src/main/asciidoc/js/jquery-3.2.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), +a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), +null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" + + + +
+ + + +
+ ++++ +endif::backend-spring-html[] diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-atomic/.gitignore b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-atomic/README.adoc b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/README.adoc new file mode 100644 index 0000000..c9ead6a --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/README.adoc @@ -0,0 +1,13 @@ += Atomic Upgrade to Boot 3 + +This example shows how to implement a simple Boot application that uses `spring-rewrite-commons` to apply a recipe that upgrades a given Spring application (<3.1) to Spring Boot 3.1. + +== Running the example + +WARNING: The example will change the sources in the provided project. + +From the module dir + +. run `mvn clean install` +. run `java -jar target/boot-3-upgrade-atomic-0.1.0-SNAPSHOT.jar ` +. Changes were applied to `` \ No newline at end of file diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-atomic/pom.xml b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/pom.xml new file mode 100644 index 0000000..9319c93 --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + org.springframework.rewrite + spring-rewrite-commons-examples + 0.1.0-SNAPSHOT + ../pom.xml + + + com.example + boot-3-upgrade-atomic + + Boot 3 Atomic Upgrade Example + + + 17 + 17 + UTF-8 + 3.1.3 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + + org.apache.maven.wagon + wagon-http + + + org.apache.maven.resolver + maven-resolver-transport-wagon + + + org.apache.maven.resolver + maven-resolver-connector-basic + + + org.apache.maven + maven-compat + + + org.apache.maven + maven-embedder + + + org.codehaus.plexus + plexus-cipher + + + org.eclipse.sisu + org.eclipse.sisu.plexus + + + com.acme.example.SpringBoot3Upgrade + + + + + + \ No newline at end of file diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-atomic/src/main/java/com/acme/example/SpringBoot3Upgrade.java b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/src/main/java/com/acme/example/SpringBoot3Upgrade.java new file mode 100644 index 0000000..f7ea9eb --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/src/main/java/com/acme/example/SpringBoot3Upgrade.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.acme.example; + +import org.openrewrite.Recipe; +import org.openrewrite.SourceFile; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.rewrite.parsers.RewriteProjectParser; +import org.springframework.rewrite.parsers.RewriteProjectParsingResult; +import org.springframework.rewrite.project.resource.ProjectResourceSet; +import org.springframework.rewrite.project.resource.ProjectResourceSetFactory; +import org.springframework.rewrite.project.resource.ProjectResourceSetSerializer; +import org.springframework.rewrite.recipes.RewriteRecipeDiscovery; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +/** + * @author Fabian Krüger + */ +@SpringBootApplication +public class SpringBoot3Upgrade implements CommandLineRunner { + + public static final String RECIPE_NAME = "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1"; + + public static void main(String[] args) { + SpringApplication.run(SpringBoot3Upgrade.class, args); + } + + private final RewriteProjectParser parser; + + private final RewriteRecipeDiscovery discovery; + + private final ProjectResourceSetSerializer serializer; + + private final ProjectResourceSetFactory projectResourceSetFactory; + + public SpringBoot3Upgrade(RewriteProjectParser parser, RewriteRecipeDiscovery discovery, + ProjectResourceSetSerializer serializer, ProjectResourceSetFactory projectResourceSetFactory) { + this.parser = parser; + this.discovery = discovery; + this.serializer = serializer; + this.projectResourceSetFactory = projectResourceSetFactory; + } + + @Override + public void run(String... args) { + Path baseDir; + if (args.length == 0) { + throw new IllegalArgumentException("A path must be provided"); + } + else { + String pathStr = args[0]; + baseDir = Path.of(pathStr); + } + + // parse + RewriteProjectParsingResult parsingResult = parser.parse(baseDir); + List sourceFiles = parsingResult.sourceFiles(); + ProjectResourceSet projectResourceSet = projectResourceSetFactory.create(baseDir, sourceFiles); + + // discover + List recipes = discovery.discoverRecipes(); + Optional recipe = recipes.stream().filter(r -> RECIPE_NAME.equals(r.getName())).findFirst(); + + // apply + recipe.ifPresent((Recipe r) -> { + projectResourceSet.apply(r); + serializer.writeChanges(projectResourceSet); + }); + } + +} diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-atomic/src/main/resources/application.properties b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/src/main/resources/application.properties new file mode 100644 index 0000000..96cd9be --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-atomic/src/main/resources/application.properties @@ -0,0 +1,2 @@ +logging.level.root=warn +logging.level.org.springframework.sbm=debug \ No newline at end of file diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.gitignore b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.mvn/wrapper/maven-wrapper.jar b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..cb28b0e Binary files /dev/null and b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.mvn/wrapper/maven-wrapper.properties b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..5f0536e --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/README.adoc b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/README.adoc new file mode 100644 index 0000000..d3fc676 --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/README.adoc @@ -0,0 +1,4 @@ += Iterative Upgrade to Boot 3 using PRs + +This example shows how to implement a simple Boot application that uses `spring-rewrite-commons` to apply recipes that upgrade the Spring Boot PetClinic from 2.3.x to Spring Boot 3.1 by applying recipes to upgrade from minor to minor version by creating PRs for every recipe run. +No new recipes are applied until the last PR has been merged or closed. \ No newline at end of file diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/mvnw b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/mvnw new file mode 100755 index 0000000..66df285 --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/mvnw.cmd b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/mvnw.cmd new file mode 100644 index 0000000..95ba6f5 --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/pom.xml b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/pom.xml new file mode 100644 index 0000000..f0b44f3 --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + + org.springframework.rewrite + spring-rewrite-commons-examples + 0.1.0-SNAPSHOT + ../pom.xml + + + com.example + boot-3-upgrade-iterative + + Boot 3 Iterative Upgrade Example + Example of iterative Boot upgrade with PRs + + + 17 + 1.317 + + + + + org.kohsuke + github-api + ${github-api.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + com.example.bootupgrade.IterativeBoot3UpgradeExample + + paketobuildpacks/builder-jammy-base:latest + + + + org.projectlombok + lombok + + + + + + + + diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/java/com/example/bootupgrade/BootUpgradeIterativeApplication.java b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/java/com/example/bootupgrade/BootUpgradeIterativeApplication.java new file mode 100644 index 0000000..991e3f4 --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/java/com/example/bootupgrade/BootUpgradeIterativeApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.bootupgrade; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BootUpgradeIterativeApplication { + + public static void main(String[] args) { + + SpringApplication.run(BootUpgradeIterativeApplication.class, args); + } + +} diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/java/com/example/bootupgrade/IterativeBoot3UpgradeExample.java b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/java/com/example/bootupgrade/IterativeBoot3UpgradeExample.java new file mode 100644 index 0000000..fa8cc7a --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/java/com/example/bootupgrade/IterativeBoot3UpgradeExample.java @@ -0,0 +1,345 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.bootupgrade; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; +import org.openrewrite.Recipe; +import org.openrewrite.SourceFile; +import org.openrewrite.shaded.jgit.api.CreateBranchCommand; +import org.openrewrite.shaded.jgit.api.Git; +import org.openrewrite.shaded.jgit.api.ListBranchCommand; +import org.openrewrite.shaded.jgit.api.ResetCommand; +import org.openrewrite.shaded.jgit.api.errors.GitAPIException; +import org.openrewrite.shaded.jgit.lib.Ref; +import org.openrewrite.shaded.jgit.revwalk.RevCommit; +import org.openrewrite.shaded.jgit.transport.UsernamePasswordCredentialsProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.event.EventListener; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.parsers.*; +import org.springframework.rewrite.parsers.events.StartedParsingResourceEvent; +import org.springframework.rewrite.project.resource.ProjectResourceSet; +import org.springframework.rewrite.project.resource.ProjectResourceSetFactory; +import org.springframework.rewrite.project.resource.ProjectResourceSetSerializer; +import org.springframework.rewrite.recipes.RewriteRecipeDiscovery; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.util.FileSystemUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * @author Fabian Krüger + */ +@SpringBootApplication +@EnableScheduling +@Slf4j +public class IterativeBoot3UpgradeExample implements ApplicationRunner { + + private static final String MAIN_BRANCH = "main"; + + public static final String GH_ORG_NAME = "fabapp2"; + + public static final String GH_REPO_NAME = "spring-petclinic-detached"; + + public static final String GIT_COMMIT_HASH = "baafc5a80c988cffa0c11e88859143308e850f08"; + + private String repoUrl; + + private String ghToken; + + public static void main(String[] args) { + SpringApplication.run(IterativeBoot3UpgradeExample.class, args); + } + + private static Map> versionToRecipeMap = new HashMap<>(); + + static { + versionToRecipeMap = Map.of("2.3.5.RELEASE", List.of("org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4"), + + "2.4.13", List.of("org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5"), + + "2.5.15", List.of("org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6"), + + "2.6.15", List.of("org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7"), + + "2.7.17", List.of("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0"), + + "3.0.12", List.of("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1"), + + "3.1.5", List.of("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2")); + // map.keySet() + // .stream() + // .forEach(keys -> { + // keys.stream() + // .forEach(k -> { + // versionToRecipeMap.put(k, map.get(keys)); + // }); + // }); + + } + + @Autowired + private RewriteProjectParser parser; + + @Autowired + private ProjectScanner scanner; + + @Autowired + private RewriteRecipeDiscovery discovery; + + @Autowired + private ProjectResourceSetSerializer serializer; + + @Autowired + private ProjectResourceSetFactory projectResourceSetFactory; + + @EventListener(StartedParsingResourceEvent.class) + public void onStartedParsingResourceEvent(StartedParsingResourceEvent event) { + } + + @Override + public void run(ApplicationArguments args) { + repoUrl = getRepoUrl(args); + ghToken = getGitHubToken(args); + } + + @Scheduled(fixedDelay = 10_000) + void loop() throws IOException, GitAPIException { + log.info("Scheduling..."); + if (repoUrl == null || ghToken == null) { + return; + } + // clone + Path baseDir = cloneFreshRepo(repoUrl); + + String pomXml = Files.readString(baseDir.resolve("pom.xml")); + String currentBootVersion = extractSpringBootVersion(pomXml); + + if (versionToRecipeMap.containsKey(currentBootVersion)) { + Optional newBootVersionOpt = calculateNextBootVersion(currentBootVersion); + if (newBootVersionOpt.isPresent()) { + String newBootVersion = newBootVersionOpt.get(); + log.info("Found migration path to Spring Boot %s".formatted(newBootVersion)); + String branchName = calculateBranchName(newBootVersion); + Git git = Git.open(baseDir.resolve(".git").toFile()); + if (branchExists(git, branchName)) { + log.info("There might be an open PR? Otherwise please delete this remote branch: " + branchName); + return; + } + // find recipe name + List recipeNames = versionToRecipeMap.get(currentBootVersion); + // parse + ProjectResourceSet projectResourceSet = parseProject(baseDir); + // discover + List recipes = discoverRecipes(recipeNames); + // apply + applyRecipe(projectResourceSet, recipes); + // create branch + Ref branchRef = createBranch(git, branchName); + checkout(git, branchName); + // commit + String message = "Upgrade Spring Boot from %s to %s".formatted(currentBootVersion, newBootVersion); + RevCommit commit = commit(git, message); + log.info("commit: " + commit); + push(git, branchRef, ghToken); + + // Create PR + String mainBranch = "main"; + createPullRequest(ghToken, message, branchName, mainBranch, message); + } + else { + log.info("No migration path found."); + } + } + else { + log.info("No migration found for Spring Boot version: %s".formatted(currentBootVersion)); + } + } + + private boolean branchExists(Git git, String branchName) { + try { + git.fetch().call(); + // FIXME: This works only in this example. origin could be different + return git.branchList() + .setListMode(ListBranchCommand.ListMode.REMOTE) + .call() + .stream() + .anyMatch(b -> "refs/remotes/origin/%s".formatted(branchName).equals(b.getName())); + } + catch (GitAPIException e) { + throw new RuntimeException(e); + } + } + + private String calculateBranchName(String newBootVersion) { + return "app-analyzer/boot-upgrade-%s".formatted(newBootVersion.replace(".", "")); + } + + private Optional calculateNextBootVersion(String currentBootVersion) { + List sortedVersions = versionToRecipeMap.keySet().stream().sorted(String::compareTo).toList(); + int i = sortedVersions.indexOf(currentBootVersion); + if (i < sortedVersions.size() - 1) { + return Optional.of(sortedVersions.get(i + 1)); + } + return Optional.empty(); + } + + private String extractSpringBootVersion(String pomXml) { + try { + ObjectMapper mm = new XmlMapper(); + String version = mm.readTree(pomXml).get("parent").get("version").textValue(); + return version; + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private static void checkout(Git git, String branchName) throws GitAPIException { + git.checkout().setName(branchName).call(); + } + + private String getGitHubToken(ApplicationArguments args) { + if (args.getNonOptionArgs().size() < 2) { + throw new IllegalArgumentException("The GitHub token must be provided as second parameter."); + } + return args.getNonOptionArgs().get(1); + } + + private void push(Git git, Ref branch, String ghToken) throws GitAPIException { + git.push().add(branch).setCredentialsProvider(new UsernamePasswordCredentialsProvider(ghToken, "")).call(); + } + + private static RevCommit commit(Git git, String message) throws GitAPIException { + git.add().addFilepattern(".").call(); + return git.commit().setMessage(message).call(); + } + + private static Ref createBranch(Git git, String branchName) throws GitAPIException { + Ref branchRef = git.branchCreate() + .setName(branchName) + .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.SET_UPSTREAM) + .setForce(true) + .call(); + return branchRef; + } + + private ProjectResourceSet parseProject(Path baseDir) { + // scan + List resources = scanner.scan(baseDir); + // parse + RewriteProjectParsingResult parsingResult = parser.parse(baseDir, resources); + List sourceFiles = parsingResult.sourceFiles(); + ProjectResourceSet projectResourceSet = projectResourceSetFactory.create(baseDir, sourceFiles); + return projectResourceSet; + } + + @NotNull + private Path cloneFreshRepo(String repoUrl) throws IOException { + Path targetDir = Path.of(System.getProperty("java.io.tmpdir")).resolve(GH_REPO_NAME); // Files.createTempDirectory(GH_REPO_NAME).toAbsolutePath(); + if (targetDir.toFile().exists()) { + FileSystemUtils.deleteRecursively(targetDir); + } + String commitHash = null;// GIT_COMMIT_HASH; // + // https://github.com/spring-projects/spring-petclinic/commit/a3294f2 + cloneFreshRepo(repoUrl, targetDir, commitHash); + System.out.println("Cloned to %s".formatted(targetDir)); + return targetDir; + } + + private static String getRepoUrl(ApplicationArguments args) { + if (args.getNonOptionArgs().isEmpty()) { + throw new IllegalArgumentException("A repository URL must be provided."); + } + String repoUrl = args.getNonOptionArgs().get(0); + return repoUrl; + } + + private void cloneFreshRepo(String repoUrl, Path targetDir, String gitHash) { + try { + File directory = targetDir.toFile(); + Git git = Git.cloneRepository().setDirectory(directory).setURI(repoUrl).call(); + + if (gitHash != null) { + git.reset().setMode(ResetCommand.ResetType.HARD).setRef(GIT_COMMIT_HASH); + } + } + catch (GitAPIException e) { + throw new RuntimeException(e); + } + } + + private GHPullRequest createPullRequest(String ghToken, String name, String head, String base, String body) { + try { + GitHub gitHub = new GitHubBuilder().withOAuthToken(ghToken).build(); + + GHRepository repo = gitHub + /* .getOrganization(GH_ORG_NAME) */ + .getRepository(GH_ORG_NAME + "/" + GH_REPO_NAME); + + GHPullRequest pr = repo.createPullRequest(name, head, base, body); + log.info("Created PR '%s'".formatted(name)); + return pr; + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void applyRecipe(ProjectResourceSet projectResourceSet, List recipes) { + // FIXME: If recipes is a list they need to be provided through + // Recipe.getRecipeList() to not require a new parse + log.info("Applying recipes %s".formatted(recipes.stream().map(Recipe::getDisplayName).toList())); + if (recipes.size() > 1) + throw new UnsupportedOperationException("Can only handle single recipes."); + recipes.forEach(r -> this.applyRecipe(projectResourceSet, r)); + } + + private void applyRecipe(ProjectResourceSet projectResourceSet, Recipe recipe) { + projectResourceSet.apply(recipe); + serializer.writeChanges(projectResourceSet); + System.out.println("Applied recipe " + recipe.getDisplayName()); + } + + private List discoverRecipes(List recipeNames) { + List recipes = discovery.discoverRecipes(); + List matchingRecipes = recipes.stream().filter(r -> recipeNames.contains(r.getName())).toList(); + return matchingRecipes; + } + +} diff --git a/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/resources/application.properties b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/spring-rewrite-commons-examples/boot-3-upgrade-iterative/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/spring-rewrite-commons-examples/list-applicable-recipes-example/.gitignore b/spring-rewrite-commons-examples/list-applicable-recipes-example/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/spring-rewrite-commons-examples/list-applicable-recipes-example/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/spring-rewrite-commons-examples/list-applicable-recipes-example/.mvn/wrapper/maven-wrapper.jar b/spring-rewrite-commons-examples/list-applicable-recipes-example/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..cb28b0e Binary files /dev/null and b/spring-rewrite-commons-examples/list-applicable-recipes-example/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-rewrite-commons-examples/list-applicable-recipes-example/.mvn/wrapper/maven-wrapper.properties b/spring-rewrite-commons-examples/list-applicable-recipes-example/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..5f0536e --- /dev/null +++ b/spring-rewrite-commons-examples/list-applicable-recipes-example/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/spring-rewrite-commons-examples/list-applicable-recipes-example/mvnw b/spring-rewrite-commons-examples/list-applicable-recipes-example/mvnw new file mode 100755 index 0000000..66df285 --- /dev/null +++ b/spring-rewrite-commons-examples/list-applicable-recipes-example/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-rewrite-commons-examples/list-applicable-recipes-example/mvnw.cmd b/spring-rewrite-commons-examples/list-applicable-recipes-example/mvnw.cmd new file mode 100644 index 0000000..95ba6f5 --- /dev/null +++ b/spring-rewrite-commons-examples/list-applicable-recipes-example/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/spring-rewrite-commons-examples/list-applicable-recipes-example/pom.xml b/spring-rewrite-commons-examples/list-applicable-recipes-example/pom.xml new file mode 100644 index 0000000..37b1515 --- /dev/null +++ b/spring-rewrite-commons-examples/list-applicable-recipes-example/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + org.springframework.rewrite + spring-rewrite-commons-examples + 0.1.0-SNAPSHOT + ../pom.xml + + + org.example + list-applicable-recipes-example + + List Applicable Recipes Example + Example project listing applicable recipes for a given application + + + 17 + + + + + + org.openrewrite.recipe + rewrite-recipe-bom + 2.2.0 + pom + import + + + + + + + org.openrewrite.recipe + rewrite-migrate-java + + + org.openrewrite.recipe + rewrite-spring + + + org.openrewrite.recipe + rewrite-hibernate + + + org.openrewrite.recipe + rewrite-java-dependencies + + + org.openrewrite.recipe + rewrite-testing-frameworks + + + org.openrewrite.recipe + rewrite-static-analysis + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + spring-snapshot + https://repo.spring.io/snapshot + + false + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + + diff --git a/spring-rewrite-commons-examples/list-applicable-recipes-example/src/main/java/org/example/app/ListApplicableRecipesExampleApplication.java b/spring-rewrite-commons-examples/list-applicable-recipes-example/src/main/java/org/example/app/ListApplicableRecipesExampleApplication.java new file mode 100644 index 0000000..6e2f00e --- /dev/null +++ b/spring-rewrite-commons-examples/list-applicable-recipes-example/src/main/java/org/example/app/ListApplicableRecipesExampleApplication.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.app; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.RecipeRun; +import org.openrewrite.SourceFile; +import org.openrewrite.config.RecipeDescriptor; +import org.openrewrite.internal.InMemoryLargeSourceSet; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.parsers.ProjectScanner; +import org.springframework.rewrite.parsers.RewriteProjectParser; +import org.springframework.rewrite.parsers.RewriteProjectParsingResult; +import org.springframework.rewrite.recipes.RewriteRecipeDiscovery; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +@SpringBootApplication +public class ListApplicableRecipesExampleApplication implements ApplicationRunner { + + private final RewriteRecipeDiscovery discovery; + + private final ProjectScanner scanner; + + private final RewriteProjectParser parser; + + private final ExecutionContext executionContext; + + public ListApplicableRecipesExampleApplication(RewriteRecipeDiscovery discovery, ProjectScanner scanner, + RewriteProjectParser parser, ExecutionContext executionContext) { + this.discovery = discovery; + this.scanner = scanner; + this.parser = parser; + this.executionContext = executionContext; + } + + public static void main(String[] args) { + SpringApplication.run(ListApplicableRecipesExampleApplication.class, args); + } + + @Override + public void run(ApplicationArguments args) { + Path baseDir = Path.of(args.getNonOptionArgs().get(0)); + List recipeNames = Arrays.asList(args.getNonOptionArgs().get(1).split(",")); + List recipes = discovery.discoverRecipes(); + List matchingRecipes = recipes.stream().filter(r -> recipeNames.contains(r.getName())).toList(); + List resources = scanner.scan(baseDir); + // parse + RewriteProjectParsingResult parsingResult = parser.parse(baseDir, resources); + List sourceFiles = parsingResult.sourceFiles(); + matchingRecipes.forEach(recipe -> { + RecipeRun recipeRun = recipe.run(new InMemoryLargeSourceSet(sourceFiles), executionContext); + recipeRun.getChangeset().getAllResults().forEach(result -> { + result.getRecipeDescriptorsThatMadeChanges().forEach(rd -> { + printResourceDescriptor(0, rd); + }); + }); + }); + } + + private static void printResourceDescriptor(int indent, RecipeDescriptor rd) { + String recipeThatWouldMakeChanges = rd.getName(); + System.out.println(" ".repeat(indent) + recipeThatWouldMakeChanges); + rd.getRecipeList().forEach(re -> { + printResourceDescriptor(indent + 1, re); + }); + } + +} diff --git a/spring-rewrite-commons-examples/list-applicable-recipes-example/src/main/resources/application.properties b/spring-rewrite-commons-examples/list-applicable-recipes-example/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/spring-rewrite-commons-examples/list-applicable-recipes-example/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/spring-rewrite-commons-examples/pom.xml b/spring-rewrite-commons-examples/pom.xml new file mode 100644 index 0000000..30084a9 --- /dev/null +++ b/spring-rewrite-commons-examples/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + org.springframework.rewrite + spring-rewrite-commons + 0.1.0-SNAPSHOT + ../pom.xml + + + spring-rewrite-commons-examples + pom + + Spring Rewrite Commons - Examples + Examples using Spring Rewrite Commons + + + boot-3-upgrade-atomic + boot-3-upgrade-iterative + list-applicable-recipes-example + + + + 17 + 5.0.10 + 2.3.0 + + + + + + org.springframework.rewrite + spring-rewrite-commons-launcher + ${project.version} + + + org.openrewrite.recipe + rewrite-spring + ${rewrite-spring.version} + + + org.openrewrite.recipe + rewrite-migrate-java + ${rewrite-migrate-java.version} + + + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.rewrite + spring-rewrite-commons-launcher + + + org.openrewrite.recipe + rewrite-spring + + + org.openrewrite.recipe + rewrite-migrate-java + + + org.projectlombok + lombok + true + + + diff --git a/spring-rewrite-commons-functional-tests/pom.xml b/spring-rewrite-commons-functional-tests/pom.xml new file mode 100644 index 0000000..8fba74b --- /dev/null +++ b/spring-rewrite-commons-functional-tests/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + org.springframework.rewrite + spring-rewrite-commons + 0.1.0-SNAPSHOT + ../pom.xml + + + spring-rewrite-commons-functional-tests + pom + + Spring Rewrite Commons - Functional Tests + https://github.com/spring-projects/spring-rewrite-commons + + + VMware Inc. + https://spring.io + + + + private-artifact-repository-tests + + + + 17 + 17 + UTF-8 + + + 3.1.3 + 5.3.2 + 2.3.1 + 5.3.2 + 2.3.1 + + + 3.9.1 + 1.9.13 + 3.5.3 + 1.8 + 3.2.0 + 2.1.0 + + + 3.11.0 + 3.0.0-M7 + 3.4.1 + 2.16.2 + 3.3.0 + 0.0.39 + 4.1 + 3.6.1 + + + 1.18.28 + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/pom.xml b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/pom.xml new file mode 100644 index 0000000..67ecc6c --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/pom.xml @@ -0,0 +1,168 @@ + + + 4.0.0 + + + org.springframework.rewrite + spring-rewrite-commons-functional-tests + 0.1.0-SNAPSHOT + ../pom.xml + + + private-artifact-repository-tests + + Private Artifact Repo Test + Functional tests for Spring Rewrite Commons using a private artifact repository + + + + org.springframework.rewrite + spring-rewrite-commons-launcher + 0.1.0-SNAPSHOT + + + org.springframework.rewrite + spring-rewrite-commons-launcher + test + test-jar + 0.1.0-SNAPSHOT + test + + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.codehaus.plexus + plexus-sec-dispatcher + 2.0 + + + + org.apache.maven + maven-embedder + ${maven.version} + + + org.sonatype.plexus + plexus-cipher + + + + + commons-cli + commons-cli + 1.4 + + + + org.openrewrite.maven + rewrite-maven-plugin + ${rewrite-maven-plugin.version} + test + + + org.apache.maven.wagon + wagon-http + ${maven-wagon-http.version} + test + + + org.apache.maven.resolver + maven-resolver-transport-wagon + ${maven-resolver.version} + test + + + org.apache.maven.resolver + maven-resolver-connector-basic + ${maven-resolver.version} + test + + + org.apache.maven.resolver + maven-resolver-impl + ${maven-resolver.version} + test + + + org.apache.maven + maven-resolver-provider + ${maven.version} + test + + + org.apache.maven + maven-compat + ${maven.version} + test + + + org.codehaus.plexus + plexus-cipher + ${plexus-cypher.version} + + + org.apache.maven.shared + maven-invoker + ${maven-invoker.version} + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit-pioneer + junit-pioneer + ${junit-pioneer.version} + test + + + + + org.springframework.boot + spring-boot-testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.apache.commons + commons-text + 1.10.0 + test + + + org.powermock + powermock-core + 2.0.9 + test + + + + + + com.google.code.findbugs + jsr305 + 3.0.2 + provided + + + + + \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/src/test/java/org/springframework/sbm/PrivateArtifactRepositoryTest.java b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/src/test/java/org/springframework/sbm/PrivateArtifactRepositoryTest.java new file mode 100644 index 0000000..3f93369 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/src/test/java/org/springframework/sbm/PrivateArtifactRepositoryTest.java @@ -0,0 +1,424 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.sbm; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.shared.invoker.*; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.maven.cache.LocalMavenArtifactCache; +import org.openrewrite.maven.cache.MavenArtifactCache; +import org.openrewrite.maven.tree.MavenRepository; +import org.powermock.reflect.Whitebox; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.rewrite.boot.autoconfigure.SbmSupportRewriteConfiguration; +import org.springframework.rewrite.parsers.RewriteProjectParser; +import org.springframework.rewrite.parsers.RewriteProjectParsingResult; +import org.springframework.rewrite.parsers.maven.SbmTestConfiguration; +import org.springframework.util.FileSystemUtils; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; + +/** + * Test that dependencies from a private repository can be downloaded with provided + * credentials. + *

+ * A secured private repository is started as Docker container and a project depending on + * a dependency only available through the repository gets parsed. Verifying the types of + * the dependency were resolved proves that the secured access to the repository was + * successful and thus credentials provided were decrypted and used. All resources for + * this test live under {@code ./testcode/maven-projects/private-repository}. + *

    + *
  • {@code dependency-project} provides the code for the dependency + * {@code com.acme.dependency:dependency-project:1.0-SNAPSHOT}.
  • + *
  • {@code dependent-project} provides code that depends on + * {@code dependency-project}.
  • + *
  • {@code user.home} provides {@code .m2} and required Maven files + * {@code settings.xml} and {@code settings-security.xml}
  • + *
  • {@code reposilite-data} provides files used by reposilite to provide the required + * configuration and state (auth + dependency)
  • + *
+ *

+ * The test starts a private repository in a Docker container, removes any cached local + * jars and scans {@code dependent-project}. It then verifies that type information from + * {@code com.acme.dependency:dependency-project:1.0-SNAPSHOT} was retrieved from the + * private repository. + *

+ * The credentials are provided through {@code ~/.m2/settings.xml} and + * {@code ~/.m2/settings-security.xml}. To not interfere the local Maven installation and + * configuration, the user home directory is redirected to + * {@code ./testcode/maven-projects/private-repository/user.home}. Therefor the Maven + * {@code .m2} is pointing to + * {@code ./testcode/maven-projects/private-repository/user.home/.m2} and configuration + * files are taken from there. + * + * @author Fabian Krüger + */ +@SpringBootTest(classes = { MavenArtifactCacheTestConfig.class, SbmSupportRewriteConfiguration.class, + SbmTestConfiguration.class }) +@Testcontainers +public class PrivateArtifactRepositoryTest { + + // All test resources live here + public static final String TESTCODE_DIR = "testcode/maven-projects/private-repository"; + + // The private Artifact repository (reposilite) provides the dependency. + @Container + static GenericContainer reposilite = new GenericContainer(DockerImageName.parse("dzikoysk/reposilite:3.4.10")) + .withExposedPorts(8080) + // copy required config files and cached dependency to repository + .withCopyFileToContainer(MountableFile.forHostPath("./" + TESTCODE_DIR + "/reposilite-data"), "/app/data") + // Create temp user 'user' with password 'secret' + .withEnv("REPOSILITE_OPTS", "--token user:secret --shared-config shared.configuration.json"); + + public static final String DEPENDENCY_CLASS_FQNAME = "com.example.dependency.DependencyClass"; + + private static final String NEW_USER_HOME = Path.of(".") + .resolve(TESTCODE_DIR + "/user.home") + .toAbsolutePath() + .normalize() + .toString(); + + private static final Path DEPENDENCY_PATH_IN_LOCAL_MAVEN_REPO = Path + .of(NEW_USER_HOME + "/.m2/repository/com/example/dependency/dependency-project") + .toAbsolutePath() + .normalize(); + + private static final File LOCAL_MAVEN_REPOSITORY = Path.of(NEW_USER_HOME + "/.m2/repository").toFile(); + + private static MavenRepository originalMavenRepository; + + private static String originalUserHome; + + @Autowired + private RewriteProjectParser parser; + + @BeforeAll + static void beforeAll(@TempDir Path tempDir) { + originalUserHome = System.getProperty("user.home"); + System.setProperty("user.home", NEW_USER_HOME); + originalMavenRepository = MavenRepository.MAVEN_LOCAL_DEFAULT; + // overwrites MavenRepository.MAVEN_LOCAL_DEFAULT which is statically initialized + // and used previous value of + // 'user.home'. This constant is used elsewhere to retrieve the default local + // Maven repo URI. + // To reflect the new user.home this constant is overwritten using Powermock + // Whitebox class and then set back + // in after the test. + MavenRepository mavenRepository = new MavenRepository("local", + new File(System.getProperty("user.home") + "/.m2/repository").toURI().toString(), "true", "true", true, + null, null, false); + Whitebox.setInternalState(MavenRepository.class, "MAVEN_LOCAL_DEFAULT", mavenRepository); + } + + @AfterAll + static void afterAll() { + // set back to initial values + System.setProperty("user.home", originalUserHome); + Whitebox.setInternalState(MavenRepository.class, "MAVEN_LOCAL_DEFAULT", originalMavenRepository); + FileSystemUtils.deleteRecursively(LOCAL_MAVEN_REPOSITORY); + } + + @BeforeEach + void beforeEach() throws IOException { + Integer port = reposilite.getMappedPort(8080); + System.out.println("Reposilite: http://localhost:" + port + " login with user:secret"); + TestHelper.renderTemplates(port); + TestHelper.clearDependencyFromLocalMavenRepo(); + } + + @Test + @DisplayName("Maven settings should be read from secured private repo") + void mavenSettingsShouldBeReadFromSecuredPrivateRepo() { + verifyDependencyDoesNotExistInLocalMavenRepo(); + RewriteProjectParsingResult parsingResult = parseDependentProject(); + verifyDependencyExistsInLocalMavenRepo(); + verifyTypesFromDependencyWereResolved(parsingResult); + } + + private static void verifyTypesFromDependencyWereResolved(RewriteProjectParsingResult parsingResult) { + J.CompilationUnit cu = (J.CompilationUnit) parsingResult.sourceFiles() + .stream() + .filter(s -> s.getSourcePath().toFile().getName().endsWith(".java")) + .findFirst() + .get(); + List fqClassesInUse = cu.getTypesInUse() + .getTypesInUse() + .stream() + .filter(JavaType.FullyQualified.class::isInstance) + .map(JavaType.FullyQualified.class::cast) + .map(JavaType.FullyQualified::getFullyQualifiedName) + .toList(); + + // DependencyClass must be in list of used types + assertThat(fqClassesInUse).contains(DEPENDENCY_CLASS_FQNAME); + + // type should be on classpath + List classpathFqNames = cu.getMarkers() + .findFirst(JavaSourceSet.class) + .get() + .getClasspath() + .stream() + .map(fqn -> fqn.getFullyQualifiedName()) + .toList(); + assertThat(classpathFqNames).contains(DEPENDENCY_CLASS_FQNAME); + + // Type of member should be resolvable + J.ClassDeclaration classDeclaration = cu.getClasses().get(0); + JavaType.Class type = (JavaType.Class) ((J.VariableDeclarations) classDeclaration.getBody() + .getStatements() + .get(0)).getType(); + assertThat(type.getFullyQualifiedName()).isEqualTo(DEPENDENCY_CLASS_FQNAME); + } + + private static void verifyDependencyExistsInLocalMavenRepo() { + Path snapshotDir = DEPENDENCY_PATH_IN_LOCAL_MAVEN_REPO.resolve("1.0-SNAPSHOT").toAbsolutePath().normalize(); + assertThat(snapshotDir).isDirectory(); + assertThat(Arrays.stream(snapshotDir.toFile().listFiles()).map(f -> f.getName()).findFirst().get()) + .matches("dependency-project-1.0-.*\\.jar"); + } + + private RewriteProjectParsingResult parseDependentProject() { + Path migrateApplication = Path.of(TESTCODE_DIR + "/dependent-project"); + RewriteProjectParsingResult parsingResult = parser.parse(migrateApplication); + return parsingResult; + } + + private static void verifyDependencyDoesNotExistInLocalMavenRepo() { + Path dependencyArtifactDir = DEPENDENCY_PATH_IN_LOCAL_MAVEN_REPO.getParent(); + assertThat(LOCAL_MAVEN_REPOSITORY).isDirectory(); + assertThat(LOCAL_MAVEN_REPOSITORY.listFiles()).isEmpty(); + } + + class TestHelper { + + public static final String $USER_HOME_PLACEHOLDER = "${user.home}"; + + public static final String $PORT_PLACEHOLDER = "${port}"; + + private static void renderTemplates(Integer port) throws IOException { + // create pom.xml with correct port for dependency-project + Path dependencyPomTmplPath = Path.of(TESTCODE_DIR + "/dependency-project/pom.xml.template") + .toAbsolutePath() + .normalize(); + Path dependencyPomPath = renderPomXml(port, dependencyPomTmplPath); + + // create pom.xml with correct port for dependent-project + Path dependentPomTmplPath = Path.of(TESTCODE_DIR + "/dependent-project/pom.xml.template") + .toAbsolutePath() + .normalize(); + Path dependentPomPath = renderPomXml(port, dependentPomTmplPath); + + // adjust path in settings.xml + Path settingsXmlTmplPath = Path.of("./") + .resolve(NEW_USER_HOME + "/.m2/settings.xml.template") + .toAbsolutePath() + .normalize(); + renderSettingsXml(NEW_USER_HOME, settingsXmlTmplPath); + } + + private static Path renderSettingsXml(String testcodeDir, Path settingsXmlTmplPath) throws IOException { + String settingsXmlContent = Files.readString(settingsXmlTmplPath); + String replaced = settingsXmlContent.replace($USER_HOME_PLACEHOLDER, testcodeDir); + Path settingsXmlPath = Path.of(settingsXmlTmplPath.toString().replace(".template", "")); + return Files.writeString(settingsXmlPath, replaced); + } + + private static Path renderPomXml(Integer port, Path pomXmlTmplPath) throws IOException { + String given = Files.readString(pomXmlTmplPath); + String replaced = given.replace($PORT_PLACEHOLDER, port.toString()); + Path pomXmlPath = Path.of(pomXmlTmplPath.toString().replace(".template", "")); + return Files.writeString(pomXmlPath, replaced); + } + + static void clearDependencyFromLocalMavenRepo() { + try { + FileSystemUtils.deleteRecursively(DEPENDENCY_PATH_IN_LOCAL_MAVEN_REPO); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + } + + /* + * Currently not used as the dependency is provided to the container (cached). But + * kept in case deployment of the dependency or building the dependent project is + * needed. + */ + class DeploymentHelper { + + void deployDependency(Path pomXmlPath) throws MavenInvocationException { + InvocationRequest request = new DefaultInvocationRequest(); + request.setPomFile(pomXmlPath.toFile()); + request.setShowErrors(true); + request.setUserSettingsFile(Path.of(TESTCODE_DIR + "/user.home/.m2/settings-clear-password.xml").toFile()); + request.setGoals(List.of("deploy")); + request.setLocalRepositoryDirectory(LOCAL_MAVEN_REPOSITORY); + request.setBatchMode(true); + Invoker invoker = new DefaultInvoker(); + invoker.setMavenHome(Path.of(TESTCODE_DIR + "/user.home/apache-maven-3.9.5").toFile()); + InvocationResult result = invoker.execute(request); + if (result.getExitCode() != 0) { + if (result.getExecutionException() != null) { + fail("Maven deploy failed.", result.getExecutionException()); + } + else { + fail("Maven deploy failed. Exit code: " + result.getExitCode()); + } + } + } + + private void buildProject(Path dependentPomPath) throws MavenInvocationException { + InvocationRequest request = new DefaultInvocationRequest(); + request.setPomFile(dependentPomPath.toFile()); + request.setShowErrors(true); + request.setUserSettingsFile(Path.of(TESTCODE_DIR + "/user.home/.m2/settings.xml").toFile()); + request.setGoals(List.of("clean", "package")); + request.setLocalRepositoryDirectory(LOCAL_MAVEN_REPOSITORY); + request.setBatchMode(true); + request.setGlobalChecksumPolicy(InvocationRequest.CheckSumPolicy.Warn); + request.setOutputHandler(s -> System.out.println(s)); + Invoker invoker = new DefaultInvoker(); + invoker.setMavenHome(Path.of(TESTCODE_DIR + "/user.home/apache-maven-3.9.5").toFile()); + InvocationResult result = invoker.execute(request); + if (result.getExitCode() != 0) { + if (result.getExecutionException() != null) { + fail("Maven clean package failed.", result.getExecutionException()); + } + else { + fail("Maven clean package. Exit code: " + result.getExitCode()); + } + } + } + + static void installMavenForTestIfNotExists(Path tempDir) { + if (!Path.of("./testcode/maven-projects/private-repository/user.home/apache-maven-3.9.5/bin/mvn") + .toFile() + .exists()) { + String mavenDownloadUrl = "https://dlcdn.apache.org/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.zip"; + try { + Path mavenInstallDir = Path.of(TESTCODE_DIR + "/user.home"); + File downloadedMavenZipFile = tempDir.resolve("apache-maven-3.9.5-bin.zip").toFile(); + FileUtils.copyURLToFile(new URL(mavenDownloadUrl), downloadedMavenZipFile, 10000, 30000); + Unzipper.unzip(downloadedMavenZipFile, mavenInstallDir); + File file = mavenInstallDir.resolve("apache-maven-3.9.5/bin/mvn").toFile(); + file.setExecutable(true, false); + assertThat(file.canExecute()).isTrue(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + class Unzipper { + + private static void unzip(File downloadedMavenZipFile, Path mavenInstallDir) { + try { + byte[] buffer = new byte[1024]; + ZipInputStream zis = null; + + zis = new ZipInputStream(new FileInputStream(downloadedMavenZipFile)); + + ZipEntry zipEntry = zis.getNextEntry(); + while (zipEntry != null) { + File newFile = newFile(mavenInstallDir.toFile(), zipEntry); + if (zipEntry.isDirectory()) { + if (!newFile.isDirectory() && !newFile.mkdirs()) { + throw new IOException("Failed to create directory " + newFile); + } + } + else { + // fix for Windows-created archives + File parent = newFile.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) { + throw new IOException("Failed to create directory " + parent); + } + + // write file content + FileOutputStream fos = new FileOutputStream(newFile); + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + } + zipEntry = zis.getNextEntry(); + } + zis.closeEntry(); + zis.close(); + } + catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { + File destFile = new File(destinationDir, zipEntry.getName()); + + String destDirPath = destinationDir.getCanonicalPath(); + String destFilePath = destFile.getCanonicalPath(); + + if (!destFilePath.startsWith(destDirPath + java.io.File.separator)) { + throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); + } + + return destFile; + } + + } + + } + +} + +// Overwrite MavenArtifactCache to use the modified 'user.home'. +@Configuration +class MavenArtifactCacheTestConfig { + + @Bean + MavenArtifactCache mavenArtifactCache() { + return new LocalMavenArtifactCache(Paths.get(System.getProperty("user.home"), ".m2", "repository")); + } + +} \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/.gitignore b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/.gitignore new file mode 100644 index 0000000..7c573fe --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/.gitignore @@ -0,0 +1 @@ +cwa-server \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/.gitignore b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/.gitignore new file mode 100644 index 0000000..b8f740a --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/.gitignore @@ -0,0 +1,15 @@ +**/target +**/.rewrite-cache +/dependency-project/pom.xml +/dependent-project/pom.xml +/user.home/.config/ +/user.home/apache-maven-3.9.5/ +/user.home/.m2/repository +/user.home/.m2/settings.xml +!/user.home/.m2/settings.xml.template +!/user.home/.m2/settings-security.xml +/reposilite-data/static +/reposilite-data/.local +/reposilite-data/plugins +/reposilite-data/reposilite.db +/reposilite-data/configuration.cdn diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/README.adoc b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/README.adoc new file mode 100644 index 0000000..094c99e --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/README.adoc @@ -0,0 +1,36 @@ +# Artifact Repository Test + +Test that artifacts available in a private artifact repository configured in `~/.m2/settings.xml` can be accessed. +This is important as many enterprise projects use their private artifact repository to retrieve private dependencies. + +- A private artifact repository using (https://github.com/dzikoysk/reposilite[reposilite]) is started in a Docker container. +The reposilite instance has a user configured (admin:secret) which can deploy and access artifacts. + +- The repositories in the artifact repository (e.g. snapshot) require successful authentication (deploy + download). + +- `dependency-project` has a simple class `DependencyClass` and gets deployed to the artifact repository. + +- `dependent-project` depends on `dependency-project` and has a class `DependentClass` that uses `DependencyClass` + +- `dependent-project` gets parsed + +- The resulting AST has the type information of `dependency-project` resolved when the repository information and credentials were read from `settings.xml` and `security-settings.xml`. + +Technical requirements: + +- The port of the Docker container is dynamic and used in settings.xml and pom.xml. +- The local Maven installation of any system should not be affected by this test. +- The location of the Maven dir `.m2` must therefore point to a different location while the test is running. + + + + + + +This requires temporarily a different `.m2` location, here `testcode/maven-projects/private-repository/user.home/.m2`. +When deploying the `dependency-project` the path to `settings.xml` is provided, pointing to `testcode/maven-projects/private-repository/user.home/.m2/settings.xml`. +This file declares the location of the local Maven repository pointing to the same dir. +Because these paths can't be relative for this test and absolute paths + + +The `user.home` is set to point to `testcode/maven-projects/private-repository/user.home` which contains a `.m2` directory providing access configuration to the reposilite instance through `.m2/settings.xml` and `.m2/security-settings.xml`, diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependency-project/pom.xml.template b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependency-project/pom.xml.template new file mode 100644 index 0000000..920e5e6 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependency-project/pom.xml.template @@ -0,0 +1,25 @@ + + + 4.0.0 + + com.example.dependency + dependency-project + 1.0-SNAPSHOT + + dependency-project + + + UTF-8 + 17 + 17 + + + + + repository-snapshots + Snapshots Repository + http://localhost:${port}/snapshots + + + diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependency-project/src/main/java/com/example/dependency/DependencyClass.java b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependency-project/src/main/java/com/example/dependency/DependencyClass.java new file mode 100644 index 0000000..67b1f0d --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependency-project/src/main/java/com/example/dependency/DependencyClass.java @@ -0,0 +1,13 @@ +package com.example.dependency; +public class DependencyClass +{ + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependent-project/pom.xml.template b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependent-project/pom.xml.template new file mode 100644 index 0000000..797073c --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependent-project/pom.xml.template @@ -0,0 +1,33 @@ + + + 4.0.0 + + com.example.dependent + dependent-project + 1.0-SNAPSHOT + + dependent-project + + + UTF-8 + 17 + 17 + + + + + com.example.dependency + dependency-project + 1.0-SNAPSHOT + + + + + + repository-snapshots + Snapshots Repository + http://localhost:${port}/snapshots + + + diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependent-project/src/main/java/com/example/dependent/DependentClass.java b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependent-project/src/main/java/com/example/dependent/DependentClass.java new file mode 100644 index 0000000..2fead28 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/dependent-project/src/main/java/com/example/dependent/DependentClass.java @@ -0,0 +1,7 @@ +package com.example.dependent; + +import com.example.dependency.DependencyClass; + +public class DependentClass { + private DependencyClass dependencyClass; +} diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar new file mode 100644 index 0000000..7e3c168 Binary files /dev/null and b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar differ diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar.md5 b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar.md5 new file mode 100644 index 0000000..49cdda0 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar.md5 @@ -0,0 +1 @@ +5c1fa9eb9814ca94bec1d507acb02910 \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar.sha1 b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar.sha1 new file mode 100644 index 0000000..27e7477 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.jar.sha1 @@ -0,0 +1 @@ +453d6aa069f223e17d5505601a3a3a7f576d61f4 \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom new file mode 100644 index 0000000..78420e9 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom @@ -0,0 +1,25 @@ + + + 4.0.0 + + com.example.dependency + dependency-project + 1.0-SNAPSHOT + + dependency-project + + + UTF-8 + 17 + 17 + + + + + repository-snapshots + Snapshots Repository + http://localhost:52260/snapshots + + + diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom.md5 b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom.md5 new file mode 100644 index 0000000..940ac3b --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom.md5 @@ -0,0 +1 @@ +b5e883c0ee08ff935cf96727bb8623f4 \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom.sha1 b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom.sha1 new file mode 100644 index 0000000..50ec63f --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0-20231105.102337-1.pom.sha1 @@ -0,0 +1 @@ +534f31aa5ef763fe4562871d4dbb167a5cdc26f0 \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0.jar b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0.jar new file mode 100644 index 0000000..7e3c168 Binary files /dev/null and b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/dependency-project-1.0.jar differ diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml new file mode 100644 index 0000000..aacf348 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml @@ -0,0 +1,25 @@ + + + com.example.dependency + dependency-project + + 20231105102337 + + 20231105.102337 + 1 + + + + pom + 1.0-20231105.102337-1 + 20231105102337 + + + jar + 1.0-20231105.102337-1 + 20231105102337 + + + + 1.0-SNAPSHOT + diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml.md5 b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml.md5 new file mode 100644 index 0000000..03c2d4d --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml.md5 @@ -0,0 +1 @@ +40910b0eb66467f44ca8792bfe5a847e \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml.sha1 new file mode 100644 index 0000000..24af63b --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/1.0-SNAPSHOT/maven-metadata.xml.sha1 @@ -0,0 +1 @@ +6262ab25ed0f7e2128fb26fab1acd090c535abaf \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml new file mode 100644 index 0000000..10b9e95 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml @@ -0,0 +1,11 @@ + + + com.example.dependency + dependency-project + + + 1.0-SNAPSHOT + + 20231105102337 + + diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml.md5 b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml.md5 new file mode 100644 index 0000000..fa762d2 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml.md5 @@ -0,0 +1 @@ +9bf1c816b6c0f8166efd71605db83682 \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml.sha1 b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml.sha1 new file mode 100644 index 0000000..3aa41af --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/repositories/snapshots/com/example/dependency/dependency-project/maven-metadata.xml.sha1 @@ -0,0 +1 @@ +4fa1150761b6af92a0b232fe8ec688b9e90d0f88 \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/shared.configuration.json b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/shared.configuration.json new file mode 100644 index 0000000..90c3c48 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/reposilite-data/shared.configuration.json @@ -0,0 +1,75 @@ +{ + "statistics": { + "enabled": true, + "resolvedRequestsInterval": "MONTHLY" + }, + "web": { + "forwardedIp": "X-Forwarded-For" + }, + "frontend": { + "id": "reposilite-repository", + "title": "Reposilite Repository", + "description": "Public Maven repository hosted through the Reposilite", + "organizationWebsite": "https://reposilite.com", + "organizationLogo": "https://avatars.githubusercontent.com/u/88636591", + "icpLicense": "" + }, + "authentication": { + "ldap": { + "enabled": false, + "ssl": false, + "hostname": "ldap.domain.com", + "port": 389, + "baseDn": "dc=company,dc=com", + "searchUserDn": "cn=reposilite,ou=admins,dc=domain,dc=com", + "searchUserPassword": "reposilite-admin-secret", + "typeAttribute": "person", + "userAttribute": "cn", + "userFilter": "(&(objectClass=person)(ou=Maven Users))", + "userType": "PERSISTENT" + } + }, + "maven": { + "repositories": [ + { + "id": "releases", + "visibility": "PRIVATE", + "redeployment": false, + "preserveSnapshots": false, + "storageProvider": { + "type": "fs", + "quota": "100%", + "mount": "" + }, + "storagePolicy": "PRIORITIZE_UPSTREAM_METADATA", + "proxied": [] + }, + { + "id": "snapshots", + "visibility": "PRIVATE", + "redeployment": true, + "preserveSnapshots": false, + "storageProvider": { + "type": "fs", + "quota": "100%", + "mount": "./snapshots" + }, + "storagePolicy": "PRIORITIZE_UPSTREAM_METADATA", + "proxied": [] + }, + { + "id": "private", + "visibility": "PRIVATE", + "redeployment": false, + "preserveSnapshots": false, + "storageProvider": { + "type": "fs", + "quota": "100%", + "mount": "" + }, + "storagePolicy": "PRIORITIZE_UPSTREAM_METADATA", + "proxied": [] + } + ] + } +} \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings-clear-password.xml b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings-clear-password.xml new file mode 100644 index 0000000..4d22090 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings-clear-password.xml @@ -0,0 +1,14 @@ + + + /Users/fkrueger/projects/spring-boot-migrator/sbm-support-rewrite/testcode/maven-projects/private-repository/user.home/.m2/repository + + + + repository-snapshots + user + + secret + + + diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings-security.xml b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings-security.xml new file mode 100644 index 0000000..69a6799 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings-security.xml @@ -0,0 +1,4 @@ + + + {BzCEWWQMgMkHk0P8+Rr+hsscSisZT6A4+G9Mub7f/m4=} + \ No newline at end of file diff --git a/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings.xml.template b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings.xml.template new file mode 100644 index 0000000..6994fc2 --- /dev/null +++ b/spring-rewrite-commons-functional-tests/private-artifact-repository-tests/testcode/maven-projects/private-repository/user.home/.m2/settings.xml.template @@ -0,0 +1,15 @@ + + + ${user.home}/.m2/repository + + + + repository-snapshots + user + + {iKYxpFKiVu0HTAe4w0RAzev3TAav0DG8wEom2qNoRws=} + + + + diff --git a/spring-rewrite-commons-launcher/.gitignore b/spring-rewrite-commons-launcher/.gitignore new file mode 100644 index 0000000..3fffeaa --- /dev/null +++ b/spring-rewrite-commons-launcher/.gitignore @@ -0,0 +1,2 @@ +/testcode/**/target/** +/testcode/**/.rewrite-cache/** \ No newline at end of file diff --git a/spring-rewrite-commons-launcher/README.adoc b/spring-rewrite-commons-launcher/README.adoc new file mode 100644 index 0000000..4892638 --- /dev/null +++ b/spring-rewrite-commons-launcher/README.adoc @@ -0,0 +1,152 @@ +# sbm-support-rewrite + +The `sbm-support-rewrite` project provides Spring beans classes to parse a given project to an OpenRewrite abstract syntax tree (AST) which can then be used to run OpenRewrite recipes that were discovered on the classpath. + +## Components + + +## Getting started + + +## Configuration + +Some behaviour can be configured through application properties or by providing custom Spring beans. + +### Maven Artifact Cache +OpenRewrite uses a `MavenArtifactCache` to store downloaded jar dependencies. +The provided `MavenArtifactCache` bean tries to retrieve jars from local Maven cache `~/.m2/repository` first. +If the dependency doesn't exist it is searched under `~/.rewrite/cache/artifacts` and if it doesn't exist it is downloaded to this dir. + +[source,java] +..... +@Bean +MavenArtifactCache mavenArtifactCache() { + Path userHome = Path.of(System.getProperty("user.home")); + Path localMavenRepo = userHome.resolve(".m2/repository"); + Path localRewriteRepo = userHome.resolve(".rewrite/cache/artifacts"); + return new LocalMavenArtifactCache(localMavenRepo) + .orElse(localRewriteRepo)); +} +..... + +#### Custom Maven Artifact Cache + +The provided cache configuration can be replaced with a custom bean. + +[source,java] +..... +@Bean +MavenArtifactCache mavenArtifactCache() { + return new CustomMavenArtifactCache(); +} + +..... + + + + +### Maven Pom Cache +OpenRewrite downloads Maven Pom files to resolve dependencies. +The pom files get cached and the cache depends on the system. + +- 32-Bit systems use the `InMemoryPomCache`. +- 64-Bit systems use the `RocksdbMavenPomCache`. + + +#### Pom Cache Properties + +|=== +|Name |Description |Default Value + +|`parser.isPomCacheEnabled` +|If the flag is set to false, only the default, in-memory cache is used. +|`false` + +|`parser.pomCacheDirectory` +|Defines the cache dir for RocksdbMavenPomCache when `parser.isPomCacheEnabled` is `true` +|`~/.rewrite-cache` +|=== + +#### Custom Pom Cache +A custom `MavenPomCache` implementation can be provided through a custom Spring bean declaration. + +[source,java] +..... +@Bean +public MavenPomCache mavenPomCache() { + return new CustomMavenPomCache(); +} +..... + + +## Example + +Example code showing how to apply OpenRewrite's UpgradeSpringBoot_3_1 recipe + +[source,java] +..... +package com.example; + +import org.openrewrite.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.rewrite.parsers.ProjectScanner; +import org.springframework.rewrite.parsers.RewriteProjectParser; +import org.springframework.rewrite.parsers.RewriteProjectParsingResult; +import org.springframework.rewrite.project.resource.ProjectResourceSet; +import org.springframework.rewrite.project.resource.ProjectResourceSetFactory; +import org.springframework.rewrite.project.resource.ProjectResourceSetSerializer; +import org.springframework.rewrite.recipes.RewriteRecipeDiscovery; + +import java.nio.file.Path; +import java.util.List; + +@SpringBootApplication +public class BootUpgrade implements CommandLineRunner { + public static void main(String[] args) { + SpringApplication.run(BootUpgrade.class, args); + } + + @Autowired + ProjectScanner scanner; + @Autowired + RewriteProjectParser parser; + @Autowired + RewriteRecipeDiscovery discovery; + @Autowired + ProjectResourceSetSerializer serializer; + @Autowired + ProjectResourceSetFactory factory; + + @Override + public void run(String... args) throws Exception { + + String path = "demo-spring-song-app"; + Path baseDir = Path.of(path ).toAbsolutePath().normalize(); + System.out.println(baseDir); + if(!baseDir.toFile().exists() || !baseDir.toFile().isDirectory()) { + throw new IllegalArgumentException("Given path '%s' does not exist or is not a directory.".formatted(path)); + } + + // parse + RewriteProjectParsingResult parsingResult = parser.parse(baseDir); + List sourceFiles = parsingResult.sourceFiles(); + + // create ProjectResourceSet + ProjectResourceSet projectResourceSet = factory.create(baseDir, sourceFiles); + + // find recipe + String recipeName = "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1"; + List recipes = discovery.discoverRecipes(); + Recipe recipe = findRecipe(recipes, recipeName); + + // apply recipe + projectResourceSet.apply(recipe); + + // write changes to fs + serializer.writeChanges(projectResourceSet); + } +} +..... \ No newline at end of file diff --git a/spring-rewrite-commons-launcher/pom.xml b/spring-rewrite-commons-launcher/pom.xml new file mode 100644 index 0000000..b671ba3 --- /dev/null +++ b/spring-rewrite-commons-launcher/pom.xml @@ -0,0 +1,197 @@ + + + 4.0.0 + + + org.springframework.rewrite + spring-rewrite-commons + 0.1.0-SNAPSHOT + ../pom.xml + + + spring-rewrite-commons-launcher + + Launcher + Run OpenRewrite recipes without build tool plugin + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.openrewrite + rewrite-maven + ${rewrite.version} + + + org.openrewrite + rewrite-java + ${rewrite.version} + + + org.openrewrite + rewrite-java-17 + ${rewrite.version} + + + org.openrewrite + rewrite-json + ${rewrite.version} + + + org.openrewrite + rewrite-yaml + ${rewrite.version} + + + org.openrewrite + rewrite-hcl + ${rewrite.version} + + + org.openrewrite + rewrite-protobuf + ${rewrite.version} + + + org.openrewrite + rewrite-xml + ${rewrite.version} + + + org.openrewrite + rewrite-properties + ${rewrite.version} + + + javax.xml.bind + jaxb-api + ${jaxb-api.version} + + + + org.codehaus.plexus + plexus-sec-dispatcher + 2.0 + + + org.codehaus.plexus + plexus-cipher + ${plexus-cypher.version} + + + + org.apache.maven + maven-embedder + ${maven.version} + + + org.sonatype.plexus + plexus-cipher + + + + + + + org.openrewrite.maven + rewrite-maven-plugin + ${rewrite-maven-plugin.version} + test + + + org.apache.maven.wagon + wagon-http + ${maven-wagon-http.version} + test + + + org.apache.maven.resolver + maven-resolver-transport-wagon + ${maven-resolver.version} + test + + + org.apache.maven.resolver + maven-resolver-connector-basic + ${maven-resolver.version} + test + + + org.apache.maven.resolver + maven-resolver-impl + ${maven-resolver.version} + test + + + org.apache.maven + maven-resolver-provider + ${maven.version} + test + + + org.apache.maven + maven-compat + ${maven.version} + test + + + org.apache.maven.shared + maven-invoker + ${maven-invoker.version} + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit-pioneer + junit-pioneer + ${junit-pioneer.version} + test + + + + + com.google.code.findbugs + jsr305 + 3.0.2 + provided + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + test + package + + test-jar + + + test + **/parsers/maven/** + + + + + + + + \ No newline at end of file diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/ProjectResourceSetConfiguration.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/ProjectResourceSetConfiguration.java new file mode 100644 index 0000000..a976ee1 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/ProjectResourceSetConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.boot.autoconfigure; + +import org.openrewrite.ExecutionContext; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.rewrite.project.RewriteSourceFileWrapper; +import org.springframework.rewrite.project.resource.*; + +/** + * Configuration for {@link ProjectResourceSet} related beans. + * + * @author Fabian Krüger + */ +@AutoConfiguration +public class ProjectResourceSetConfiguration { + + @Bean + RewriteSourceFileWrapper rewriteSourceFileWrapper() { + return new RewriteSourceFileWrapper(); + } + + @Bean + RewriteMigrationResultMerger rewriteMigrationResultMerger(RewriteSourceFileWrapper rewriteSourceFileWrapper) { + return new RewriteMigrationResultMerger(rewriteSourceFileWrapper); + } + + @Bean + ProjectResourceSerializer projectResourceSerializer() { + return new ProjectResourceSerializer(); + } + + @Bean + ProjectResourceSetSerializer projectResourceSetSerializer(ProjectResourceSerializer resourceSerializer) { + return new ProjectResourceSetSerializer(resourceSerializer); + } + + @Bean + ProjectResourceSetFactory projectResourceSetFactory(RewriteMigrationResultMerger rewriteMigrationResultMerger, + RewriteSourceFileWrapper sourceFileWrapper, ExecutionContext executionContext) { + return new ProjectResourceSetFactory(rewriteMigrationResultMerger, sourceFileWrapper, executionContext); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/RecipeDiscoveryConfiguration.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/RecipeDiscoveryConfiguration.java new file mode 100644 index 0000000..9067dd8 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/RecipeDiscoveryConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.boot.autoconfigure; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.rewrite.parsers.SpringRewriteProperties; +import org.springframework.rewrite.parsers.RewriteParserConfiguration; +import org.springframework.rewrite.recipes.RewriteRecipeDiscovery; + +/** + * Configuration for {@link RewriteRecipeDiscovery}. + * + * @author Fabian Krüger + */ +@AutoConfiguration(after = RewriteParserConfiguration.class) +@Import(RewriteParserConfiguration.class) +@EnableConfigurationProperties(SpringRewriteProperties.class) +public class RecipeDiscoveryConfiguration { + + @Bean + RewriteRecipeDiscovery rewriteRecipeDiscovery(SpringRewriteProperties springRewriteProperties) { + return new RewriteRecipeDiscovery(springRewriteProperties); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/SbmSupportRewriteConfiguration.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/SbmSupportRewriteConfiguration.java new file mode 100644 index 0000000..80397f5 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/SbmSupportRewriteConfiguration.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.boot.autoconfigure; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Import; +import org.springframework.rewrite.parsers.RewriteParserConfiguration; + +/** + * Configuration for all components. + * + * @author Fabian Krüger + */ +@AutoConfiguration +@Import({ RecipeDiscoveryConfiguration.class, RewriteParserConfiguration.class, ProjectResourceSetConfiguration.class }) +public class SbmSupportRewriteConfiguration { + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/ScopeConfiguration.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/ScopeConfiguration.java new file mode 100644 index 0000000..fbfe995 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/boot/autoconfigure/ScopeConfiguration.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.boot.autoconfigure; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.maven.cache.MavenPomCache; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.rewrite.scopes.ExecutionScope; +import org.springframework.rewrite.scopes.ProjectMetadata; +import org.springframework.rewrite.scopes.ScanScope; + +import java.util.function.Supplier; + +/** + * @author Fabian Krüger + */ +@AutoConfiguration +public class ScopeConfiguration { + + @Bean + ExecutionScope executionScope() { + return new ExecutionScope(); + } + + @Bean + ScanScope scanScope() { + return new ScanScope(); + } + + /** + * Register {@link ScanScope} and {@link ExecutionScope}. + */ + @Bean + public static BeanFactoryPostProcessor beanFactoryPostProcessor(ExecutionScope executionScope, + ScanScope scanScope) { + return beanFactory -> { + beanFactory.registerScope(ScanScope.SCOPE_NAME, scanScope); + beanFactory.registerScope(ExecutionScope.SCOPE_NAME, executionScope); + }; + } + + @Bean + @org.springframework.rewrite.scopes.annotations.ScanScope + ProjectMetadata projectMetadata() { + return new ProjectMetadata(); + } + + @Bean + @ConditionalOnMissingBean(name = "executionContextSupplier") + Supplier executionContextSupplier() { + return () -> new InMemoryExecutionContext(t -> { + throw new RuntimeException(t); + }); + } + + @Bean + @org.springframework.rewrite.scopes.annotations.ScanScope + ExecutionContext executionContext(ProjectMetadata projectMetadata, + Supplier executionContextSupplier, MavenPomCache mavenPomCache) { + ExecutionContext executionContext = executionContextSupplier.get(); + return executionContext; + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/JavaParserBuilder.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/JavaParserBuilder.java new file mode 100644 index 0000000..0d5bb71 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/JavaParserBuilder.java @@ -0,0 +1,93 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.internal.JavaTypeCache; + +import java.nio.charset.Charset; +import java.util.Collection; + +public class JavaParserBuilder extends JavaParser.Builder { + + private JavaParser.Builder delegate = JavaParser.fromJavaVersion(); + + @Override + public JavaParser build() { + return delegate.build(); + } + + @Override + public JavaParser.Builder charset(Charset charset) { + return delegate.charset(charset); + } + + @Override + public JavaParser.Builder classpath(Collection classpath) { + return delegate.classpath(classpath); + } + + @Override + public JavaParser.Builder classpath(String... classpath) { + return delegate.classpath(classpath); + } + + @Override + public JavaParser.Builder classpathFromResources(ExecutionContext ctx, String... classpath) { + return delegate.classpathFromResources(ctx, classpath); + } + + public JavaParser.Builder getDelegate() { + return delegate; + } + + @Override + public JavaParser.Builder classpath(byte[]... classpath) { + return delegate.classpath(classpath); + } + + @Override + public JavaParser.Builder logCompilationWarningsAndErrors(boolean logCompilationWarningsAndErrors) { + return delegate.logCompilationWarningsAndErrors(logCompilationWarningsAndErrors); + } + + @Override + public JavaParser.Builder typeCache(JavaTypeCache javaTypeCache) { + return delegate.typeCache(javaTypeCache); + } + + @Override + public JavaParser.Builder dependsOn(Collection collection) { + return delegate.dependsOn(collection); + } + + @Override + public JavaParser.Builder dependsOn(String... inputsAsStrings) { + return delegate.dependsOn(inputsAsStrings); + } + + @Override + public JavaParser.Builder styles(Iterable iterable) { + return delegate.styles(iterable); + } + + @Override + public String getDslName() { + return delegate.getDslName(); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/MavenProject.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/MavenProject.java new file mode 100644 index 0000000..32ec7d6 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/MavenProject.java @@ -0,0 +1,272 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.jetbrains.annotations.NotNull; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.maven.utilities.MavenArtifactDownloader; +import org.openrewrite.xml.tree.Xml; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.parsers.maven.MavenRuntimeInformation; +import org.springframework.rewrite.utils.ResourceUtil; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.function.Predicate; + +/** + * @author Fabian Krüger + */ +public class MavenProject { + + private final Path projectRoot; + + private final Resource pomFile; + + // FIXME: 945 temporary method, model should nopt come from Maven + private final Model pomModel; + + private List collectedProjects = new ArrayList<>(); + + private Xml.Document sourceFile; + + private final MavenArtifactDownloader rewriteMavenArtifactDownloader; + + private final List resources; + + private ProjectId projectId; + + public MavenProject(Path projectRoot, Resource pomFile, Model pomModel, + MavenArtifactDownloader rewriteMavenArtifactDownloader, List resources) { + this.projectRoot = projectRoot; + this.pomFile = pomFile; + this.pomModel = pomModel; + this.rewriteMavenArtifactDownloader = rewriteMavenArtifactDownloader; + this.resources = resources; + projectId = new ProjectId(getGroupId(), getArtifactId()); + } + + public File getFile() { + return ResourceUtil.getPath(pomFile).toFile(); + } + + public Path getBasedir() { + // TODO: 945 Check if this is correct + return pomFile == null ? null : ResourceUtil.getPath(pomFile).getParent(); + } + + public void setCollectedProjects(List collected) { + this.collectedProjects = collected; + } + + public List getCollectedProjects() { + return collectedProjects; + } + + public Resource getResource() { + return pomFile; + } + + public Path getModuleDir() { + if (getBasedir() == null) { + return null; + } + else if (projectRoot.relativize(ResourceUtil.getPath(pomFile)).toString().equals("pom.xml")) { + return Path.of(""); + } + else { + return projectRoot.relativize(ResourceUtil.getPath(pomFile)).getParent(); + } + } + + public String getGroupIdAndArtifactId() { + return this.pomModel.getGroupId() + ":" + pomModel.getArtifactId(); + } + + public Path getPomFilePath() { + return ResourceUtil.getPath(pomFile); + } + + public Plugin getPlugin(String s) { + return pomModel.getBuild() == null ? null : pomModel.getBuild().getPluginsAsMap().get(s); + } + + public Properties getProperties() { + return pomModel.getProperties(); + } + + public MavenRuntimeInformation getMavenRuntimeInformation() { + // FIXME: 945 implement this + return new MavenRuntimeInformation(); + } + + public String getName() { + return pomModel.getName(); + } + + public String getGroupId() { + return pomModel.getGroupId() == null ? pomModel.getParent().getGroupId() : pomModel.getGroupId(); + } + + public String getArtifactId() { + return pomModel.getArtifactId(); + } + + /** + * FIXME: when the version of parent pom is null (inherited by it's parent) the + * version will be null. + */ + public String getVersion() { + return pomModel.getVersion() == null ? pomModel.getParent().getVersion() : pomModel.getVersion(); + } + + @Override + public String toString() { + String groupId = pomModel.getGroupId() == null ? pomModel.getParent().getGroupId() : pomModel.getGroupId(); + return groupId + ":" + pomModel.getArtifactId(); + } + + public String getBuildDirectory() { + String s = pomModel.getBuild() != null ? pomModel.getBuild().getDirectory() : null; + return s == null + ? ResourceUtil.getPath(pomFile).getParent().resolve("target").toAbsolutePath().normalize().toString() + : s; + } + + public String getSourceDirectory() { + String s = pomModel.getBuild() != null ? pomModel.getBuild().getSourceDirectory() : null; + return s == null ? ResourceUtil.getPath(pomFile) + .getParent() + .resolve("src/main/java") + .toAbsolutePath() + .normalize() + .toString() : s; + } + + public List getCompileClasspathElements() { + Scope scope = Scope.Compile; + return getClasspathElements(scope); + } + + public List getTestClasspathElements() { + return getClasspathElements(Scope.Test); + } + + @NotNull + private List getClasspathElements(Scope scope) { + MavenResolutionResult pom = getSourceFile().getMarkers().findFirst(MavenResolutionResult.class).get(); + List resolvedDependencies = pom.getDependencies().get(scope); + if (resolvedDependencies != null) { + return resolvedDependencies + // FIXME: 945 - deal with dependencies to projects in reactor + // + .stream() + .filter(rd -> rd.getRepository() != null) + .map(rd -> rewriteMavenArtifactDownloader.downloadArtifact(rd)) + .filter(Objects::nonNull) + .distinct() + .toList(); + } + else { + return new ArrayList<>(); + } + } + + public String getTestSourceDirectory() { + String s = pomModel.getBuild() != null ? pomModel.getBuild().getSourceDirectory() : null; + return s == null ? ResourceUtil.getPath(pomFile) + .getParent() + .resolve("src/test/java") + .toAbsolutePath() + .normalize() + .toString() : s; + } + + public void setSourceFile(Xml.Document sourceFile) { + this.sourceFile = sourceFile; + } + + private static List listJavaSources(List resources, Path sourceDirectory) { + return resources.stream().filter(whenIn(sourceDirectory)).filter(whenFileNameEndsWithJava()).toList(); + } + + @NotNull + private static Predicate whenFileNameEndsWithJava() { + return p -> ResourceUtil.getPath(p).getFileName().toString().endsWith(".java"); + } + + @NotNull + private static Predicate whenIn(Path sourceDirectory) { + return r -> ResourceUtil.getPath(r).toString().startsWith(sourceDirectory.toString()); + } + + public List getJavaSourcesInTarget() { + return listJavaSources(getResources(), getBasedir().resolve(getBuildDirectory())); + } + + private List getResources() { + return this.resources; + } + + public List getMainJavaSources() { + return listJavaSources(resources, getProjectRoot().resolve(getModuleDir()).resolve("src/main/java")); + } + + public Path getModulePath() { + return projectRoot.resolve(getModuleDir()); + } + + public ProjectId getProjectId() { + return projectId; + } + + public Object getProjectEncoding() { + return getPomModel().getProperties().get("project.build.sourceEncoding"); + } + + public Path getProjectRoot() { + return projectRoot; + } + + public Resource getPomFile() { + return pomFile; + } + + public Model getPomModel() { + return pomModel; + } + + public Xml.Document getSourceFile() { + return sourceFile; + } + + public MavenArtifactDownloader getRewriteMavenArtifactDownloader() { + return rewriteMavenArtifactDownloader; + } + + public void setProjectId(ProjectId projectId) { + this.projectId = projectId; + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ModuleParser.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ModuleParser.java new file mode 100644 index 0000000..39e5d0b --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ModuleParser.java @@ -0,0 +1,275 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.jetbrains.annotations.NotNull; +import org.openrewrite.ExecutionContext; +import org.openrewrite.FileAttributes; +import org.openrewrite.Parser; +import org.openrewrite.SourceFile; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.internal.JavaTypeCache; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.marker.Generated; +import org.openrewrite.marker.Marker; +import org.openrewrite.marker.Markers; +import org.openrewrite.xml.tree.Xml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.utils.ResourceUtil; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.openrewrite.Tree.randomId; + +/** + * @author Fabian Krüger + */ +public class ModuleParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(ModuleParser.class); + + /** + * Add {@link Marker}s to {@link SourceFile}. + */ + public UnaryOperator addProvenance(Path baseDir, List provenance, + @Nullable Collection generatedSources) { + return s -> { + Markers markers = s.getMarkers(); + for (Marker marker : provenance) { + markers = markers.addIfAbsent(marker); + } + if (generatedSources != null && generatedSources.contains(baseDir.resolve(s.getSourcePath()))) { + markers = markers.addIfAbsent(new Generated(randomId())); + } + return s.withMarkers(markers); + }; + } + + /** + * Parse Java sources and resources under {@code src/main} of current module. + */ + public SourceSetParsingResult processMainSources(Path baseDir, List resources, + Xml.Document moduleBuildFile, JavaParser.Builder javaParserBuilder, + RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, + ExecutionContext executionContext, MavenProject currentProject) { + LOGGER.info("Processing main sources in module '%s'".formatted(currentProject.getProjectId())); + // FIXME: 945 + // Some annotation processors output generated sources to the /target directory. + // These are added for parsing but + // should be filtered out of the final SourceFile list. + + List mainJavaSources = new ArrayList<>(); + List javaSourcesInTarget = currentProject.getJavaSourcesInTarget(); // listJavaSources(resources, + // currentProject.getBasedir().resolve(currentProject.getBuildDirectory())); + List javaSourcesInMain = currentProject.getMainJavaSources(); // listJavaSources(resources, + // currentProject.getBasedir().resolve(currentProject.getSourceDirectory())); + mainJavaSources.addAll(javaSourcesInTarget); + mainJavaSources.addAll(javaSourcesInMain); + + LOGGER.info("[%s] Parsing source files".formatted(currentProject)); + + // FIXME 945 classpath + // - Resolve dependencies to non-reactor projects from Maven repository + // - Resolve dependencies to reactor projects by providing the sources + // javaParserBuilder.classpath(byte[]) + + // we're processing a module here. The classpath of the module consists of all + // declared dependencies and their transitive dependencies too. + // For dependencies to projects that belong to the current rector... + // They'd either need to be built with Maven before to guarantee that the jars are + // installed to local Maven repo. + // Or, the classpath must be created from the sources of the project. + + List dependencies = currentProject.getCompileClasspathElements(); + + javaParserBuilder.classpath(dependencies); + + JavaTypeCache typeCache = new JavaTypeCache(); + javaParserBuilder.typeCache(typeCache); + + Iterable inputs = mainJavaSources.stream().map(r -> { + FileAttributes fileAttributes = null; + Path path = ResourceUtil.getPath(r); + boolean isSynthetic = Files.exists(path); + Supplier inputStreamSupplier = () -> ResourceUtil.getInputStream(r); + Parser.Input input = new Parser.Input(path, fileAttributes, inputStreamSupplier, isSynthetic); + return input; + }).toList(); + + Set localClassesCp = new HashSet<>(); + JavaSourceSet javaSourceSet = sourceSet("main", dependencies, typeCache); + List cus = javaParserBuilder.build() + .parseInputs(inputs, baseDir, executionContext) + .peek(s -> { + ((J.CompilationUnit) s).getClasses() + .stream() + .map(J.ClassDeclaration::getType) + .forEach(localClassesCp::add); + + alreadyParsed.add(baseDir.resolve(s.getSourcePath())); + }) + .toList(); + + // TODO: This is a hack: + // Parsed java sources are not themselves on the classpath (here). + // The actual parsing happens when the stream is terminated (toList), + // therefore the toList() must be called before the parsed compilation units can + // be added to the classpath + List mainProjectProvenance = new ArrayList<>(provenanceMarkers); + javaSourceSet = appendToClasspath(localClassesCp, javaSourceSet); + mainProjectProvenance.add(javaSourceSet); + + List parsedJavaPaths = javaSourcesInTarget.stream().map(ResourceUtil::getPath).toList(); + Stream parsedJava = cus.stream() + .map(addProvenance(baseDir, mainProjectProvenance, parsedJavaPaths)); + LOGGER.debug( + "[%s] Scanned %d java source files in main scope.".formatted(currentProject, mainJavaSources.size())); + + // Filter out any generated source files from the returned list, as we do not want + // to apply the recipe to the + // generated files. + Path buildDirectory = Paths.get(currentProject.getBuildDirectory()); + List sourceFiles = parsedJava.filter(s -> !s.getSourcePath().startsWith(buildDirectory)) + .collect(Collectors.toCollection(ArrayList::new)); + + int sourcesParsedBefore = alreadyParsed.size(); + alreadyParsed.addAll(parsedJavaPaths); + List parsedResourceFiles = rp + .parse(currentProject.getModulePath().resolve("src/main/resources"), resources, alreadyParsed) + .map(addProvenance(baseDir, mainProjectProvenance, null)) + .toList(); + + LOGGER.debug("[%s] Scanned %d resource files in main scope.".formatted(currentProject, + (alreadyParsed.size() - sourcesParsedBefore))); + // Any resources parsed from "main/resources" should also have the main source set + // added to them. + sourceFiles.addAll(parsedResourceFiles); + return new SourceSetParsingResult(sourceFiles, javaSourceSet.getClasspath()); + } + + /** + * Add entries that don't exist in the classpath of {@code javaSourceSet} from + * {@code appendingClasspath}. + */ + @NotNull + private static JavaSourceSet appendToClasspath(Set appendingClasspath, + JavaSourceSet javaSourceSet) { + List curCp = javaSourceSet.getClasspath(); + appendingClasspath.forEach(f -> { + if (!curCp.contains(f)) { + curCp.add(f); + } + }); + javaSourceSet = javaSourceSet.withClasspath(new ArrayList<>(curCp)); + return javaSourceSet; + } + + @NotNull + private static JavaSourceSet sourceSet(String name, List dependencies, JavaTypeCache typeCache) { + return JavaSourceSet.build(name, dependencies, typeCache, false); + } + + /** + * Parse Java sources and resource files under {@code src/test}. + */ + public SourceSetParsingResult processTestSources(Path baseDir, Xml.Document moduleBuildFile, + JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, + List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext, + MavenProject currentProject, List resources, List classpath) { + LOGGER.info("Processing test sources in module '%s'".formatted(currentProject.getProjectId())); + + List testDependencies = currentProject.getTestClasspathElements(); + + javaParserBuilder.classpath(testDependencies); + JavaTypeCache typeCache = new JavaTypeCache(); + javaParserBuilder.typeCache(typeCache); + + List testJavaSources = listJavaSources(resources, + currentProject.getBasedir().resolve(currentProject.getTestSourceDirectory())); + alreadyParsed.addAll(testJavaSources.stream().map(ResourceUtil::getPath).toList()); + + Iterable inputs = testJavaSources.stream() + .map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r))) + .toList(); + + final List localClassesCp = new ArrayList<>(); + List cus = javaParserBuilder.build() + .parseInputs(inputs, baseDir, executionContext) + .peek(s -> { + ((J.CompilationUnit) s).getClasses() + .stream() + .map(J.ClassDeclaration::getType) + .forEach(localClassesCp::add); + alreadyParsed.add(baseDir.resolve(s.getSourcePath())); + }) + .toList(); + + List markers = new ArrayList<>(provenanceMarkers); + + JavaSourceSet javaSourceSet = sourceSet("test", testDependencies, typeCache); + Set curClasspath = Stream.concat(classpath.stream(), localClassesCp.stream()) + .collect(Collectors.toSet()); + javaSourceSet = appendToClasspath(curClasspath, javaSourceSet); + markers.add(javaSourceSet); + Stream parsedJava = cus.stream().map(addProvenance(baseDir, markers, null)); + + LOGGER.debug( + "[%s] Scanned %d java source files in test scope.".formatted(currentProject, testJavaSources.size())); + Stream sourceFiles = parsedJava; + + // Any resources parsed from "test/resources" should also have the test source set + // added to them. + int sourcesParsedBefore = alreadyParsed.size(); + Stream parsedResourceFiles = rp + .parse(currentProject.getBasedir().resolve("src/test/resources"), resources, alreadyParsed) + .map(addProvenance(baseDir, markers, null)); + LOGGER.debug("[%s] Scanned %d resource files in test scope.".formatted(currentProject, + (alreadyParsed.size() - sourcesParsedBefore))); + sourceFiles = Stream.concat(sourceFiles, parsedResourceFiles); + List result = sourceFiles.toList(); + return new SourceSetParsingResult(result, javaSourceSet.getClasspath()); + } + + // FIXME: 945 take Java sources from resources + private static List listJavaSources(List resources, Path sourceDirectory) { + return resources.stream().filter(whenIn(sourceDirectory)).filter(whenFileNameEndsWithJava()).toList(); + } + + @NotNull + private static Predicate whenFileNameEndsWithJava() { + return p -> ResourceUtil.getPath(p).getFileName().toString().endsWith(".java"); + } + + @NotNull + private static Predicate whenIn(Path sourceDirectory) { + return r -> ResourceUtil.getPath(r).toString().startsWith(sourceDirectory.toString()); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ParserContext.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ParserContext.java new file mode 100644 index 0000000..db92798 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ParserContext.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.xml.tree.Xml; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.utils.ResourceUtil; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Fabian Krüger + */ +public class ParserContext { + + private final Path baseDir; + + private final List resources; + + private final List sortedProjects; + + public ParserContext(Path baseDir, List resources, List sortedProjects) { + this.baseDir = baseDir; + this.resources = resources; + this.sortedProjects = sortedProjects; + } + + private Map pathDocumentMap; + + public List getResources() { + return resources; + } + + public List getSortedProjects() { + return sortedProjects; + } + + public List getActiveProfiles() { + // FIXME: Add support for Maven profiles + return List.of("default"); + } + + public Resource getMatchingBuildFileResource(MavenProject pom) { + return resources.stream() + .filter(r -> ResourceUtil.getPath(r).toString().equals(pom.getPomFilePath().toString())) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "Could not find a resource in the list of resources that matches the path of MavenProject '%s'" + .formatted(pom.getPomFile().toString()))); + } + + public List getBuildFileResources() { + return sortedProjects.stream().map(p -> p.getPomFile()).toList(); + } + + public Xml.Document getXmlDocument(Path path) { + return pathDocumentMap.get(path); + } + + public void setParsedBuildFiles(List xmlDocuments) { + this.pathDocumentMap = xmlDocuments.stream() + .peek(doc -> addSourceFileToModel(baseDir, getSortedProjects(), doc)) + .collect(Collectors.toMap(doc -> baseDir.resolve(doc.getSourcePath()), doc -> doc)); + } + + public List getSortedBuildFileDocuments() { + return getSortedProjects().stream().map(p -> pathDocumentMap.get(p.getFile().toPath())).toList(); + } + + private void addSourceFileToModel(Path baseDir, List sortedProjectsList, Xml.Document s) { + sortedProjectsList.stream() + .filter(p -> ResourceUtil.getPath(p.getPomFile()) + .toString() + .equals(baseDir.resolve(s.getSourcePath()).toString())) + .forEach(p -> p.setSourceFile(s)); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ProjectId.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ProjectId.java new file mode 100644 index 0000000..3f1cde1 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ProjectId.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import java.util.Objects; + +/** + * @author Fabian Krüger + */ +public record ProjectId(String groupId, String artifactId) { + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ProjectId projectId = (ProjectId) o; + return Objects.equals(groupId, projectId.groupId) && Objects.equals(artifactId, projectId.artifactId); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, artifactId); + } + + @Override + public String toString() { + return groupId + ":" + artifactId; + } +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ProjectScanner.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ProjectScanner.java new file mode 100644 index 0000000..e9d20bb --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/ProjectScanner.java @@ -0,0 +1,126 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.rewrite.utils.LinuxWindowsPathUnifier; +import org.springframework.rewrite.utils.ResourceUtil; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Fabian Krüger + */ +public class ProjectScanner { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProjectScanner.class); + + private final ResourceLoader resourceLoader; + + private final SpringRewriteProperties springRewriteProperties; + + public ProjectScanner(ResourceLoader resourceLoader, SpringRewriteProperties springRewriteProperties) { + this.resourceLoader = resourceLoader; + this.springRewriteProperties = springRewriteProperties; + } + + public List scan(Path baseDir) { + if (!baseDir.isAbsolute()) { + baseDir = baseDir.toAbsolutePath().normalize(); + } + if (!baseDir.toFile().exists()) { + throw new IllegalArgumentException("Provided path does not exist: " + baseDir); + } + Path absoluteRootPath = baseDir.toAbsolutePath(); + String unifiedPath = new LinuxWindowsPathUnifier().unifyPath(absoluteRootPath.toString() + "/**"); + String pattern = "file:" + unifiedPath; + try { + Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader) + .getResources(pattern); + + LOGGER.debug("Scanned %d resources in dir: '%s'".formatted(resources.length, absoluteRootPath.toString())); + + List resultingResources = filterIgnoredResources(absoluteRootPath, resources); + + int numResulting = resultingResources.size(); + int numIgnored = resources.length - numResulting; + LOGGER.debug("Scan returns %s resources, %d resources were ignored.".formatted(numResulting, numIgnored)); + LOGGER.trace("Resources resulting from scan: %s".formatted(resultingResources.stream() + .map(r -> absoluteRootPath.relativize(ResourceUtil.getPath(r)).toString()) + .collect(Collectors.joining(", ")))); + + return resultingResources; + } + catch (IOException e) { + throw new RuntimeException("Can't get resources for pattern '" + pattern + "'", e); + } + } + + @NotNull + private List filterIgnoredResources(Path baseDir, Resource[] resources) { + Set effectivePathMatcherPatterns = new HashSet<>(); + List pathMatchers = springRewriteProperties.getIgnoredPathPatterns() + .stream() + .map(p -> p.startsWith("glob:") ? p : "glob:" + p) + .peek(p -> effectivePathMatcherPatterns.add(p)) + .map(baseDir.getFileSystem()::getPathMatcher) + .toList(); + + LOGGER.trace("Ignore resources matching any of these PathMatchers: %s" + .formatted(effectivePathMatcherPatterns.stream().collect(Collectors.joining(", ")))); + + List resultingResources = Stream.of(resources) + .filter(r -> isAccepted(baseDir, r, pathMatchers)) + .toList(); + + if (resultingResources.isEmpty()) { + throw new IllegalArgumentException("No resources were scanned. Check directory and ignore patterns."); + } + + return resultingResources; + } + + private boolean isAccepted(Path baseDir, Resource r, List pathMatchers) { + if (ResourceUtil.getPath(r).toFile().isDirectory()) { + return false; + } + Optional isIgnored = pathMatchers.stream().filter(matcher -> { + Path resourcePath = ResourceUtil.getPath(r); + boolean matches = matcher.matches(resourcePath); + return matches; + }).findFirst(); + if (isIgnored.isPresent() && LOGGER.isInfoEnabled()) { + Set ignoredPathPatterns = springRewriteProperties.getIgnoredPathPatterns(); + LOGGER.info("Ignoring scanned resource '%s' given these path matchers: %s." + .formatted(baseDir.relativize(ResourceUtil.getPath(r)), ignoredPathPatterns)); + } + return isIgnored.isEmpty(); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RecipeValidationErrorException.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RecipeValidationErrorException.java new file mode 100644 index 0000000..3b0002c --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RecipeValidationErrorException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +/** + * Thrown when recipe validation failed during discovery. + * + * @author Fabian Krüger + */ +public class RecipeValidationErrorException extends RuntimeException { + + public RecipeValidationErrorException(String s) { + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteExecutionContext.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteExecutionContext.java new file mode 100644 index 0000000..09fe43b --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteExecutionContext.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.internal.lang.Nullable; + +import java.util.Collection; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class RewriteExecutionContext implements ExecutionContext { + + private ExecutionContext delegate; + + public RewriteExecutionContext(Consumer onError) { + this(new InMemoryExecutionContext(onError)); + } + + public RewriteExecutionContext() { + this(new InMemoryExecutionContext(new RewriteExecutionContextErrorHandler( + new RewriteExecutionContextErrorHandler.ThrowExceptionSwitch()))); + } + + public RewriteExecutionContext(ExecutionContext delegate) { + this.delegate = delegate; + } + + @Override + public void putMessage(String key, @Nullable Object value) { + delegate.putMessage(key, value); + } + + @Override + public @Nullable T getMessage(String key) { + return delegate.getMessage(key); + } + + @Override + public > C putMessageInCollection(String key, V value, Supplier newCollection) { + return delegate.putMessageInCollection(key, value, newCollection); + } + + @Override + public Set putMessageInSet(String key, T value) { + return delegate.putMessageInSet(key, value); + } + + @Override + public @Nullable T pollMessage(String key) { + return delegate.pollMessage(key); + } + + @Override + public T pollMessage(String key, T defaultValue) { + return delegate.pollMessage(key, defaultValue); + } + + @Override + public void putCurrentRecipe(Recipe recipe) { + delegate.putCurrentRecipe(recipe); + } + + @Override + public Consumer getOnError() { + return delegate.getOnError(); + } + + @Override + public BiConsumer getOnTimeout() { + return delegate.getOnTimeout(); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteExecutionContextErrorHandler.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteExecutionContextErrorHandler.java new file mode 100644 index 0000000..29c5c9e --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteExecutionContextErrorHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.java.JavaParsingException; +import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.internal.MavenParsingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.Consumer; + +public class RewriteExecutionContextErrorHandler implements Consumer { + + private static final Logger LOGGER = LoggerFactory.getLogger(RewriteExecutionContextErrorHandler.class); + + private final ThrowExceptionSwitch throwExceptionSwitch; + + RewriteExecutionContextErrorHandler(ThrowExceptionSwitch throwExceptionSwitch) { + this.throwExceptionSwitch = throwExceptionSwitch; + } + + @Override + public void accept(Throwable t) { + if (t instanceof MavenParsingException) { + LOGGER.warn(t.getMessage()); + } + else if (t instanceof MavenDownloadingException) { + LOGGER.warn(t.getMessage()); + } + else if (t instanceof JavaParsingException) { + if (t.getMessage().equals("Failed symbol entering or attribution")) { + throw new RuntimeException( + "This could be a broken jar. Activate logging on WARN level for 'org.openrewrite' might reveal more information.", + t); + } + } + else { + throw new RuntimeException(t.getMessage(), t); + } + } + + public static class ThrowExceptionSwitch { + + private boolean throwExceptions = true; + + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteParserConfiguration.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteParserConfiguration.java new file mode 100644 index 0000000..8cad8b0 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteParserConfiguration.java @@ -0,0 +1,153 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.maven.cache.CompositeMavenPomCache; +import org.openrewrite.maven.cache.InMemoryMavenPomCache; +import org.openrewrite.maven.cache.MavenPomCache; +import org.openrewrite.maven.cache.RocksdbMavenPomCache; +import org.openrewrite.maven.utilities.MavenArtifactDownloader; +import org.openrewrite.tree.ParsingEventListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.ResourceLoader; +import org.springframework.rewrite.boot.autoconfigure.ScopeConfiguration; +import org.springframework.rewrite.parsers.events.RewriteParsingEventListenerAdapter; +import org.springframework.rewrite.parsers.maven.*; +import org.springframework.rewrite.project.resource.SbmApplicationProperties; +import org.springframework.rewrite.scopes.annotations.ScanScope; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import java.util.function.Consumer; + +/** + * Module configuration. + * + * @author Fabian Krüger + */ +@AutoConfiguration(after = { ScopeConfiguration.class }) +@EnableConfigurationProperties({ SpringRewriteProperties.class, SbmApplicationProperties.class }) +@Import({ org.springframework.rewrite.scopes.ScanScope.class, ScopeConfiguration.class, + RewriteParserMavenConfiguration.class }) +public class RewriteParserConfiguration { + + private static final Logger LOGGER = LoggerFactory.getLogger(RewriteParserConfiguration.class); + + @Bean + ProjectScanner projectScanner(ResourceLoader resourceLoader, SpringRewriteProperties springRewriteProperties) { + return new ProjectScanner(resourceLoader, springRewriteProperties); + } + + @Bean + ProvenanceMarkerFactory provenanceMarkerFactory(MavenProvenanceMarkerFactory mavenPovenanceMarkerFactory) { + return new ProvenanceMarkerFactory(mavenPovenanceMarkerFactory); + } + + @Bean + @ScanScope + JavaParserBuilder javaParserBuilder() { + return new JavaParserBuilder(); + } + + @Bean + Consumer artifactDownloaderErrorConsumer() { + return (t) -> { + throw new RuntimeException(t); + }; + } + + @Bean + ModuleParser moduleParser() { + return new ModuleParser(); + } + + @Bean + MavenModuleParser mavenModuleParser(SpringRewriteProperties springRewriteProperties, ModuleParser moduleParser) { + return new MavenModuleParser(springRewriteProperties, moduleParser); + } + + @Bean + SourceFileParser sourceFileParser(MavenModuleParser mavenModuleParser) { + return new SourceFileParser(mavenModuleParser); + } + + @Bean + StyleDetector styleDetector() { + return new StyleDetector(); + } + + @Bean + @ConditionalOnMissingBean(ParsingEventListener.class) + ParsingEventListener parsingEventListener(ApplicationEventPublisher eventPublisher) { + return new RewriteParsingEventListenerAdapter(eventPublisher); + } + + @Bean + MavenProjectAnalyzer mavenProjectAnalyzer(MavenArtifactDownloader artifactDownloader) { + return new MavenProjectAnalyzer(artifactDownloader); + } + + @Bean + RewriteProjectParser rewriteProjectParser(ProvenanceMarkerFactory provenanceMarkerFactory, + BuildFileParser buildFileParser, SourceFileParser sourceFileParser, StyleDetector styleDetector, + SpringRewriteProperties springRewriteProperties, ParsingEventListener parsingEventListener, + ApplicationEventPublisher eventPublisher, org.springframework.rewrite.scopes.ScanScope scanScope, + ConfigurableListableBeanFactory beanFactory, ProjectScanner projectScanner, + ExecutionContext executionContext, MavenProjectAnalyzer mavenProjectAnalyzer) { + return new RewriteProjectParser(provenanceMarkerFactory, buildFileParser, sourceFileParser, styleDetector, + springRewriteProperties, parsingEventListener, eventPublisher, scanScope, beanFactory, projectScanner, + executionContext, mavenProjectAnalyzer); + } + + @Bean + @ConditionalOnMissingBean(MavenPomCache.class) + MavenPomCache mavenPomCache(SpringRewriteProperties springRewriteProperties) { + MavenPomCache mavenPomCache = new InMemoryMavenPomCache(); + if (springRewriteProperties.isPomCacheEnabled()) { + if (!"64".equals(System.getProperty("sun.arch.data.model", "64"))) { + LOGGER.warn( + "parser.isPomCacheEnabled was set to true but RocksdbMavenPomCache is not supported on 32-bit JVM. falling back to InMemoryMavenPomCache"); + } + else { + try { + mavenPomCache = new CompositeMavenPomCache(new InMemoryMavenPomCache(), + new RocksdbMavenPomCache(Path.of(springRewriteProperties.getPomCacheDirectory()))); + } + catch (Exception e) { + LOGGER.warn("Unable to initialize RocksdbMavenPomCache, falling back to InMemoryMavenPomCache"); + if (LOGGER.isDebugEnabled()) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + String exceptionAsString = sw.toString(); + LOGGER.debug(exceptionAsString); + } + } + } + } + return mavenPomCache; + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteProjectParser.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteProjectParser.java new file mode 100644 index 0000000..317b58b --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteProjectParser.java @@ -0,0 +1,182 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.jetbrains.annotations.NotNull; +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.marker.Marker; +import org.openrewrite.style.NamedStyles; +import org.openrewrite.tree.ParsingEventListener; +import org.openrewrite.tree.ParsingExecutionContextView; +import org.openrewrite.xml.tree.Xml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.parsers.events.StartedParsingProjectEvent; +import org.springframework.rewrite.parsers.events.SuccessfullyParsedProjectEvent; +import org.springframework.rewrite.parsers.maven.BuildFileParser; +import org.springframework.rewrite.parsers.maven.MavenProjectAnalyzer; +import org.springframework.rewrite.parsers.maven.ProvenanceMarkerFactory; +import org.springframework.rewrite.recipes.RewriteRecipeDiscovery; +import org.springframework.rewrite.scopes.ScanScope; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Project parser parsing resources under a given {@link Path} to OpenRewrite Lossless + * Semantic Tree (LST). The implementation aims to produce the exact same result as the + * build tool plugins provided by OpenRewrite. The LST is provided as + * {@code List<}{@link SourceFile}{@code >}. + * + *

+ * This dummy code shows how the AST can be used to run OpenRewrite recipes: + * + *

{@code
+ *  Path projectBaseDir = ...
+ *  RewriteProjectParsingResult parsingResult = parser.parse(projectBaseDir);
+ *  List ast = parsingResult.sourceFiles();
+ *  List recipes = discovery.discoverRecipes();
+ *  RecipeRun recipeRun = recipes.get(0).run(ast, ctx);
+ *  }
+ * 
+ * + * @author Fabian Krüger + * @see RewriteRecipeDiscovery + * @see LST + */ +public class RewriteProjectParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(RewriteProjectParser.class); + + private final ProvenanceMarkerFactory provenanceMarkerFactory; + + private final BuildFileParser buildFileParser; + + private final SourceFileParser sourceFileParser; + + private final StyleDetector styleDetector; + + private final SpringRewriteProperties springRewriteProperties; + + private final ParsingEventListener parsingEventListener; + + private final ApplicationEventPublisher eventPublisher; + + private final ScanScope scanScope; + + private final ConfigurableListableBeanFactory beanFactory; + + private final ProjectScanner scanner; + + private final ExecutionContext executionContext; + + private final MavenProjectAnalyzer mavenProjectAnalyzer; + + public RewriteProjectParser(ProvenanceMarkerFactory provenanceMarkerFactory, BuildFileParser buildFileParser, + SourceFileParser sourceFileParser, StyleDetector styleDetector, + SpringRewriteProperties springRewriteProperties, ParsingEventListener parsingEventListener, + ApplicationEventPublisher eventPublisher, ScanScope scanScope, ConfigurableListableBeanFactory beanFactory, + ProjectScanner scanner, ExecutionContext executionContext, MavenProjectAnalyzer mavenProjectAnalyzer) { + this.provenanceMarkerFactory = provenanceMarkerFactory; + this.buildFileParser = buildFileParser; + this.sourceFileParser = sourceFileParser; + this.styleDetector = styleDetector; + this.springRewriteProperties = springRewriteProperties; + this.parsingEventListener = parsingEventListener; + this.eventPublisher = eventPublisher; + this.scanScope = scanScope; + this.beanFactory = beanFactory; + this.scanner = scanner; + this.executionContext = executionContext; + this.mavenProjectAnalyzer = mavenProjectAnalyzer; + } + + /** + * Parse the given {@code baseDir} to OpenRewrite AST. + */ + public RewriteProjectParsingResult parse(Path baseDir) { + List resources = scanner.scan(baseDir); + return this.parse(baseDir, resources); + } + + /** + * Parse given {@link Resource}s in {@code baseDir} to OpenRewrite AST representation. + */ + public RewriteProjectParsingResult parse(Path givenBaseDir, List resources) { + scanScope.clear(beanFactory); + + final Path baseDir = normalizePath(givenBaseDir); + + eventPublisher.publishEvent(new StartedParsingProjectEvent(resources)); + + ParsingExecutionContextView.view(executionContext).setParsingListener(parsingEventListener); + + // TODO: "runPerSubmodule" + // TODO: See ConfigurableRewriteMojo#getPlainTextMasks() + // TODO: where to retrieve styles from? --> see + // AbstractRewriteMojo#getActiveStyles() & AbstractRewriteMojo#loadStyles() + List styles = List.of(); + + // Get the ordered otherSourceFiles of projects + ParserContext parserContext = mavenProjectAnalyzer.createParserContext(baseDir, resources); + + // generate provenance + Map> provenanceMarkers = provenanceMarkerFactory.generateProvenanceMarkers(baseDir, + parserContext); + + // 127: parse build files + // TODO: 945 this map is only used to lookup module pom by path in + // SourceFileParser. If possible provide the build file from ParserContext and + // remove this map. + List parsedBuildFiles = buildFileParser.parseBuildFiles(baseDir, + parserContext.getBuildFileResources(), parserContext.getActiveProfiles(), executionContext, + springRewriteProperties.isSkipMavenParsing(), provenanceMarkers); + parserContext.setParsedBuildFiles(parsedBuildFiles); + + LOGGER.trace("Start to parse %d source files in %d modules" + .formatted(resources.size() + parsedBuildFiles.size(), parsedBuildFiles.size())); + List otherSourceFiles = sourceFileParser.parseOtherSourceFiles(baseDir, parserContext, resources, + provenanceMarkers, styles, executionContext); + + List sortedBuildFileDocuments = parserContext.getSortedBuildFileDocuments(); + + List resultingList = new ArrayList<>(); + resultingList.addAll(sortedBuildFileDocuments); + resultingList.addAll(otherSourceFiles); + List sourceFiles = styleDetector.sourcesWithAutoDetectedStyles(resultingList.stream()); + + eventPublisher.publishEvent(new SuccessfullyParsedProjectEvent(sourceFiles)); + + return new RewriteProjectParsingResult(sourceFiles, executionContext); + } + + @NotNull + private static Path normalizePath(Path givenBaseDir) { + if (!givenBaseDir.isAbsolute()) { + givenBaseDir = givenBaseDir.toAbsolutePath().normalize(); + } + final Path baseDir = givenBaseDir; + return baseDir; + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteProjectParsingResult.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteProjectParsingResult.java new file mode 100644 index 0000000..7862b90 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteProjectParsingResult.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; + +import java.util.List; + +/** + * @author Fabian Krüger + */ +public record RewriteProjectParsingResult(List sourceFiles, ExecutionContext executionContext) { +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteResourceParser.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteResourceParser.java new file mode 100644 index 0000000..b832202 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/RewriteResourceParser.java @@ -0,0 +1,334 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.jetbrains.annotations.NotNull; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.SourceFile; +import org.openrewrite.hcl.HclParser; +import org.openrewrite.java.JavaParser; +import org.openrewrite.json.JsonParser; +import org.openrewrite.properties.PropertiesParser; +import org.openrewrite.protobuf.ProtoParser; +import org.openrewrite.quark.QuarkParser; +import org.openrewrite.text.PlainTextParser; +import org.openrewrite.xml.XmlParser; +import org.openrewrite.yaml.YamlParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.utils.ResourceUtil; + +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Code from + * https://github.com/fabapp2/rewrite-maven-plugin/blob/83d184ea9ffe3046429f16c91aa56a9610bae832/src/main/java/org/openrewrite/maven/ResourceParser.java + * The motivation was to decouple the parser from file access. + */ +public class RewriteResourceParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(RewriteResourceParser.class); + + private static final Set DEFAULT_IGNORED_DIRECTORIES = new HashSet<>(Arrays.asList("build", "target", "out", + ".sonar", ".gradle", ".idea", ".project", "node_modules", ".git", ".metadata", ".DS_Store")); + + private final Path baseDir; + + private final Collection exclusions; + + private final int sizeThresholdMb; + + private final Collection excludedDirectories; + + private final Collection plainTextMasks; + + /** + * Sometimes java files will exist in the src/main/resources directory. For example, + * Drools: + */ + private final JavaParser.Builder javaParserBuilder; + + private final ExecutionContext executionContext; + + public RewriteResourceParser(Path baseDir, Collection exclusions, Collection plainTextMasks, + int sizeThresholdMb, Collection excludedDirectories, + JavaParser.Builder javaParserBuilder, ExecutionContext executionContext) { + this.baseDir = baseDir; + this.javaParserBuilder = javaParserBuilder; + this.executionContext = executionContext; + this.exclusions = pathMatchers(baseDir, exclusions); + this.sizeThresholdMb = sizeThresholdMb; + this.excludedDirectories = excludedDirectories; + this.plainTextMasks = pathMatchers(baseDir, plainTextMasks); + } + + private Collection pathMatchers(Path basePath, Collection pathExpressions) { + return pathExpressions.stream() + .map(o -> basePath.getFileSystem().getPathMatcher("glob:" + o)) + .collect(Collectors.toList()); + } + + public Stream parse(Path searchDir, List resources, Set alreadyParsed) { + // TODO: 945 remove/clean this up + List resourcesLeft = resources.stream() + .filter(r -> alreadyParsed.stream() + .noneMatch(path -> ResourceUtil.getPath(r).toString().startsWith(path.toString()))) + .toList(); + return this.parseSourceFiles(searchDir, resourcesLeft, alreadyParsed, executionContext); + + // + // Stream sourceFiles = Stream.empty(); + // if (!searchDir.toFile().exists()) { + // return sourceFiles; + // } else { + // Consumer errorConsumer = (t) -> { + // this.logger.debug("Error parsing", t); + // }; + // InMemoryExecutionContext ctx = new InMemoryExecutionContext(errorConsumer); + // + // try { + // sourceFiles = Stream.concat(sourceFiles, this.parseSourceFiles(searchDir, + // alreadyParsed, ctx)); + // return sourceFiles; + // } catch (IOException var7) { + // this.logger.error(var7.getMessage(), var7); + // throw new UncheckedIOException(var7); + // } + // } + } + + @SuppressWarnings({ "DuplicatedCode", "unchecked" }) + public Stream parseSourceFiles(Path searchDir, List resources, + Set alreadyParsed, ExecutionContext ctx) { + + List resourcesLeft = new ArrayList<>(); + List quarkPaths = new ArrayList<>(); + List plainTextPaths = new ArrayList<>(); + + List filteredResources = resources.stream() + .filter(r -> ResourceUtil.getPath(r).toString().startsWith(searchDir.toString())) + .toList(); + + filteredResources.forEach(resource -> { + Path file = ResourceUtil.getPath(resource); + Path dir = file.getParent(); + if (isExcluded(dir) || isIgnoredDirectory(searchDir, dir) || excludedDirectories.contains(dir) + || alreadyParsed.contains(new FileSystemResource(dir)) || alreadyParsed.contains(resource)) { + return; + } + else { + // FIXME: 945 only check threshold if value > 0 is given + long fileSize = ResourceUtil.contentLength(resource); + if (isOverSizeThreshold(fileSize)) { + LOGGER.info("Parsing as quark " + file + " as its size " + fileSize / (1024L * 1024L) + + "Mb exceeds size threshold " + sizeThresholdMb + "Mb"); + quarkPaths.add(file); + } + else if (isParsedAsPlainText(file)) { + plainTextPaths.add(file); + } + else { + resourcesLeft.add(file); + } + } + }); + + Stream sourceFiles = Stream.empty(); + + JavaParser javaParser = javaParserBuilder.build(); + List javaPaths = new ArrayList<>(); + + JsonParser jsonParser = new JsonParser(); + List jsonPaths = new ArrayList<>(); + + XmlParser xmlParser = new XmlParser(); + List xmlPaths = new ArrayList<>(); + + YamlParser yamlParser = new YamlParser(); + List yamlPaths = new ArrayList<>(); + + PropertiesParser propertiesParser = new PropertiesParser(); + List propertiesPaths = new ArrayList<>(); + + ProtoParser protoParser = new ProtoParser(); + List protoPaths = new ArrayList<>(); + + // Python currently not supported + // PythonParser pythonParser = PythonParser.builder().build(); + // List pythonPaths = new ArrayList<>(); + + HclParser hclParser = HclParser.builder().build(); + List hclPaths = new ArrayList<>(); + + PlainTextParser plainTextParser = new PlainTextParser(); + + QuarkParser quarkParser = new QuarkParser(); + + filteredResources.forEach(resource -> { + // See + // https://github.com/quarkusio/quarkus/blob/main/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/resteasy-reactive-codestart/java/src/main/java/org/acme/%7Bresource.class-name%7D.tpl.qute.java + // for an example of why we don't want qute files be parsed as java + Path path = ResourceUtil.getPath(resource); + // if (javaParser.accept(path) && !path.toString().endsWith(".qute.java")) { + // javaPaths.add(path); + // } + if (jsonParser.accept(path)) { + jsonPaths.add(path); + } + else if (xmlParser.accept(path)) { + xmlPaths.add(path); + } + else if (yamlParser.accept(path)) { + yamlPaths.add(path); + } + else if (propertiesParser.accept(path)) { + propertiesPaths.add(path); + } + else if (protoParser.accept(path)) { + protoPaths.add(path); + } + /* + * else if(pythonParser.accept(path)) { pythonPaths.add(path); } + */ else if (hclParser.accept(path)) { + hclPaths.add(path); + } + else if (quarkParser.accept(path)) { + quarkPaths.add(path); + } + }); + + Map pathToResource = filteredResources.stream() + .collect(Collectors.toMap(r -> ResourceUtil.getPath(r), r -> r)); + + if (!javaPaths.isEmpty()) { + List inputs = getInputs(pathToResource, javaPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) javaParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(javaPaths); + } + + if (!jsonPaths.isEmpty()) { + List inputs = getInputs(pathToResource, jsonPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) jsonParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(jsonPaths); + } + + if (!xmlPaths.isEmpty()) { + List inputs = getInputs(pathToResource, xmlPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) xmlParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(xmlPaths); + } + + if (!yamlPaths.isEmpty()) { + List inputs = getInputs(pathToResource, yamlPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) yamlParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(yamlPaths); + } + + if (!propertiesPaths.isEmpty()) { + List inputs = getInputs(pathToResource, propertiesPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) propertiesParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(propertiesPaths); + } + + if (!protoPaths.isEmpty()) { + List inputs = getInputs(pathToResource, protoPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) protoParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(protoPaths); + } + + // if (!pythonPaths.isEmpty()) { + // List inputs = getInputs(pathToResource, pythonPaths); + // sourceFiles = Stream.concat(sourceFiles, (Stream) + // pythonParser.parseInputs(inputs, baseDir, ctx)); + // alreadyParsed.addAll(pythonPaths); + // } + + if (!hclPaths.isEmpty()) { + List inputs = getInputs(pathToResource, hclPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) hclParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(hclPaths); + } + + if (!plainTextPaths.isEmpty()) { + List inputs = getInputs(pathToResource, plainTextPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) plainTextParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(plainTextPaths); + } + + if (!quarkPaths.isEmpty()) { + List inputs = getInputs(pathToResource, quarkPaths); + sourceFiles = Stream.concat(sourceFiles, (Stream) quarkParser.parseInputs(inputs, baseDir, ctx)); + alreadyParsed.addAll(quarkPaths); + } + + return sourceFiles; + } + + @NotNull + private static List getInputs(Map pathResourceMap, List paths) { + return paths.stream() + .map(path -> new Parser.Input(path, () -> ResourceUtil.getInputStream(pathResourceMap.get(path)))) + .toList(); + } + + private boolean isOverSizeThreshold(long fileSize) { + return sizeThresholdMb > 0 && fileSize > sizeThresholdMb * 1024L * 1024L; + } + + private boolean isExcluded(Path path) { + if (!exclusions.isEmpty()) { + for (PathMatcher excluded : exclusions) { + if (excluded.matches(baseDir.relativize(path))) { + return true; + } + } + } + return false; + } + + private boolean isParsedAsPlainText(Path path) { + if (!plainTextMasks.isEmpty()) { + Path computed = baseDir.relativize(path); + if (!computed.startsWith("/")) { + computed = Paths.get("/").resolve(computed); + } + for (PathMatcher matcher : plainTextMasks) { + if (matcher.matches(computed)) { + return true; + } + } + } + return false; + } + + private boolean isIgnoredDirectory(Path searchDir, Path path) { + for (Path pathSegment : searchDir.relativize(path)) { + if (DEFAULT_IGNORED_DIRECTORIES.contains(pathSegment.toString())) { + return true; + } + } + return false; + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SourceFileParser.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SourceFileParser.java new file mode 100644 index 0000000..5c0379b --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SourceFileParser.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.marker.Marker; +import org.openrewrite.style.NamedStyles; +import org.openrewrite.xml.tree.Xml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.parsers.maven.MavenModuleParser; + +import java.nio.file.Path; +import java.util.*; + +/** + * @author Fabian Krüger + */ +public class SourceFileParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(RewriteParserConfiguration.class); + + private final MavenModuleParser moduleParser; + + public SourceFileParser(MavenModuleParser moduleParser) { + this.moduleParser = moduleParser; + } + + public List parseOtherSourceFiles(Path baseDir, ParserContext parserContext, List resources, + Map> provenanceMarkers, List styles, ExecutionContext executionContext) { + + Set parsedSourceFiles = new LinkedHashSet<>(); + + parserContext.getSortedProjects().forEach(currentMavenProject -> { + Xml.Document moduleBuildFile = currentMavenProject.getSourceFile(); + List markers = provenanceMarkers.get(currentMavenProject.getPomFilePath()); + if (markers == null || markers.isEmpty()) { + LOGGER.warn("Could not find provenance markers for resource '%s'" + .formatted(parserContext.getMatchingBuildFileResource(currentMavenProject))); + } + List sourceFiles = moduleParser.parseModuleSourceFiles(resources, currentMavenProject, + moduleBuildFile, markers, styles, executionContext, baseDir); + parsedSourceFiles.addAll(sourceFiles); + }); + + return new ArrayList<>(parsedSourceFiles); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SourceSetParsingResult.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SourceSetParsingResult.java new file mode 100644 index 0000000..512cd7b --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SourceSetParsingResult.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.SourceFile; +import org.openrewrite.java.tree.JavaType; + +import java.util.List; + +/** + * @author Fabian Krüger + */ +public record SourceSetParsingResult(List sourceFiles, List classpath) { +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SpringRewriteProperties.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SpringRewriteProperties.java new file mode 100644 index 0000000..6f4bb4d --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/SpringRewriteProperties.java @@ -0,0 +1,162 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +/** + * ConfigurationProperties with prefix {@code parser}. Defaults coming from + * {@code META-INF/sbm-support-rewrite.properties} + * + * @author Fabian Krüger + */ +@ConfigurationProperties(prefix = "spring.rewrite") +public class SpringRewriteProperties { + + /** + * Whether to skip parsing maven pom files + */ + private boolean skipMavenParsing = false; + + /** + * Enable/Disable the MavenPomCache. With {@code false} OpenRewrite's + * InMemoryMavenPomCache will be used. With {@code true} a composite cache of + * RocksdbMavenPomCache and InMemoryMavenPomCache will be used on 64-Bit system. + */ + private boolean pomCacheEnabled = false; + + /** + * Defines the cache dir for RocksdbMavenPomCache when {@code parser.pomCacheEnabled} + * is {@code true}. + */ + private String pomCacheDirectory = Path.of(System.getProperty("user.home")) + .resolve(".rewrite-cache") + .toAbsolutePath() + .normalize() + .toString(); + + /** + * Comma-separated list of patterns used to create PathMatcher The pattern should not + * contain a leading 'glob:' + */ + private Set plainTextMasks = Set.of("*.txt"); + + /** + * Project resources exceeding this threshold will not be parsed and provided as + * org.openrewrite.quark.Quark + */ + private int sizeThresholdMb = 10; + + /** + * Whether only the current Maven module will be parsed + */ + private boolean runPerSubmodule = false; + + /** + * Comma-separated list of active Maven profiles + */ + private List activeProfiles = List.of("default"); + + /** + * Comma-separated list of patterns used to create PathMatcher to exclude paths from + * being parsed. + */ + private Set ignoredPathPatterns = Set.of("**/target/**", "target/**", "**/.idea/**", ".idea/**", ".mvn/**", + "**/.mvn/**", "**.git/**"); + + /** + * Whether the discovery should fail on invalid active recipes. TODO: Move to + * 'discovery' prefix + */ + private boolean failOnInvalidActiveRecipes = true; + + public boolean isSkipMavenParsing() { + return skipMavenParsing; + } + + public void setSkipMavenParsing(boolean skipMavenParsing) { + this.skipMavenParsing = skipMavenParsing; + } + + public boolean isPomCacheEnabled() { + return pomCacheEnabled; + } + + public void setPomCacheEnabled(boolean pomCacheEnabled) { + this.pomCacheEnabled = pomCacheEnabled; + } + + public String getPomCacheDirectory() { + return pomCacheDirectory; + } + + public void setPomCacheDirectory(String pomCacheDirectory) { + this.pomCacheDirectory = pomCacheDirectory; + } + + public Set getPlainTextMasks() { + return plainTextMasks; + } + + public void setPlainTextMasks(Set plainTextMasks) { + this.plainTextMasks = plainTextMasks; + } + + public int getSizeThresholdMb() { + return sizeThresholdMb; + } + + public void setSizeThresholdMb(int sizeThresholdMb) { + this.sizeThresholdMb = sizeThresholdMb; + } + + public boolean isRunPerSubmodule() { + return runPerSubmodule; + } + + public void setRunPerSubmodule(boolean runPerSubmodule) { + this.runPerSubmodule = runPerSubmodule; + } + + public List getActiveProfiles() { + return activeProfiles; + } + + public void setActiveProfiles(List activeProfiles) { + this.activeProfiles = activeProfiles; + } + + public Set getIgnoredPathPatterns() { + return ignoredPathPatterns; + } + + public void setIgnoredPathPatterns(Set ignoredPathPatterns) { + this.ignoredPathPatterns = ignoredPathPatterns; + } + + public boolean isFailOnInvalidActiveRecipes() { + return failOnInvalidActiveRecipes; + } + + public void setFailOnInvalidActiveRecipes(boolean failOnInvalidActiveRecipes) { + this.failOnInvalidActiveRecipes = failOnInvalidActiveRecipes; + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/StyleDetector.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/StyleDetector.java new file mode 100644 index 0000000..6fd7e22 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/StyleDetector.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers; + +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.marker.Marker; +import org.openrewrite.style.NamedStyles; +import org.openrewrite.xml.tree.Xml; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +/** + * @author Fabian Krüger + */ + +public class StyleDetector { + + List sourcesWithAutoDetectedStyles(Stream sourceFiles) { + org.openrewrite.java.style.Autodetect.Detector javaDetector = org.openrewrite.java.style.Autodetect.detector(); + org.openrewrite.xml.style.Autodetect.Detector xmlDetector = org.openrewrite.xml.style.Autodetect.detector(); + List sourceFileList = sourceFiles.peek(javaDetector::sample).peek(xmlDetector::sample).toList(); + + Map, NamedStyles> stylesByType = new HashMap<>(); + stylesByType.put(JavaSourceFile.class, javaDetector.build()); + stylesByType.put(Xml.Document.class, xmlDetector.build()); + + return ListUtils.map(sourceFileList, applyAutodetectedStyle(stylesByType)); + } + + private UnaryOperator applyAutodetectedStyle(Map, NamedStyles> stylesByType) { + return (before) -> { + Iterator var2 = stylesByType.entrySet().iterator(); + + while (var2.hasNext()) { + Map.Entry, NamedStyles> styleTypeEntry = (Map.Entry) var2.next(); + if (((Class) styleTypeEntry.getKey()).isAssignableFrom(before.getClass())) { + before = (SourceFile) before + .withMarkers(before.getMarkers().add((Marker) styleTypeEntry.getValue())); + } + } + + return before; + }; + } + + // public List sourcesWithAutoDetectedStyles(Stream + // sourceFilesStream) { + // OpenedRewriteMojo m = new OpenedRewriteMojo(); + // Method method = ReflectionUtils.findMethod(OpenedRewriteMojo.class, + // "sourcesWithAutoDetectedStyles", Stream.class); + // ReflectionUtils.makeAccessible(method); + // return (List) ReflectionUtils.invokeMethod(method, m, + // sourceFilesStream); + // } + // + // static class OpenedRewriteMojo extends AbstractRewriteMojo { + // + // @Override + // public void execute() throws MojoExecutionException, MojoFailureException { + // throw new UnsupportedOperationException(); + // } + // } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/FinishedParsingResourceEvent.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/FinishedParsingResourceEvent.java new file mode 100644 index 0000000..ef68591 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/FinishedParsingResourceEvent.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers.events; + +import org.openrewrite.Parser; +import org.openrewrite.SourceFile; + +/** + * Event published after every parsed resource. + * + * @author Fabian Krüger + */ +public record FinishedParsingResourceEvent(Parser.Input input, SourceFile sourceFile) { +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/IntermediateParsingEvent.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/IntermediateParsingEvent.java new file mode 100644 index 0000000..b9080a5 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/IntermediateParsingEvent.java @@ -0,0 +1,28 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.rewrite.parsers.events; + +import org.openrewrite.tree.ParsingEventListener; + +/** + * Event published on every call to + * {@link ParsingEventListener#intermediateMessage(String)}. + * + * @author Fabian Krüger + */ +public record IntermediateParsingEvent(String stateMessage) { +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/RewriteParsingEventListenerAdapter.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/RewriteParsingEventListenerAdapter.java new file mode 100644 index 0000000..d248b0f --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/RewriteParsingEventListenerAdapter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers.events; + +import org.openrewrite.Parser; +import org.openrewrite.SourceFile; +import org.openrewrite.tree.ParsingEventListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; + +/** + * Adapter listening to OpenRewrite ParsingEvents and publishing them as Spring + * application events. + * + * @author Fabian Krüger + */ +public class RewriteParsingEventListenerAdapter implements ParsingEventListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(RewriteParsingEventListenerAdapter.class); + + private final ApplicationEventPublisher eventPublisher; + + public RewriteParsingEventListenerAdapter(ApplicationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + @Override + public void intermediateMessage(String stateMessage) { + eventPublisher.publishEvent(new IntermediateParsingEvent(stateMessage)); + } + + @Override + public void startedParsing(Parser.Input input) { + eventPublisher.publishEvent(new StartedParsingResourceEvent(input)); + } + + @Override + public void parsed(Parser.Input input, SourceFile sourceFile) { + LOGGER.debug("Parsed %s to %s".formatted(input.getPath(), sourceFile.getSourcePath())); + eventPublisher.publishEvent(new FinishedParsingResourceEvent(input, sourceFile)); + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/StartedParsingProjectEvent.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/StartedParsingProjectEvent.java new file mode 100644 index 0000000..83f97c6 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/StartedParsingProjectEvent.java @@ -0,0 +1,28 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers.events; + +import org.springframework.core.io.Resource; + +import java.util.List; + +/** + * Event published when project parsing started. + * + * @author Fabian Krüger + */ +public record StartedParsingProjectEvent(List resources) { +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/StartedParsingResourceEvent.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/StartedParsingResourceEvent.java new file mode 100644 index 0000000..03157b2 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/StartedParsingResourceEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers.events; + +import org.openrewrite.Parser; + +/** + * Event published when a new resource gets parsed. + * + * @author Fabian Krüger + */ +public record StartedParsingResourceEvent(Parser.Input input) { +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/SuccessfullyParsedProjectEvent.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/SuccessfullyParsedProjectEvent.java new file mode 100644 index 0000000..e0d7edc --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/events/SuccessfullyParsedProjectEvent.java @@ -0,0 +1,28 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers.events; + +import org.openrewrite.SourceFile; + +import java.util.List; + +/** + * Event published after successfully parsing the project. + * + * @author Fabian Krüger + */ +public record SuccessfullyParsedProjectEvent(List sourceFiles) { +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/maven/BuildFileParser.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/maven/BuildFileParser.java new file mode 100644 index 0000000..8b84141 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/maven/BuildFileParser.java @@ -0,0 +1,178 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers.maven; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.SourceFile; +import org.openrewrite.marker.Marker; +import org.openrewrite.maven.MavenParser; +import org.openrewrite.xml.tree.Xml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.rewrite.utils.ResourceUtil; +import org.springframework.util.Assert; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Collections.emptyList; + +/** + * Copies behaviour from rewrite-maven-plugin:5.2.2 + * + * @author Fabian Krüger + */ +public class BuildFileParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(BuildFileParser.class); + + private final MavenSettingsInitializer mavenSettingsInitilizer; + + public BuildFileParser(MavenSettingsInitializer mavenSettingsInitilizer) { + this.mavenSettingsInitilizer = mavenSettingsInitilizer; + } + + /** + * Parse a list of Maven Pom files to a {@code List} of {@link Xml.Document}s. The + * {@link Xml.Document}s get marked with + * {@link org.openrewrite.maven.tree.MavenResolutionResult} and the provided + * provenance markers. + * @param baseDir the {@link Path} to the root of the scanned project + * @param buildFiles the list of resources for relevant pom files. + * @param activeProfiles the active Maven profiles + * @param executionContext the ExecutionContext to use + * @param skipMavenParsing skip parsing Maven files + * @param provenanceMarkers the map of markers to be added + */ + public List parseBuildFiles(Path baseDir, List buildFiles, List activeProfiles, + ExecutionContext executionContext, boolean skipMavenParsing, Map> provenanceMarkers) { + Assert.notNull(baseDir, "Base directory must be provided but was null."); + Assert.notEmpty(buildFiles, "No build files provided."); + List nonPomFiles = retrieveNonPomFiles(buildFiles); + Assert.isTrue(nonPomFiles.isEmpty(), "Provided resources which are not Maven build files: '%s'" + .formatted(nonPomFiles.stream().map(r -> ResourceUtil.getPath(r).toAbsolutePath()).toList())); + List resourcesWithoutProvenanceMarker = findResourcesWithoutProvenanceMarker(baseDir, buildFiles, + provenanceMarkers); + Assert.isTrue(resourcesWithoutProvenanceMarker.isEmpty(), + "No provenance marker provided for these pom files %s" + .formatted(resourcesWithoutProvenanceMarker.stream() + .map(r -> ResourceUtil.getPath(r).toAbsolutePath()) + .toList())); + + if (skipMavenParsing) { + LOGGER.info("Maven parsing skipped [parser.skipMavenParsing=true]."); + return List.of(); + } + + MavenParser.Builder mavenParserBuilder = MavenParser.builder() + .mavenConfig(baseDir.resolve(".mvn/maven.config")); + + mavenSettingsInitilizer.initializeMavenSettings(); + + mavenParserBuilder.activeProfiles(activeProfiles.toArray(new String[] {})); + + List parsedPoms = parsePoms(baseDir, buildFiles, mavenParserBuilder, executionContext) + .map(pp -> this.markPomFile(pp, + provenanceMarkers.getOrDefault(baseDir.resolve(pp.getSourcePath()), emptyList()))) + .toList(); + + return parsedPoms; + } + + private List findResourcesWithoutProvenanceMarker(Path baseDir, List buildFileResources, + Map> provenanceMarkers) { + return buildFileResources.stream() + .filter(r -> !provenanceMarkers.containsKey(baseDir.resolve(ResourceUtil.getPath(r)).normalize())) + .toList(); + } + + private static List retrieveNonPomFiles(List buildFileResources) { + return buildFileResources.stream() + .filter(r -> !"pom.xml".equals(ResourceUtil.getPath(r).getFileName().toString())) + .toList(); + } + + private Xml.Document markPomFile(Xml.Document pp, List markers) { + for (Marker marker : markers) { + pp = pp.withMarkers(pp.getMarkers().addIfAbsent(marker)); + } + return pp; + } + + private Map createResult(Path basePath, List pomFiles, List parsedPoms) { + return parsedPoms.stream() + .map(pom -> mapResourceToDocument(basePath, pom, pomFiles)) + .collect(Collectors.toMap(e -> ResourceUtil.getPath(e.getKey()), e -> e.getValue())); + } + + private Map.Entry mapResourceToDocument(Path basePath, SourceFile pom, + List parsedPoms) { + Xml.Document doc = (Xml.Document) pom; + Resource resource = parsedPoms.stream() + .filter(p -> ResourceUtil.getPath(p) + .toString() + .equals(basePath.resolve(pom.getSourcePath()).toAbsolutePath().normalize().toString())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Could not find matching path for Xml.Document '%s'" + .formatted(pom.getSourcePath().toAbsolutePath().normalize().toString()))); + return Map.entry(resource, doc); + } + + private Stream parsePoms(Path baseDir, List pomFiles, + MavenParser.Builder mavenParserBuilder, ExecutionContext executionContext) { + Iterable pomFileInputs = pomFiles.stream() + .map(p -> new Parser.Input(ResourceUtil.getPath(p), () -> ResourceUtil.getInputStream(p))) + .toList(); + return mavenParserBuilder.build() + .parseInputs(pomFileInputs, baseDir, executionContext) + .map(Xml.Document.class::cast); + } + + public List filterAndSortBuildFiles(List resources) { + return resources.stream() + .filter(r -> "pom.xml".equals(ResourceUtil.getPath(r).toFile().getName())) + .filter(r -> filterTestResources(r)) + .sorted((r1, r2) -> { + + Path r1Path = ResourceUtil.getPath(r1); + ArrayList r1PathParts = new ArrayList<>(); + r1Path.iterator().forEachRemaining(it -> r1PathParts.add(it.toString())); + + Path r2Path = ResourceUtil.getPath(r2); + ArrayList r2PathParts = new ArrayList<>(); + r2Path.iterator().forEachRemaining(it -> r2PathParts.add(it.toString())); + return Integer.compare(r1PathParts.size(), r2PathParts.size()); + }) + .toList(); + } + + private static boolean filterTestResources(Resource r) { + String path = ResourceUtil.getPath(r).toString(); + boolean underTest = path.contains("src/test"); + if (underTest) { + LOGGER.info("Ignore build file '%s' having 'src/test' in its path indicating it's a build file for tests." + .formatted(path)); + } + return !underTest; + } + +} diff --git a/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/maven/MavenConfigFileParser.java b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/maven/MavenConfigFileParser.java new file mode 100644 index 0000000..8f8c0b3 --- /dev/null +++ b/spring-rewrite-commons-launcher/src/main/java/org/springframework/rewrite/parsers/maven/MavenConfigFileParser.java @@ -0,0 +1,353 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.rewrite.parsers.maven; + +import org.apache.commons.cli.*; +import org.apache.maven.cli.CleanArgument; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Parse {@code .mvn/maven.config/} and provide access to relevant settings. Code + * thankfully taken from org.apache.maven.cli.CLIManager. + * + * @author Fabian Krüger + */ +class MavenConfigFileParser { + + private static final char ALTERNATE_POM_FILE = 'f'; + + private static final char BATCH_MODE = 'B'; + + private static final char SET_USER_PROPERTY = 'D'; + + private static final char OFFLINE = 'o'; + + private static final char QUIET = 'q'; + + private static final char DEBUG = 'X'; + + private static final char ERRORS = 'e'; + + private static final char HELP = 'h'; + + private static final char VERSION = 'v'; + + private static final char SHOW_VERSION = 'V'; + + private static final char NON_RECURSIVE = 'N'; + + private static final char UPDATE_SNAPSHOTS = 'U'; + + private static final char ACTIVATE_PROFILES = 'P'; + + private static final String SUPRESS_SNAPSHOT_UPDATES = "nsu"; + + private static final char CHECKSUM_FAILURE_POLICY = 'C'; + + private static final char CHECKSUM_WARNING_POLICY = 'c'; + + private static final char ALTERNATE_USER_SETTINGS = 's'; + + private static final String ALTERNATE_GLOBAL_SETTINGS = "gs"; + + private static final char ALTERNATE_USER_TOOLCHAINS = 't'; + + private static final String ALTERNATE_GLOBAL_TOOLCHAINS = "gt"; + + private static final String FAIL_FAST = "ff"; + + private static final String FAIL_AT_END = "fae"; + + private static final String FAIL_NEVER = "fn"; + + private static final String RESUME_FROM = "rf"; + + private static final String PROJECT_LIST = "pl"; + + private static final String ALSO_MAKE = "am"; + + private static final String ALSO_MAKE_DEPENDENTS = "amd"; + + private static final String LOG_FILE = "l"; + + private static final String ENCRYPT_MASTER_PASSWORD = "emp"; + + private static final String ENCRYPT_PASSWORD = "ep"; + + private static final String THREADS = "T"; + + private static final String BUILDER = "b"; + + private static final String NO_TRANSFER_PROGRESS = "ntp"; + + private static final String COLOR = "color"; + + private static final String MVN_MAVEN_CONFIG = ".mvn/maven.config"; + + public List getActivatedProfiles(Path baseDir) { + File configFile = baseDir.resolve(MVN_MAVEN_CONFIG).toFile(); + if (configFile.isFile()) { + try (Stream lines = Files.lines(configFile.toPath(), Charset.defaultCharset())) { + String[] args = readFile(lines); + return parse(args).stream() + .filter(o -> String.valueOf(ACTIVATE_PROFILES).equals(o.getOpt())) + .map(Option::getValue) + .map(v -> v.split(",")) + .flatMap(Arrays::stream) + .map(String::trim) + .toList(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + else { + return List.of(); + } + } + + public Map getUserProperties(Path baseDir) { + File configFile = baseDir.resolve(MVN_MAVEN_CONFIG).toFile(); + if (configFile.isFile()) { + try (Stream lines = Files.lines(configFile.toPath(), Charset.defaultCharset())) { + String[] args = readFile(lines); + return parse(args).stream() + .filter(o -> String.valueOf(SET_USER_PROPERTY).equals(o.getOpt())) + .map(Option::getValue) + .filter(v -> v.contains("=")) + .map(v -> v.split("=")) + .collect(Collectors.toMap(a -> a[0], a -> a[1])); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + else { + return Map.of(); + } + } + + @NotNull + private static String[] readFile(Stream lines) { + return lines.filter(arg -> !arg.isEmpty() && !arg.startsWith("#")).toArray(String[]::new); + } + + public List