diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 1e486fe672c8..5c58b85ab5a3 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -59,3 +59,8 @@ runs: version=$(sed -n 's/version=\(.*\)/\1/p' gradle.properties) echo "Version is $version" echo "version=$version" >> $GITHUB_OUTPUT + - name: SanityCheck + id: build + if: ${{ inputs.publish == 'false' }} + shell: bash + run: ./gradlew rewriteDryRun -Dorg.gradle.jvmargs=-Xmx8G \ No newline at end of file diff --git a/build.gradle b/build.gradle index a86c93fcd8a6..0439a9d771ff 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ plugins { id 'com.gradleup.shadow' version "9.2.2" apply false id 'me.champeau.jmh' version '0.7.2' apply false id 'io.spring.nullability' version '0.0.8' apply false + id 'org.openrewrite.rewrite' version '7.20.0' apply false } ext { @@ -16,6 +17,8 @@ ext { description = "Spring Framework" +apply from: "$rootDir/gradle/rewrite.gradle" + configure(allprojects) { project -> apply plugin: "org.springframework.build.localdev" group = "org.springframework" diff --git a/gradle/rewrite.gradle b/gradle/rewrite.gradle new file mode 100644 index 000000000000..b366d8915ba0 --- /dev/null +++ b/gradle/rewrite.gradle @@ -0,0 +1,12 @@ +apply plugin: 'org.openrewrite.rewrite' + +rewrite { + activeRecipe('org.springframework.openrewrite.SanityCheck') + setExportDatatables(true) + setFailOnDryRunResults(true) +} + +dependencies { + rewrite(platform('org.openrewrite.recipe:rewrite-recipe-bom:3.18.0')) + rewrite('org.openrewrite.recipe:rewrite-rewrite:0.15.0') +} diff --git a/rewrite.yml b/rewrite.yml new file mode 100644 index 000000000000..02e679a80ef3 --- /dev/null +++ b/rewrite.yml @@ -0,0 +1,19 @@ +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.springframework.openrewrite.SanityCheck +displayName: Apply all Java & Gradle best practices +description: Comprehensive code quality recipe combining modernization, security, and best practices. +tags: + - java + - gradle + - static-analysis + - cleanup +recipeList: + - org.openrewrite.gradle.EnableGradleBuildCache + - org.openrewrite.gradle.EnableGradleParallelExecution + - org.openrewrite.gradle.GradleBestPractices + - tech.picnic.errorprone.refasterrules.TimeRulesRecipes + # TBD + # - org.openrewrite.java.migrate.Java8toJava11 # https://github.com/google/error-prone/pull/5328 + # - org.openrewrite.java.migrate.UpgradeToJava17 # https://github.com/checkstyle/checkstyle/pull/17730 +--- \ No newline at end of file diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeConverters.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeConverters.java index 5a1c6b6f1caf..55f7665b1261 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeConverters.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeConverters.java @@ -81,8 +81,7 @@ private static ZonedDateTime calendarToZonedDateTime(Calendar source) { return gc.toZonedDateTime(); } else { - return ZonedDateTime.ofInstant(Instant.ofEpochMilli(source.getTimeInMillis()), - source.getTimeZone().toZoneId()); + return Instant.ofEpochMilli(source.getTimeInMillis()).atZone(source.getTimeZone().toZoneId()); } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java index a88aa1a51de0..5a055ce66ff6 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java @@ -115,7 +115,7 @@ public String getExpression() { public @Nullable Instant nextExecution(TriggerContext triggerContext) { Instant timestamp = determineLatestTimestamp(triggerContext); ZoneId zone = (this.zoneId != null ? this.zoneId : triggerContext.getClock().getZone()); - ZonedDateTime zonedTimestamp = ZonedDateTime.ofInstant(timestamp, zone); + ZonedDateTime zonedTimestamp = timestamp.atZone(zone); ZonedDateTime nextTimestamp = this.expression.next(zonedTimestamp); return (nextTimestamp != null ? nextTimestamp.toInstant() : null); } diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/DefaultManagedTaskSchedulerTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/DefaultManagedTaskSchedulerTests.java index 2e5002432580..6d52a6349292 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/DefaultManagedTaskSchedulerTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/DefaultManagedTaskSchedulerTests.java @@ -18,7 +18,6 @@ import java.time.Duration; import java.time.Instant; -import java.time.temporal.ChronoUnit; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.Test; @@ -53,28 +52,28 @@ void scheduleWithInstantAndNoScheduledExecutorProvidesDedicatedException() { void scheduleAtFixedRateWithStartTimeAndDurationAndNoScheduledExecutorProvidesDedicatedException() { DefaultManagedTaskScheduler scheduler = new DefaultManagedTaskScheduler(); assertNoExecutorException(() -> scheduler.scheduleAtFixedRate( - NO_OP, Instant.now(), Duration.of(1, ChronoUnit.MINUTES))); + NO_OP, Instant.now(), Duration.ofMinutes(1))); } @Test void scheduleAtFixedRateWithDurationAndNoScheduledExecutorProvidesDedicatedException() { DefaultManagedTaskScheduler scheduler = new DefaultManagedTaskScheduler(); assertNoExecutorException(() -> scheduler.scheduleAtFixedRate( - NO_OP, Duration.of(1, ChronoUnit.MINUTES))); + NO_OP, Duration.ofMinutes(1))); } @Test void scheduleWithFixedDelayWithStartTimeAndDurationAndNoScheduledExecutorProvidesDedicatedException() { DefaultManagedTaskScheduler scheduler = new DefaultManagedTaskScheduler(); assertNoExecutorException(() -> scheduler.scheduleWithFixedDelay( - NO_OP, Instant.now(), Duration.of(1, ChronoUnit.MINUTES))); + NO_OP, Instant.now(), Duration.ofMinutes(1))); } @Test void scheduleWithFixedDelayWithDurationAndNoScheduledExecutorProvidesDedicatedException() { DefaultManagedTaskScheduler scheduler = new DefaultManagedTaskScheduler(); assertNoExecutorException(() -> scheduler.scheduleWithFixedDelay( - NO_OP, Duration.of(1, ChronoUnit.MINUTES))); + NO_OP, Duration.ofMinutes(1))); } private void assertNoExecutorException(ThrowingCallable callable) { diff --git a/spring-test/src/main/java/org/springframework/test/http/HttpHeadersAssert.java b/spring-test/src/main/java/org/springframework/test/http/HttpHeadersAssert.java index a9dd7ef01f18..af2fc3fed9cb 100644 --- a/spring-test/src/main/java/org/springframework/test/http/HttpHeadersAssert.java +++ b/spring-test/src/main/java/org/springframework/test/http/HttpHeadersAssert.java @@ -18,6 +18,7 @@ import java.time.Instant; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Arrays; @@ -42,7 +43,7 @@ */ public class HttpHeadersAssert extends AbstractObjectAssert { - private static final ZoneId GMT = ZoneId.of("GMT"); + private static final ZoneId GMT = ZoneOffset.UTC; private final AbstractCollectionAssert, String, ObjectAssert> namesAssert; @@ -173,7 +174,7 @@ public HttpHeadersAssert hasValue(String name, Instant value) { containsHeader(name); Assertions.assertThat(this.actual.getFirstZonedDateTime(name)) .as("check primary date value for HTTP header '%s'", name) - .isCloseTo(ZonedDateTime.ofInstant(value, GMT), Assertions.within(999, ChronoUnit.MILLIS)); + .isCloseTo(value.atZone(GMT), Assertions.within(999, ChronoUnit.MILLIS)); return this.myself; } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/client/RestTestClientTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/client/RestTestClientTests.java index 08f9a23289d9..0dbe6ec628d7 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/client/RestTestClientTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/client/RestTestClientTests.java @@ -18,7 +18,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Map; @@ -263,7 +263,7 @@ void testAcceptCharset() { @Test void testIfModifiedSince() { RestTestClientTests.this.client.get().uri("/test") - .ifModifiedSince(ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneId.of("GMT"))) + .ifModifiedSince(ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) .exchange() .expectStatus().isOk() .expectBody().jsonPath("$.headers.If-Modified-Since").isEqualTo("Thu, 01 Jan 1970 00:00:00 GMT"); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/HeaderResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/HeaderResultMatchersTests.java index 0312672eb43a..a3166e16df10 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/HeaderResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/HeaderResultMatchersTests.java @@ -16,7 +16,7 @@ package org.springframework.test.web.servlet.result; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import org.junit.jupiter.api.Test; @@ -45,7 +45,7 @@ public class HeaderResultMatchersTests { @Test // SPR-17330 public void matchDateFormattedWithHttpHeaders() throws Exception { - long epochMilli = ZonedDateTime.of(2018, 10, 5, 0, 0, 0, 0, ZoneId.of("GMT")).toInstant().toEpochMilli(); + long epochMilli = ZonedDateTime.of(2018, 10, 5, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli(); HttpHeaders headers = new HttpHeaders(); headers.setDate("myDate", epochMilli); this.response.setHeader("d", headers.getFirst("myDate")); diff --git a/spring-test/src/test/java/org/springframework/test/web/support/HeaderAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/support/HeaderAssertionTests.java index 903b3fbf9ef2..3666fd971807 100644 --- a/spring-test/src/test/java/org/springframework/test/web/support/HeaderAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/support/HeaderAssertionTests.java @@ -17,7 +17,7 @@ package org.springframework.test.web.support; import java.net.URI; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.concurrent.TimeUnit; @@ -272,7 +272,7 @@ void contentType() { @Test void expires() { HttpHeaders headers = new HttpHeaders(); - ZonedDateTime expires = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); + ZonedDateTime expires = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); headers.setExpires(expires); TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.expires(expires.toInstant().toEpochMilli()); @@ -285,7 +285,7 @@ void expires() { @Test void lastModified() { HttpHeaders headers = new HttpHeaders(); - ZonedDateTime lastModified = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); + ZonedDateTime lastModified = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); headers.setLastModified(lastModified.toInstant().toEpochMilli()); TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.lastModified(lastModified.toInstant().toEpochMilli()); diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 1868088cb34d..c1da0f210ec3 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -27,6 +27,7 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -414,7 +415,7 @@ public class HttpHeaders implements Serializable { private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols(Locale.ROOT); - private static final ZoneId GMT = ZoneId.of("GMT"); + private static final ZoneId GMT = ZoneOffset.UTC; /** * Date formats with time zone as specified in the HTTP RFC to use for formatting. @@ -1516,7 +1517,7 @@ public void setZonedDateTime(String headerName, ZonedDateTime date) { * @since 5.1.4 */ public void setInstant(String headerName, Instant date) { - setZonedDateTime(headerName, ZonedDateTime.ofInstant(date, GMT)); + setZonedDateTime(headerName, date.atZone(GMT)); } /** @@ -2172,7 +2173,7 @@ private static MultiValueMap unwrap(HttpHeaders headers) { // Package-private: used in ResponseCookie static String formatDate(long date) { Instant instant = Instant.ofEpochMilli(date); - ZonedDateTime time = ZonedDateTime.ofInstant(instant, GMT); + ZonedDateTime time = instant.atZone(GMT); return DATE_FORMATTER.format(time); } diff --git a/spring-web/src/main/java/org/springframework/web/server/session/InMemoryWebSessionStore.java b/spring-web/src/main/java/org/springframework/web/server/session/InMemoryWebSessionStore.java index 58c949bfaf38..0f490f6f8015 100644 --- a/spring-web/src/main/java/org/springframework/web/server/session/InMemoryWebSessionStore.java +++ b/spring-web/src/main/java/org/springframework/web/server/session/InMemoryWebSessionStore.java @@ -19,7 +19,6 @@ import java.time.Clock; import java.time.Duration; import java.time.Instant; -import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Iterator; @@ -51,7 +50,7 @@ public class InMemoryWebSessionStore implements WebSessionStore { private int maxSessions = 10000; - private Clock clock = Clock.system(ZoneId.of("GMT")); + private Clock clock = Clock.systemUTC(); private final Map sessions = new ConcurrentHashMap<>(); diff --git a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java index 70f43076ba9b..eb77e28b3916 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java @@ -20,7 +20,7 @@ import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -370,7 +370,7 @@ void expiresLong() { @Test void expiresZonedDateTime() { - ZonedDateTime zonedDateTime = ZonedDateTime.of(2008, 12, 18, 10, 20, 0, 0, ZoneId.of("GMT")); + ZonedDateTime zonedDateTime = ZonedDateTime.of(2008, 12, 18, 10, 20, 0, 0, ZoneOffset.UTC); headers.setExpires(zonedDateTime); assertThat(headers.getExpires()).as("Invalid Expires header").isEqualTo(zonedDateTime.toInstant().toEpochMilli()); assertThat(headers.getFirst("expires")).as("Invalid Expires header").isEqualTo("Thu, 18 Dec 2008 10:20:00 GMT"); @@ -605,7 +605,7 @@ void firstDate() { @Test void firstZonedDateTime() { - ZonedDateTime date = ZonedDateTime.of(2017, 6, 2, 2, 22, 0, 0, ZoneId.of("GMT")); + ZonedDateTime date = ZonedDateTime.of(2017, 6, 2, 2, 22, 0, 0, ZoneOffset.UTC); headers.setZonedDateTime(HttpHeaders.DATE, date); assertThat(headers.getFirst(HttpHeaders.DATE)).isEqualTo("Fri, 02 Jun 2017 02:22:00 GMT"); assertThat(headers.getFirstZonedDateTime(HttpHeaders.DATE).isEqual(date)).isTrue(); diff --git a/spring-web/src/test/java/org/springframework/http/client/reactive/ClientHttpConnectorTests.java b/spring-web/src/test/java/org/springframework/http/client/reactive/ClientHttpConnectorTests.java index aa2f0f17f0be..45ab3d8cc45e 100644 --- a/spring-web/src/test/java/org/springframework/http/client/reactive/ClientHttpConnectorTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/reactive/ClientHttpConnectorTests.java @@ -24,7 +24,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -184,7 +184,7 @@ void cancelResponseBody(ClientHttpConnector connector) { @ParameterizedConnectorTest void cookieExpireValueSetAsMaxAge(ClientHttpConnector connector) { - ZonedDateTime tomorrow = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(1); + ZonedDateTime tomorrow = ZonedDateTime.now(ZoneOffset.UTC).plusDays(1); String formattedDate = tomorrow.format(DateTimeFormatter.RFC_1123_DATE_TIME); prepareResponse(builder -> builder diff --git a/spring-web/src/test/java/org/springframework/web/server/i18n/FixedLocaleContextResolverTests.java b/spring-web/src/test/java/org/springframework/web/server/i18n/FixedLocaleContextResolverTests.java index 70e959a720bd..0902e352b121 100644 --- a/spring-web/src/test/java/org/springframework/web/server/i18n/FixedLocaleContextResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/i18n/FixedLocaleContextResolverTests.java @@ -16,7 +16,7 @@ package org.springframework.web.server.i18n; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.Locale; import java.util.TimeZone; @@ -61,7 +61,7 @@ void resolveCustomizedLocale() { @Test void resolveCustomizedAndTimeZoneLocale() { - TimeZone timeZone = TimeZone.getTimeZone(ZoneId.of("UTC")); + TimeZone timeZone = TimeZone.getTimeZone(ZoneOffset.UTC); FixedLocaleContextResolver resolver = new FixedLocaleContextResolver(FRANCE, timeZone); TimeZoneAwareLocaleContext context = (TimeZoneAwareLocaleContext) resolver.resolveLocaleContext(exchange()); assertThat(context.getLocale()).isEqualTo(FRANCE); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java index fddf21a53958..3d3f79501968 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java @@ -19,7 +19,6 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -165,7 +164,7 @@ void writeTo() { RenderingResponse renderingResponse = RenderingResponse.create("view") .status(HttpStatus.FOUND) .modelAttributes(model) - .build().block(Duration.of(5, ChronoUnit.MILLIS)); + .build().block(Duration.ofMillis(5)); assertThat(renderingResponse).isNotNull(); MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://localhost")); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java index acb070a9b782..90a8f1426fa4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java @@ -23,6 +23,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; @@ -97,7 +98,7 @@ */ class HttpEntityMethodProcessorMockTests { - private static final ZoneId GMT = ZoneId.of("GMT"); + private static final ZoneId GMT = ZoneOffset.UTC; private HttpEntityMethodProcessor processor;