diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6aae359..b45261c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,9 @@ jobs: strategy: matrix: # https://en.wikipedia.org/wiki/Java_version_history - java: [ '8', '11' ] # LTS + # The tests use JUnit 6, which needs Java 17 or later. The library itself is compiled + # for Java 7 with the JDK 17 toolchain (see build.gradle.kts). + java: [ '17', '21' ] # LTS steps: - name: Checkout @@ -47,7 +49,7 @@ jobs: jdks - name: Build and test - run: ./gradlew build --stacktrace + run: ./gradlew build -PtestJavaVersion=${{ matrix.java }} --stacktrace publish-snapshot: name: Publish snapshot to GitHub Packages @@ -85,7 +87,7 @@ jobs: - name: Setup JDK uses: actions/setup-java@v5 with: - java-version: 8 + java-version: 17 distribution: 'temurin' - name: Setup Gradle diff --git a/.github/workflows/deploy-tagged.yml b/.github/workflows/deploy-tagged.yml index cfba8de..71cda94 100644 --- a/.github/workflows/deploy-tagged.yml +++ b/.github/workflows/deploy-tagged.yml @@ -39,7 +39,7 @@ jobs: - name: Setup JDK and Maven uses: actions/setup-java@v5 with: - java-version: 8 + java-version: 17 distribution: "temurin" - name: Setup key diff --git a/README.md b/README.md index 37e1410..3cfdb45 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ Supported features: The minimum requirements to build this project are: -1. Gradle 8.2 (you can use `./gradlew` or `.\gradlew.bat` which will download it for you). -2. JDK 1.8 (target is 1.7, so don't use much higher version). +1. Gradle 8.14.6 (you can use `./gradlew` or `.\gradlew.bat` which will download it for you). +2. JDK 17 or later to run Gradle. The library targets Java 7. JDK 20 and later cannot compile for Java 7, so Gradle compiles with a JDK 17 toolchain. Gradle downloads JDK 17 if it is not installed. 3. Running `./gradlew build` should run successfully. ## Maven diff --git a/build.gradle.kts b/build.gradle.kts index 2b2583a..52098c3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,23 +10,54 @@ group = "com.uploadcare" val isReleaseVersion = !version.toString().lowercase().endsWith("snapshot") +// The library targets Java 7. JDK 17 is the newest LTS JDK that can still compile for Java 7, +// and JUnit 6 needs Java 17 to run the tests. java { - sourceCompatibility = JavaVersion.VERSION_1_7 - targetCompatibility = JavaVersion.VERSION_1_7 + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } withSourcesJar() withJavadocJar() } +// Run the tests on another JDK with -PtestJavaVersion= (for example 21). +val testJavaVersion = (project.findProperty("testJavaVersion") ?: "17").toString() + +// Mockito 5 attaches an agent to the test JVM. Load it at JVM start, as the Mockito +// documentation advises, so newer JDKs do not warn about dynamic agent loading. +val mockitoAgent = configurations.create("mockitoAgent") + dependencies { - implementation("org.apache.httpcomponents:httpclient:4.5.13") - implementation("org.apache.httpcomponents:httpmime:4.5.13") - implementation("com.fasterxml.jackson.core:jackson-databind:2.16.2") - implementation("commons-codec:commons-codec:1.10") - implementation("commons-io:commons-io:2.7") - implementation("com.sun.activation:javax.activation:1.2.0") - - testImplementation("junit:junit:4.13.1") - testImplementation("org.mockito:mockito-all:1.10.19") + implementation("org.apache.httpcomponents:httpclient:4.5.14") + implementation("org.apache.httpcomponents:httpmime:4.5.14") + implementation("com.fasterxml.jackson.core:jackson-databind:2.21.8") + implementation("commons-codec:commons-codec:1.23.0") + implementation("commons-io:commons-io:2.22.0") + + testImplementation(platform("org.junit:junit-bom:6.1.3")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation("org.mockito:mockito-core:5.24.0") + testImplementation("org.mockito:mockito-junit-jupiter:5.24.0") + mockitoAgent("org.mockito:mockito-core:5.24.0") { isTransitive = false } +} + +tasks { + compileJava { + options.release.set(7) + // JDK 17 warns that release 7 is obsolete. + options.compilerArgs.add("-Xlint:-options") + } + compileTestJava { + options.release.set(17) + } + test { + useJUnitPlatform() + jvmArgs("-javaagent:${mockitoAgent.asPath}") + javaLauncher.set(project.javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(testJavaVersion)) + }) + } } // Setup global publishing repository settings. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c..02f06ab 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 62f495d..d3f0219 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -111,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -201,16 +204,16 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle.kts b/settings.gradle.kts index 1327a81..4b5741d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,6 +8,11 @@ pluginManagement { } } +plugins { + // Downloads the JDK of the Java toolchain when it is not installed. + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { diff --git a/src/main/java/com/uploadcare/api/Client.java b/src/main/java/com/uploadcare/api/Client.java index 07ebed9..1fe061b 100644 --- a/src/main/java/com/uploadcare/api/Client.java +++ b/src/main/java/com/uploadcare/api/Client.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.uploadcare.data.*; import com.uploadcare.exceptions.UploadcareApiException; import com.uploadcare.urls.*; @@ -134,7 +134,7 @@ public Client( .build(); } this.objectMapper = new ObjectMapper(); - this.objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + this.objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); this.objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); } } diff --git a/src/main/java/com/uploadcare/api/RequestHelper.java b/src/main/java/com/uploadcare/api/RequestHelper.java index dda289e..5e9cc78 100644 --- a/src/main/java/com/uploadcare/api/RequestHelper.java +++ b/src/main/java/com/uploadcare/api/RequestHelper.java @@ -23,13 +23,13 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.net.URLConnection; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.*; -import javax.activation.MimetypesFileTypeMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; @@ -77,8 +77,8 @@ public static String iso8601(Date date) { } public static String getMimeType(String fileName) { - MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); - return fileTypeMap.getContentType(fileName); + String type = fileName == null ? null : URLConnection.getFileNameMap().getContentTypeFor(fileName); + return type != null ? type : "application/octet-stream"; } public String makeSignature(HttpUriRequest request, String date, String requestBodyMD5) diff --git a/src/test/java/com/uploadcare/api/FileTest.java b/src/test/java/com/uploadcare/api/FileTest.java index 747d424..416190c 100644 --- a/src/test/java/com/uploadcare/api/FileTest.java +++ b/src/test/java/com/uploadcare/api/FileTest.java @@ -3,8 +3,9 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class FileTest { @@ -20,7 +21,7 @@ public void enumFails() throws Exception { Bug bug = mapper.readValue(json, Bug.class); - Assert.assertTrue("Color mode was not properly converted!", File.ColorMode.RGBa.equals(bug.colorMode)); + assertEquals(File.ColorMode.RGBa, bug.colorMode, "Color mode was not properly converted!"); } static class Bug { diff --git a/src/test/java/com/uploadcare/api/RequestHelperTest.java b/src/test/java/com/uploadcare/api/RequestHelperTest.java index 6867f77..b1fbb0e 100644 --- a/src/test/java/com/uploadcare/api/RequestHelperTest.java +++ b/src/test/java/com/uploadcare/api/RequestHelperTest.java @@ -2,8 +2,8 @@ import com.uploadcare.urls.Urls; import org.apache.http.client.methods.HttpGet; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.net.URI; import java.security.InvalidKeyException; @@ -11,7 +11,7 @@ import java.util.Calendar; import java.util.GregorianCalendar; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class RequestHelperTest { @@ -19,7 +19,7 @@ public class RequestHelperTest { private static final String FORMATTED_DATE = "Fri, 17 Nov 1989 00:00:00 +0000"; private RequestHelper requestHelper; - @Before + @BeforeEach public void setUp() { Client client = Client.demoClient(); requestHelper = new RequestHelper(client); @@ -41,4 +41,14 @@ public void test_makeSignature() throws InvalidKeyException, NoSuchAlgorithmExce assertEquals("535e263808dd38599343f04aab3c9f34bb15573c", signature); } + @Test + public void test_getMimeType() { + assertEquals("image/png", RequestHelper.getMimeType("olympia.png")); + assertEquals("image/jpeg", RequestHelper.getMimeType("olympia.jpg")); + assertEquals("text/plain", RequestHelper.getMimeType("notes.txt")); + assertEquals("application/octet-stream", RequestHelper.getMimeType("file.unknownext")); + assertEquals("application/octet-stream", RequestHelper.getMimeType("noextension")); + assertEquals("application/octet-stream", RequestHelper.getMimeType(null)); + } + } diff --git a/src/test/java/com/uploadcare/upload/FileUploaderTest.java b/src/test/java/com/uploadcare/upload/FileUploaderTest.java index 1c67891..3be6ffb 100644 --- a/src/test/java/com/uploadcare/upload/FileUploaderTest.java +++ b/src/test/java/com/uploadcare/upload/FileUploaderTest.java @@ -9,17 +9,16 @@ import com.uploadcare.urls.Urls; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.HttpUriRequest; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; import org.mockito.InOrder; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; public class FileUploaderTest { @@ -76,13 +75,9 @@ public RequestHelper get(Client client) { } private HttpUriRequest requestThat(final URI uri) { - return argThat(new BaseMatcher() { - public boolean matches(Object o) { - HttpUriRequest request = (HttpUriRequest) o; - return request.getURI().equals(uri); - } - - public void describeTo(Description description) { + return argThat(new ArgumentMatcher() { + public boolean matches(HttpUriRequest request) { + return request != null && request.getURI().equals(uri); } }); } diff --git a/src/test/java/com/uploadcare/urls/CdnPathBuilderTest.java b/src/test/java/com/uploadcare/urls/CdnPathBuilderTest.java index fdac63d..e3adae8 100644 --- a/src/test/java/com/uploadcare/urls/CdnPathBuilderTest.java +++ b/src/test/java/com/uploadcare/urls/CdnPathBuilderTest.java @@ -2,24 +2,30 @@ import com.uploadcare.api.File; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.awt.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) public class CdnPathBuilderTest { private static final String FILE_ID = "27c7846b-a019-4516-a5e4-de635f822161"; + + @Mock + private File file; + private CdnPathBuilder builder; - @Before + @BeforeEach public void setUp() { - File file = mock(File.class); when(file.getFileId()).thenReturn(FILE_ID); builder = new CdnPathBuilder(file); @@ -91,32 +97,16 @@ public void test_detectFaces() { public void test_dimensionGuard() { builder.resizeWidth(1); builder.resizeWidth(2048); - try { - builder.resizeWidth(0); - } catch (IllegalArgumentException e1) { - try { - builder.resizeWidth(2049); - } catch (IllegalArgumentException e2) { - return; - } - } - fail(); + assertThrows(IllegalArgumentException.class, () -> builder.resizeWidth(0)); + assertThrows(IllegalArgumentException.class, () -> builder.resizeWidth(2049)); } @Test public void test_dimensionsGuard() { builder.resize(1024, 634); builder.resize(634, 1024); - try { - builder.resize(1024, 635); - } catch (IllegalArgumentException e1) { - try { - builder.resize(635, 1024); - } catch (IllegalArgumentException e2) { - return; - } - } - fail(); + assertThrows(IllegalArgumentException.class, () -> builder.resize(1024, 635)); + assertThrows(IllegalArgumentException.class, () -> builder.resize(635, 1024)); } }