tsantalis / RefactoringMiner

MIT License
345 stars 132 forks source link

Missing files in ASTDiff view #752

Closed victorgveloso closed 1 week ago

victorgveloso commented 1 week ago

GitHub diff view:

image

ASTDiff view:

image

When analyzing the commit https://github.com/apache/logging-log4j2/commit/c624e01cc433cc36f23167c8f39ab40873ecc6ec with the latest docker image, only two out of the three file diffs are displayed by ASTDiff.

Notice how ASTDiff completely misses the file BasicPropertyEnvironmentTest.java. Important disclaimer: both parent and child versions of the file are compilable!

This is not the only time I've seen this happening. Though it's the only time I considered reporting as the others were actually part of a Pull Request or their changes were mostly trivial.

In order to replicate this issue, just execute the following command in a machine with docker pre-installed:

docker run -P -d --rm -e OAuthToken=<YOUR_GITHUB_OAUTH_TOKEN> tsantalis/refactoringminer diff --url https://github.com/apache/logging-log4j2/commit/c624e01cc433cc36f23167c8f39ab40873ecc6ec

pouryafard75 commented 1 week ago

@tsantalis I have debugged it and the problem seems to be here and related to RecordDeclaration. The casting seems to be causing error.

image

class org.eclipse.jdt.core.dom.TypeDeclaration cannot be cast to class org.eclipse.jdt.core.dom.RecordDeclaration (org.eclipse.jdt.core.dom.TypeDeclaration and org.eclipse.jdt.core.dom.RecordDeclaration are in unnamed module of loader 'app')

tsantalis commented 1 week ago

@pouryafard75 There are 8 record declarations in the file. So, the if condition is passing, but then the cast is failing. This is super weird.

else if(bodyDeclaration instanceof RecordDeclaration) {
    RecordDeclaration recordDeclaration = (RecordDeclaration)abstractTypeDeclaration;
pouryafard75 commented 1 week ago

@tsantalis It might be the drawback of dynamic adjustment of JAVA_CORE_VERSION here, but I couldnt understand the problem.

tsantalis commented 1 week ago

The type is actually RecordDeclaration. It is super weird that the casting fails.

image

pouryafard75 commented 1 week ago

@tsantalis Oh I see now. You are casting the wrong object. abstractTypeDeclaration -> bodyDeclaration

pouryafard75 commented 1 week ago

@victorgveloso Post-fix snapshot:

image
victorgveloso commented 1 week ago

Brilliant!

Is it possible to manually trigger the docker generation?

pouryafard75 commented 1 week ago

@tsantalis has the permission to do so.

On Fri, Jun 21, 2024 at 9:22 PM vgveloso @.***> wrote:

Brilliant!

Is it possible to manually trigger the docker generation?

— Reply to this email directly, view it on GitHub https://github.com/tsantalis/RefactoringMiner/issues/752#issuecomment-2183644293, or unsubscribe https://github.com/notifications/unsubscribe-auth/AG34LRFV4J4HQROWRCSDUI3ZITGVXAVCNFSM6AAAAABJWKL4SGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCOBTGY2DIMRZGM . You are receiving this because you were mentioned.Message ID: @.***>

victorgveloso commented 1 week ago

Please, @tsantalis reopen this issue. The solution seemed to work in the first place but now for some reason whenever I try to run

docker run -P --rm -e OAuthToken=<MY OAUTH HERE> tsantalis/refactoringminer:latest diff --url https://github.com/apache/logging-log4j2/commit/c624e01cc4

RMiner crashes with the following error message.


[pool-1-thread-1] INFO org.refactoringminer.rm1.GitHistoryRefactoringMinerImpl - Connected to GitHub with OAuth token
[pool-1-thread-1] WARN org.refactoringminer.rm1.GitHistoryRefactoringMinerImpl - Ignored revision c624e01cc4 due to error
org.kohsuke.github.GHException: Failed to retrieve https://api.github.com/repos/apache/logging-log4j2/commits?sha=c624e01cc4
    at org.kohsuke.github.GitHubPageIterator.fetch(GitHubPageIterator.java:151)
    at org.kohsuke.github.GitHubPageIterator.hasNext(GitHubPageIterator.java:87)
    at org.kohsuke.github.PagedIterator.fetch(PagedIterator.java:106)
    at org.kohsuke.github.PagedIterator.hasNext(PagedIterator.java:74)
    at org.kohsuke.github.PagedIterator.next(PagedIterator.java:82)
    at org.refactoringminer.rm1.GitHistoryRefactoringMinerImpl.populateWithGitHubAPI(GitHistoryRefactoringMinerImpl.java:906)
    at org.refactoringminer.rm1.GitHistoryRefactoringMinerImpl.lambda$diffAtCommit$18(GitHistoryRefactoringMinerImpl.java:1814)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: org.kohsuke.github.HttpException: Server returned HTTP response code: 200, message: 'null' for URL: https://api.github.com/repos/apache/logging-log4j2/commits?sha=c624e01cc4
    at org.kohsuke.github.GitHubClient.interpretApiError(GitHubClient.java:473)
    at org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:392)
    at org.kohsuke.github.GitHubPageIterator.fetch(GitHubPageIterator.java:140)
    ... 11 more
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `org.kohsuke.github.GHVerification$Reason` from String "bad_cert": not one of the values accepted for Enum class: [GPGVERIFY_ERROR, BAD_EMAIL, EXPIRED_KEY, GPGVERIFY_UNAVAILABLE, NO_USER, UNKNOWN_KEY, MALFORMED_SIGNATURE, UNSIGNED, UNKNOWN_SIGNATURE_TYPE, INVALID, NOT_SIGNING_KEY, VALID, UNVERIFIED_EMAIL]
 at [Source: (StringReader); line: 1, column: 88748] (through reference chain: java.lang.Object[][21]->org.kohsuke.github.GHCommit["commit"]->org.kohsuke.github.GHCommit$ShortInfo["verification"]->org.kohsuke.github.GHVerification["reason"])
    at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1991)
    at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:1219)
    at com.fasterxml.jackson.databind.deser.std.EnumDeserializer._deserializeAltString(EnumDeserializer.java:357)
    at com.fasterxml.jackson.databind.deser.std.EnumDeserializer._fromString(EnumDeserializer.java:231)
    at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:198)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:214)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:24)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
    at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2051)
    at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1492)
    at org.kohsuke.github.GitHubResponse.parseBody(GitHubResponse.java:97)
    at org.kohsuke.github.GitHubPageIterator.lambda$fetch$0(GitHubPageIterator.java:141)
    at org.kohsuke.github.GitHubClient.createResponse(GitHubClient.java:434)
    at org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:384)
    ... 12 more
Exception in thread "main" java.lang.RuntimeException: java.util.NoSuchElementException
    at gui.webdiff.WebDiffRunner.execute(WebDiffRunner.java:74)
    at org.refactoringminer.RefactoringMiner.main(RefactoringMiner.java:49)
Caused by: java.util.NoSuchElementException
    at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1599)
    at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1620)
    at org.refactoringminer.rm1.GitHistoryRefactoringMinerImpl.diffAtCommit(GitHistoryRefactoringMinerImpl.java:1852)
    at gui.webdiff.WebDiffRunner$RunMode.getProjectASTDIFF(WebDiffRunner.java:110)
    at gui.webdiff.WebDiffRunner.execute(WebDiffRunner.java:67)
    ... 1 more 
victorgveloso commented 1 week ago

It looks like a bug accessing the GitHub API (based on the logged line Caused by: org.kohsuke.github.HttpException: Server returned HTTP response code: 200, message: 'null' for URL: https://api.github.com/repos/apache/logging-log4j2/commits?sha=c624e01cc4 at org.kohsuke.github.GitHubClient.interpretApiError(GitHubClient.java:473))

But I failed to find the problem in that GitHub endpoint.

By opening https://api.github.com/repos/apache/logging-log4j2/commits/c624e01cc433cc36f23167c8f39ab40873ecc6ec in my browser

I get the expected output:

{
    "sha": "c624e01cc433cc36f23167c8f39ab40873ecc6ec",
    "node_id": "C_kwDOAKJSSNoAKGM2MjRlMDFjYzQzM2NjMzZmMjMxNjdjOGYzOWFiNDA4NzNlY2M2ZWM",
    "commit": {
        "author": {
            "name": "Piotr P. Karwasz",
            "email": "piotr.github@karwasz.org",
            "date": "2024-03-01T08:23:32Z"
        },
        "committer": {
            "name": "Piotr P. Karwasz",
            "email": "piotr.github@karwasz.org",
            "date": "2024-03-01T08:23:32Z"
        },
        "message": "Add support for default values\n\nAdds support for:\n\n* default values,\n* boxed values,\n* `Level` properties,\n* `char[]` (e.g. password) properties.",
        "tree": {
            "sha": "f32f3a485e3e4ffd555cbc6371a4b6a7c0fcdd66",
            "url": "https://api.github.com/repos/apache/logging-log4j2/git/trees/f32f3a485e3e4ffd555cbc6371a4b6a7c0fcdd66"
        },
        "url": "https://api.github.com/repos/apache/logging-log4j2/git/commits/c624e01cc433cc36f23167c8f39ab40873ecc6ec",
        "comment_count": 0,
        "verification": {
            "verified": true,
            "reason": "valid",
            "signature": "-----BEGIN PGP SIGNATURE-----\n\niQEzBAABCgAdFiEEwIAsj/uhmqhueF3eiOtwDBqRRdsFAmXhkMcACgkQiOtwDBqR\nRdvjMggAlCw2ICw7BxgVo/pqfn/r05Q2eGBK4bAUPxVsfr9iQ0Tw0K5MyGu8Uhcf\naGV8P+9KaaAytNblbJdfZj9+QGhvQm5qi/xQ8dVNjPKFIG40Wt6hjaVQuFBz2syM\nUT29VhbjmpSA4nKk1OPK18MoxsqpsfwjpmREpooJXIb0MnrpsVITaQITKLw19cyf\nst3RtmMW1QRu7DHI3HiFhFpu4ZSmSZ3+iI9k2v5sq6j97sIkLt+MZvtPeOtLEaPf\n4CMMcAjPA0krwpSlWxywbwnx7nWIQdAB5gBPtvUnuaElJKkYG5qcSaGnAIVYN1LH\nfDe8UDj+QKIfSVz4QrxwGqs95/HYEA==\n=3p3d\n-----END PGP SIGNATURE-----",
            "payload": "tree f32f3a485e3e4ffd555cbc6371a4b6a7c0fcdd66\nparent f6825d24143857a285aa5aa38c907dbea2ab7254\nauthor Piotr P. Karwasz <piotr.github@karwasz.org> 1709281412 +0100\ncommitter Piotr P. Karwasz <piotr.github@karwasz.org> 1709281412 +0100\n\nAdd support for default values\n\nAdds support for:\n\n* default values,\n* boxed values,\n* `Level` properties,\n* `char[]` (e.g. password) properties.\n"
        }
    },
    "url": "https://api.github.com/repos/apache/logging-log4j2/commits/c624e01cc433cc36f23167c8f39ab40873ecc6ec",
    "html_url": "https://github.com/apache/logging-log4j2/commit/c624e01cc433cc36f23167c8f39ab40873ecc6ec",
    "comments_url": "https://api.github.com/repos/apache/logging-log4j2/commits/c624e01cc433cc36f23167c8f39ab40873ecc6ec/comments",
    "author": {
        "login": "ppkarwasz",
        "id": 12533274,
        "node_id": "MDQ6VXNlcjEyNTMzMjc0",
        "avatar_url": "https://avatars.githubusercontent.com/u/12533274?v=4",
        "gravatar_id": "",
        "url": "https://api.github.com/users/ppkarwasz",
        "html_url": "https://github.com/ppkarwasz",
        "followers_url": "https://api.github.com/users/ppkarwasz/followers",
        "following_url": "https://api.github.com/users/ppkarwasz/following{/other_user}",
        "gists_url": "https://api.github.com/users/ppkarwasz/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/ppkarwasz/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/ppkarwasz/subscriptions",
        "organizations_url": "https://api.github.com/users/ppkarwasz/orgs",
        "repos_url": "https://api.github.com/users/ppkarwasz/repos",
        "events_url": "https://api.github.com/users/ppkarwasz/events{/privacy}",
        "received_events_url": "https://api.github.com/users/ppkarwasz/received_events",
        "type": "User",
        "site_admin": false
    },
    "committer": {
        "login": "ppkarwasz",
        "id": 12533274,
        "node_id": "MDQ6VXNlcjEyNTMzMjc0",
        "avatar_url": "https://avatars.githubusercontent.com/u/12533274?v=4",
        "gravatar_id": "",
        "url": "https://api.github.com/users/ppkarwasz",
        "html_url": "https://github.com/ppkarwasz",
        "followers_url": "https://api.github.com/users/ppkarwasz/followers",
        "following_url": "https://api.github.com/users/ppkarwasz/following{/other_user}",
        "gists_url": "https://api.github.com/users/ppkarwasz/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/ppkarwasz/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/ppkarwasz/subscriptions",
        "organizations_url": "https://api.github.com/users/ppkarwasz/orgs",
        "repos_url": "https://api.github.com/users/ppkarwasz/repos",
        "events_url": "https://api.github.com/users/ppkarwasz/events{/privacy}",
        "received_events_url": "https://api.github.com/users/ppkarwasz/received_events",
        "type": "User",
        "site_admin": false
    },
    "parents": [
        {
            "sha": "f6825d24143857a285aa5aa38c907dbea2ab7254",
            "url": "https://api.github.com/repos/apache/logging-log4j2/commits/f6825d24143857a285aa5aa38c907dbea2ab7254",
            "html_url": "https://github.com/apache/logging-log4j2/commit/f6825d24143857a285aa5aa38c907dbea2ab7254"
        }
    ],
    "stats": {
        "total": 542,
        "additions": 333,
        "deletions": 209
    },
    "files": [
        {
            "sha": "c46e1ad1fef3236a7c9ca133e2caac1c36d81c3b",
            "filename": "log4j-kit/src/main/java/org/apache/logging/log4j/kit/env/PropertyEnvironment.java",
            "status": "modified",
            "additions": 7,
            "deletions": 20,
            "changes": 27,
            "blob_url": "https://github.com/apache/logging-log4j2/blob/c624e01cc433cc36f23167c8f39ab40873ecc6ec/log4j-kit%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2FPropertyEnvironment.java",
            "raw_url": "https://github.com/apache/logging-log4j2/raw/c624e01cc433cc36f23167c8f39ab40873ecc6ec/log4j-kit%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2FPropertyEnvironment.java",
            "contents_url": "https://api.github.com/repos/apache/logging-log4j2/contents/log4j-kit%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2FPropertyEnvironment.java?ref=c624e01cc433cc36f23167c8f39ab40873ecc6ec",
            "patch": "@@ -53,15 +53,15 @@ default boolean getBooleanProperty(final String name) {\n      * @param defaultValue the default value to use if the property is undefined\n      * @return the boolean value of the property or {@code defaultValue} if undefined.\n      */\n-    @Nullable\n-    Boolean getBooleanProperty(String name, @Nullable Boolean defaultValue);\n+    Boolean getBooleanProperty(String name, Boolean defaultValue);\n \n     /**\n      * Gets the named property as a Charset value.\n      *\n      * @param name the name of the property to look up\n      * @return the Charset value of the property or {@link Charset#defaultCharset()} if undefined.\n      */\n+    @SuppressWarnings(\"null\")\n     default Charset getCharsetProperty(final String name) {\n         return getCharsetProperty(name, Charset.defaultCharset());\n     }\n@@ -73,39 +73,26 @@ default Charset getCharsetProperty(final String name) {\n      * @param defaultValue the default value to use if the property is undefined\n      * @return the Charset value of the property or {@code defaultValue} if undefined.\n      */\n-    @Nullable\n-    Charset getCharsetProperty(String name, @Nullable Charset defaultValue);\n+    Charset getCharsetProperty(String name, Charset defaultValue);\n \n     /**\n      * Gets the named property as a Class value.\n      *\n      * @param name         the name of the property to look up\n+     * @param upperBound the upper bound for the class\n      * @return the Class value of the property or {@code null} if it can not be loaded.\n      */\n-    default @Nullable Class<?> getClassProperty(final String name) {\n-        return getClassProperty(name, null);\n-    }\n-\n-    /**\n-     * Gets the named property as a Class value.\n-     *\n-     * @param name         the name of the property to look up\n-     * @param defaultValue the default value to use if the property is undefined\n-     * @return the Class value of the property or {@code defaultValue} if it can not be loaded.\n-     */\n-    default @Nullable Class<?> getClassProperty(final String name, final @Nullable Class<?> defaultValue) {\n-        return getClassProperty(name, defaultValue, Object.class);\n-    }\n+    <T> @Nullable Class<? extends T> getClassProperty(final String name, final Class<T> upperBound);\n \n     /**\n      * Gets the named property as a subclass of {@code upperBound}.\n      *\n      * @param name         the name of the property to look up\n      * @param defaultValue the default value to use if the property is undefined\n+     * @param upperBound the upper bound for the class\n      * @return the Class value of the property or {@code defaultValue} if it can not be loaded.\n      */\n-    <T> @Nullable Class<? extends T> getClassProperty(\n-            String name, @Nullable Class<? extends T> defaultValue, Class<T> upperBound);\n+    <T> Class<? extends T> getClassProperty(String name, Class<? extends T> defaultValue, Class<T> upperBound);\n \n     /**\n      * Gets the named property as {@link Duration}."
        },
        {
            "sha": "abdc482c489a36f1f68402e633aa0d59e4b55b0f",
            "filename": "log4j-kit/src/main/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironment.java",
            "status": "modified",
            "additions": 202,
            "deletions": 134,
            "changes": 336,
            "blob_url": "https://github.com/apache/logging-log4j2/blob/c624e01cc433cc36f23167c8f39ab40873ecc6ec/log4j-kit%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2Fsupport%2FBasicPropertyEnvironment.java",
            "raw_url": "https://github.com/apache/logging-log4j2/raw/c624e01cc433cc36f23167c8f39ab40873ecc6ec/log4j-kit%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2Fsupport%2FBasicPropertyEnvironment.java",
            "contents_url": "https://api.github.com/repos/apache/logging-log4j2/contents/log4j-kit%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2Fsupport%2FBasicPropertyEnvironment.java?ref=c624e01cc433cc36f23167c8f39ab40873ecc6ec",
            "patch": "@@ -16,6 +16,7 @@\n  */\n package org.apache.logging.log4j.kit.env.support;\n \n+import java.lang.reflect.AnnotatedElement;\n import java.lang.reflect.Constructor;\n import java.lang.reflect.Parameter;\n import java.lang.reflect.ParameterizedType;\n@@ -26,6 +27,10 @@\n import java.nio.charset.IllegalCharsetNameException;\n import java.time.Duration;\n import java.time.format.DateTimeParseException;\n+import java.util.Objects;\n+import java.util.function.Function;\n+import java.util.function.Supplier;\n+import org.apache.logging.log4j.Level;\n import org.apache.logging.log4j.Logger;\n import org.apache.logging.log4j.kit.env.Log4jProperty;\n import org.apache.logging.log4j.kit.env.PropertyEnvironment;\n@@ -46,125 +51,135 @@ protected BasicPropertyEnvironment(final Logger statusLogger) {\n     }\n \n     @Override\n-    public @Nullable Boolean getBooleanProperty(final String name, final @Nullable Boolean defaultValue) {\n-        final String prop = getStringProperty(name);\n-        return prop == null ? defaultValue : Boolean.parseBoolean(prop);\n+    public Boolean getBooleanProperty(final String name, final Boolean defaultValue) {\n+        return getObjectPropertyWithTypedDefault(name, this::toBoolean, defaultValue);\n+    }\n+\n+    @Override\n+    public Charset getCharsetProperty(final String name, final Charset defaultValue) {\n+        return getObjectPropertyWithTypedDefault(name, this::toCharset, defaultValue);\n+    }\n+\n+    @Override\n+    public <T> @Nullable Class<? extends T> getClassProperty(final String name, final Class<T> upperBound) {\n+        return getClassProperty(name, null, upperBound);\n+    }\n+\n+    @Override\n+    public <T> Class<? extends T> getClassProperty(\n+            final String name, final Class<? extends T> defaultValue, final Class<T> upperBound) {\n+        return getObjectPropertyWithTypedDefault(name, className -> toClass(className, upperBound), defaultValue);\n+    }\n+\n+    @Override\n+    public Duration getDurationProperty(final String name, final Duration defaultValue) {\n+        return getObjectPropertyWithTypedDefault(name, this::toDuration, defaultValue);\n+    }\n+\n+    @Override\n+    public Integer getIntegerProperty(final String name, final Integer defaultValue) {\n+        return getObjectPropertyWithTypedDefault(name, this::toInteger, defaultValue);\n+    }\n+\n+    @Override\n+    public Long getLongProperty(final String name, final Long defaultValue) {\n+        return getObjectPropertyWithTypedDefault(name, this::toLong, defaultValue);\n     }\n \n     @Override\n-    public @Nullable Charset getCharsetProperty(final String name, final @Nullable Charset defaultValue) {\n-        final String charsetName = getStringProperty(name);\n-        if (charsetName == null) {\n-            return defaultValue;\n+    public abstract @Nullable String getStringProperty(String name);\n+\n+    @Override\n+    public <T> T getProperty(final Class<T> propertyClass) {\n+        if (!propertyClass.isAnnotationPresent(Log4jProperty.class)) {\n+            throw new IllegalArgumentException(\"Unsupported configuration properties class '\" + propertyClass.getName()\n+                    + \"': missing '@Log4jProperty' annotation.\");\n         }\n+        return getRecordProperty(null, propertyClass);\n+    }\n+\n+    protected Class<?> getClassForName(final String className) throws ReflectiveOperationException {\n+        return Class.forName(className);\n+    }\n+\n+    protected Boolean toBoolean(final String value) {\n+        return Boolean.valueOf(value);\n+    }\n+\n+    protected @Nullable Charset toCharset(final String value) {\n         try {\n-            return Charset.forName(charsetName);\n+            return Charset.forName(value);\n         } catch (final IllegalCharsetNameException | UnsupportedOperationException e) {\n-            statusLogger.warn(\n-                    \"Unable to get Charset '{}' for property '{}', using default '{}'.\",\n-                    charsetName,\n-                    name,\n-                    defaultValue,\n-                    e);\n+            statusLogger.warn(\"Invalid Charset value '{}': {}\", value, e.getMessage(), e);\n         }\n-        return defaultValue;\n+        return null;\n     }\n \n-    @Override\n-    public <T> @Nullable Class<? extends T> getClassProperty(\n-            final String name, final @Nullable Class<? extends T> defaultValue, final Class<T> upperBound) {\n-        final String className = getStringProperty(name);\n-        if (className == null) {\n-            return defaultValue;\n+    protected @Nullable Duration toDuration(final CharSequence value) {\n+        try {\n+            return Duration.parse(value);\n+        } catch (final DateTimeParseException e) {\n+            statusLogger.warn(\"Invalid Duration value '{}': {}\", value, e.getMessage(), e);\n         }\n+        return null;\n+    }\n+\n+    protected char[] toCharArray(final String value) {\n+        return value.toCharArray();\n+    }\n+\n+    @SuppressWarnings(\"unchecked\")\n+    protected <T> @Nullable Class<? extends T> toClass(final String className, final Class<T> upperBound) {\n         try {\n             final Class<?> clazz = getClassForName(className);\n             if (upperBound.isAssignableFrom(clazz)) {\n                 return (Class<? extends T>) clazz;\n             }\n-            statusLogger.warn(\n-                    \"Unable to get Class '{}' for property '{}': class does not extend {}.\",\n-                    className,\n-                    name,\n-                    upperBound.getName());\n+            statusLogger.warn(\"Invalid Class value '{}': class does not extend {}.\", className, upperBound.getName());\n         } catch (final ReflectiveOperationException e) {\n-            statusLogger.warn(\n-                    \"Unable to get Class '{}' for property '{}', using default '{}'.\",\n-                    className,\n-                    name,\n-                    defaultValue,\n-                    e);\n+            statusLogger.warn(\"Invalid Class value '{}': {}\", className, e.getMessage(), e);\n         }\n-        return defaultValue;\n-    }\n-\n-    protected Class<?> getClassForName(final String className) throws ReflectiveOperationException {\n-        return Class.forName(className);\n+        return null;\n     }\n \n-    @Override\n-    public @Nullable Duration getDurationProperty(final String name, final @Nullable Duration defaultValue) {\n-        final String prop = getStringProperty(name);\n-        if (prop != null) {\n-            try {\n-                return Duration.parse(prop);\n-            } catch (final DateTimeParseException ignored) {\n-                statusLogger.warn(\n-                        \"Invalid Duration value '{}' for property '{}', using default '{}'.\", prop, name, defaultValue);\n-            }\n+    protected <T extends Enum<T>> @Nullable T toEnum(final String value, final Class<T> enumClass) {\n+        try {\n+            return Enum.valueOf(enumClass, value);\n+        } catch (final IllegalArgumentException e) {\n+            statusLogger.warn(\"Invalid enum value '{}' of type {}.\", value, enumClass.getName(), e);\n         }\n-        return defaultValue;\n+        return null;\n     }\n \n-    @Override\n-    public @Nullable Integer getIntegerProperty(final String name, final @Nullable Integer defaultValue) {\n-        final String prop = getStringProperty(name);\n-        if (prop != null) {\n-            try {\n-                return Integer.parseInt(prop);\n-            } catch (final Exception ignored) {\n-                statusLogger.warn(\n-                        \"Invalid integer value '{}' for property '{}', using default '{}'.\", prop, name, defaultValue);\n-            }\n+    protected @Nullable Integer toInteger(final String value) {\n+        try {\n+            return Integer.valueOf(value);\n+        } catch (final NumberFormatException e) {\n+            statusLogger.warn(\"Invalid integer value '{}': {}.\", value, e.getMessage(), e);\n         }\n-        return defaultValue;\n+        return null;\n     }\n \n-    @Override\n-    public @Nullable Long getLongProperty(final String name, final @Nullable Long defaultValue) {\n-        final String prop = getStringProperty(name);\n-        if (prop != null) {\n-            try {\n-                return Long.parseLong(prop);\n-            } catch (final Exception ignored) {\n-                statusLogger.warn(\n-                        \"Invalid long value '{}' for property '{}', using default '{}'.\", prop, name, defaultValue);\n-            }\n+    protected @Nullable Long toLong(final String value) {\n+        try {\n+            return Long.valueOf(value);\n+        } catch (final NumberFormatException e) {\n+            statusLogger.warn(\"Invalid long value '{}': {}.\", value, e.getMessage(), e);\n         }\n-        return defaultValue;\n+        return null;\n     }\n \n-    @Override\n-    public abstract @Nullable String getStringProperty(String name);\n+    protected @Nullable Level toLevel(final String value) {\n+        return Level.toLevel(value, null);\n+    }\n \n-    @Override\n-    public <T> T getProperty(final Class<T> propertyClass) {\n+    private <T> T getRecordProperty(final @Nullable String parentPrefix, final Class<T> propertyClass) {\n         if (!propertyClass.isRecord()) {\n             throw new IllegalArgumentException(\"Unsupported configuration properties class '\" + propertyClass.getName()\n                     + \"': class is not a record.\");\n         }\n-        if (propertyClass.getAnnotation(Log4jProperty.class) == null) {\n-            throw new IllegalArgumentException(\"Unsupported configuration properties class '\" + propertyClass.getName()\n-                    + \"': missing '@Log4jProperty' annotation.\");\n-        }\n-        return getProperty(null, propertyClass);\n-    }\n-\n-    private <T> T getProperty(final @Nullable String parentPrefix, final Class<T> propertyClass) {\n-        final Log4jProperty annotation = propertyClass.getAnnotation(Log4jProperty.class);\n-        final String prefix = parentPrefix != null\n-                ? parentPrefix\n-                : annotation != null && annotation.name().isEmpty() ? propertyClass.getSimpleName() : annotation.name();\n+        final String prefix =\n+                parentPrefix != null ? parentPrefix : getPropertyName(propertyClass, propertyClass::getSimpleName);\n \n         @SuppressWarnings(\"unchecked\")\n         final Constructor<T>[] constructors = (Constructor<T>[]) propertyClass.getDeclaredConstructors();\n@@ -178,66 +193,73 @@ private <T> T getProperty(final @Nullable String parentPrefix, final Class<T> pr\n         final Constructor<T> constructor = constructors[0];\n \n         final Parameter[] parameters = constructor.getParameters();\n-        final Object[] initArgs = new Object[parameters.length];\n+        final @Nullable Object[] initArgs = new Object[parameters.length];\n         for (int i = 0; i < initArgs.length; i++) {\n-            initArgs[i] = getProperty(prefix, parameters[i]);\n+            final String name = prefix + \".\" + getPropertyName(parameters[i], parameters[i]::getName);\n+            final String defaultValue = getPropertyDefaultAsString(parameters[i]);\n+            initArgs[i] = getObjectProperty(name, parameters[i].getParameterizedType(), defaultValue);\n         }\n         try {\n             return constructor.newInstance(initArgs);\n         } catch (final ReflectiveOperationException e) {\n-            statusLogger.warn(\"Unable to parse configuration properties class {}.\", propertyClass.getName(), e);\n-            return null;\n+            throw new IllegalArgumentException(\n+                    \"Unable to parse configuration properties class \" + propertyClass.getName() + \": \" + e.getMessage(),\n+                    e);\n         }\n     }\n \n-    private Object getProperty(final String parentPrefix, final Parameter parameter) {\n-        if (!parameter.isNamePresent()) {\n-            statusLogger.warn(\"Missing parameter name on configuration parameter {}.\", parameter);\n-            return null;\n-        }\n-        final String key = parentPrefix + \".\" + parameter.getName();\n-        final Class<?> type = parameter.getType();\n-        if (boolean.class.equals(type)) {\n-            return getBooleanProperty(key);\n-        }\n-        if (Class.class.equals(type)) {\n-            return getClassProperty(key, parameter.getAnnotatedType().getType());\n-        }\n-        if (Charset.class.equals(type)) {\n-            return getCharsetProperty(key);\n-        }\n-        if (Duration.class.equals(type)) {\n-            return getDurationProperty(key);\n+    private @Nullable Object getObjectProperty(\n+            final String name, final Type type, final @Nullable String defaultValue) {\n+        if (type instanceof final ParameterizedType parameterizedType\n+                && parameterizedType.getRawType().equals(Class.class)) {\n+            final Type[] arguments = parameterizedType.getActualTypeArguments();\n+            final Class<?> upperBound = arguments.length > 0 ? findUpperBound(arguments[0]) : Object.class;\n+            return getObjectPropertyWithStringDefault(name, defaultValue, className -> toClass(className, upperBound));\n         }\n-        if (Enum.class.isAssignableFrom(type)) {\n-            final String prop = getStringProperty(key);\n-            if (prop != null) {\n-                try {\n-                    return Enum.valueOf((Class<? extends Enum>) type, prop);\n-                } catch (final IllegalArgumentException e) {\n-                    statusLogger.warn(\"Invalid {} value '{}' for property '{}'.\", type.getSimpleName(), prop, key);\n-                }\n+        if (type instanceof final Class<?> clazz) {\n+            if (clazz.isRecord()) {\n+                return getRecordProperty(name, clazz);\n             }\n-            return null;\n-        }\n-        if (int.class.equals(type)) {\n-            return getIntegerProperty(key);\n-        }\n-        if (long.class.equals(type)) {\n-            return getLongProperty(key);\n-        }\n-        return String.class.equals(type) ? getStringProperty(key) : getProperty(key, type);\n-    }\n-\n-    private Object getClassProperty(final String key, final Type type) {\n-        Class<?> upperBound = Object.class;\n-        if (type instanceof final ParameterizedType parameterizedType) {\n-            final Type[] arguments = parameterizedType.getActualTypeArguments();\n-            if (arguments.length > 0) {\n-                upperBound = findUpperBound(arguments[0]);\n+            if (char[].class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, defaultValue, this::toCharArray);\n+            }\n+            if (boolean.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(\n+                        name, Objects.toString(defaultValue, \"false\"), this::toBoolean);\n+            }\n+            if (Boolean.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, defaultValue, this::toBoolean);\n+            }\n+            if (Charset.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, defaultValue, this::toCharset);\n+            }\n+            if (Duration.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, defaultValue, this::toDuration);\n+            }\n+            if (Enum.class.isAssignableFrom(clazz)) {\n+                return getObjectPropertyWithStringDefault(\n+                        name, defaultValue, value -> toEnum(value, (Class<? extends Enum>) clazz));\n+            }\n+            if (int.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, Objects.toString(defaultValue, \"0\"), this::toInteger);\n+            }\n+            if (Integer.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, defaultValue, this::toInteger);\n+            }\n+            if (long.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, Objects.toString(defaultValue, \"0\"), this::toLong);\n+            }\n+            if (Long.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, defaultValue, this::toLong);\n+            }\n+            if (Level.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, defaultValue, this::toLevel);\n+            }\n+            if (String.class.equals(clazz)) {\n+                return getObjectPropertyWithStringDefault(name, defaultValue, x -> x);\n             }\n         }\n-        return getClassProperty(key, null, upperBound);\n+        throw new IllegalArgumentException(\"Unsupported property of type '\" + type.getTypeName() + \"'\");\n     }\n \n     private Class<?> findUpperBound(final Type type) {\n@@ -251,4 +273,50 @@ private Class<?> findUpperBound(final Type type) {\n         }\n         return bounds.length > 0 && bounds[0] instanceof final Class<?> clazz ? clazz : Object.class;\n     }\n+\n+    private String getPropertyName(final AnnotatedElement element, final Supplier<String> fallback) {\n+        if (element.isAnnotationPresent(Log4jProperty.class)) {\n+            final String specifiedName =\n+                    element.getAnnotation(Log4jProperty.class).name();\n+            if (!specifiedName.isEmpty()) {\n+                return specifiedName;\n+            }\n+        }\n+        return fallback.get();\n+    }\n+\n+    private @Nullable String getPropertyDefaultAsString(final AnnotatedElement parameter) {\n+        if (parameter.isAnnotationPresent(Log4jProperty.class)) {\n+            final String defaultValue =\n+                    parameter.getAnnotation(Log4jProperty.class).defaultValue();\n+            if (!defaultValue.isEmpty()) {\n+                return defaultValue;\n+            }\n+        }\n+        return null;\n+    }\n+\n+    private <T> @Nullable Object getObjectPropertyWithStringDefault(\n+            final String name, final @Nullable String defaultValue, final Function<? super String, ?> converter) {\n+        final String prop = getStringProperty(name);\n+        if (prop != null) {\n+            final @Nullable Object value = converter.apply(prop);\n+            if (value != null) {\n+                return value;\n+            }\n+        }\n+        return defaultValue != null ? converter.apply(defaultValue) : null;\n+    }\n+\n+    private <T> T getObjectPropertyWithTypedDefault(\n+            final String name, final Function<? super String, ? extends @Nullable T> converter, final T defaultValue) {\n+        final String prop = getStringProperty(name);\n+        if (prop != null) {\n+            final @Nullable T value = converter.apply(prop);\n+            if (value != null) {\n+                return value;\n+            }\n+        }\n+        return defaultValue;\n+    }\n }"
        },
        {
            "sha": "4716ef565c10a70c8a368056f8f6ad6fa96cdc31",
            "filename": "log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java",
            "status": "modified",
            "additions": 124,
            "deletions": 55,
            "changes": 179,
            "blob_url": "https://github.com/apache/logging-log4j2/blob/c624e01cc433cc36f23167c8f39ab40873ecc6ec/log4j-kit%2Fsrc%2Ftest%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2Fsupport%2FBasicPropertyEnvironmentTest.java",
            "raw_url": "https://github.com/apache/logging-log4j2/raw/c624e01cc433cc36f23167c8f39ab40873ecc6ec/log4j-kit%2Fsrc%2Ftest%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2Fsupport%2FBasicPropertyEnvironmentTest.java",
            "contents_url": "https://api.github.com/repos/apache/logging-log4j2/contents/log4j-kit%2Fsrc%2Ftest%2Fjava%2Forg%2Fapache%2Flogging%2Flog4j%2Fkit%2Fenv%2Fsupport%2FBasicPropertyEnvironmentTest.java?ref=c624e01cc433cc36f23167c8f39ab40873ecc6ec",
            "patch": "@@ -16,82 +16,159 @@\n  */\n package org.apache.logging.log4j.kit.env.support;\n \n+import static java.nio.charset.StandardCharsets.UTF_8;\n import static org.assertj.core.api.Assertions.assertThat;\n \n import java.nio.charset.Charset;\n-import java.nio.charset.StandardCharsets;\n import java.time.Duration;\n import java.util.Collections;\n import java.util.List;\n import java.util.Map;\n import java.util.stream.Stream;\n+import org.apache.logging.log4j.Level;\n import org.apache.logging.log4j.Logger;\n import org.apache.logging.log4j.kit.env.Log4jProperty;\n import org.apache.logging.log4j.kit.env.PropertyEnvironment;\n import org.apache.logging.log4j.kit.logger.TestListLogger;\n import org.apache.logging.log4j.spi.StandardLevel;\n+import org.apache.logging.log4j.status.StatusLogger;\n import org.assertj.core.api.Assertions;\n+import org.jspecify.annotations.Nullable;\n import org.junit.jupiter.api.Test;\n import org.junit.jupiter.params.ParameterizedTest;\n import org.junit.jupiter.params.provider.Arguments;\n import org.junit.jupiter.params.provider.MethodSource;\n \n+/**\n+ * Tests the property values that are used as properties.\n+ */\n class BasicPropertyEnvironmentTest {\n \n-    private static final Map<String, String> TEST_PROPS = Map.of(\n-            \"ComponentProperties.boolAttr\",\n-            \"true\",\n-            \"ComponentProperties.intAttr\",\n-            \"123\",\n-            \"ComponentProperties.longAttr\",\n-            \"123456\",\n-            \"ComponentProperties.charsetAttr\",\n+    @Log4jProperty\n+    record BasicValues(boolean boolAttr, int intAttr, long longAttr) {}\n+\n+    @Log4jProperty\n+    record DefaultBasicValues(\n+            @Log4jProperty(defaultValue = \"true\") boolean boolAttr,\n+            @Log4jProperty(defaultValue = \"123\") int intAttr,\n+            @Log4jProperty(defaultValue = \"123456\") long longAttr) {}\n+\n+    @Log4jProperty(name = \"BasicValues\")\n+    record BoxedBasicValues(@Nullable Boolean boolAttr, @Nullable Integer intAttr, @Nullable Long longAttr) {}\n+\n+    private static final Map<String, String> BASIC_PROPS =\n+            Map.of(\"BasicValues.boolAttr\", \"true\", \"BasicValues.intAttr\", \"123\", \"BasicValues.longAttr\", \"123456\");\n+\n+    @Test\n+    void should_support_basic_values() {\n+        assertMapConvertsTo(Map.of(), new BasicValues(false, 0, 0L));\n+        assertMapConvertsTo(BASIC_PROPS, new BasicValues(true, 123, 123456));\n+        // Default values\n+        assertMapConvertsTo(Map.of(), new DefaultBasicValues(true, 123, 123456));\n+    }\n+\n+    @Test\n+    void should_support_boxed_values() {\n+        assertMapConvertsTo(Map.of(), new BoxedBasicValues(null, null, null));\n+        assertMapConvertsTo(BASIC_PROPS, new BoxedBasicValues(true, 123, 123456L));\n+        // No need to test default values, since properties with a default value should be primitives\n+    }\n+\n+    @Log4jProperty\n+    record ScalarValues(\n+            @Nullable Charset charsetAttr,\n+            @Nullable Duration durationAttr,\n+            @Nullable String stringAttr,\n+            @Nullable StandardLevel enumAttr,\n+            @Nullable Level levelAttr) {}\n+\n+    @Log4jProperty\n+    record DefaultScalarValues(\n+            @Log4jProperty(defaultValue = \"UTF-8\") Charset charsetAttr,\n+            @Log4jProperty(defaultValue = \"PT8H\") Duration durationAttr,\n+            @Log4jProperty(defaultValue = \"Hello child!\") String stringAttr,\n+            @Log4jProperty(defaultValue = \"WARN\") StandardLevel enumAttr,\n+            @Log4jProperty(defaultValue = \"INFO\") Level levelAttr) {}\n+\n+    private static final Map<String, String> SCALAR_PROPS = Map.of(\n+            \"ScalarValues.charsetAttr\",\n             \"UTF-8\",\n-            \"ComponentProperties.durationAttr\",\n+            \"ScalarValues.durationAttr\",\n             \"PT8H\",\n-            \"ComponentProperties.stringAttr\",\n+            \"ScalarValues.stringAttr\",\n             \"Hello child!\",\n-            \"ComponentProperties.classAttr\",\n-            \"org.apache.logging.log4j.kit.env.PropertyEnvironment\",\n-            \"ComponentProperties.level\",\n-            \"INFO\",\n-            \"ComponentProperties.subComponent.subProperty\",\n-            \"Hello parent!\");\n+            \"ScalarValues.enumAttr\",\n+            \"WARN\",\n+            \"ScalarValues.levelAttr\",\n+            \"INFO\");\n+\n+    @Test\n+    void should_support_scalar_values() {\n+        assertMapConvertsTo(Map.of(), new ScalarValues(null, null, null, null, null));\n+        assertMapConvertsTo(\n+                SCALAR_PROPS,\n+                new ScalarValues(UTF_8, Duration.ofHours(8), \"Hello child!\", StandardLevel.WARN, Level.INFO));\n+        // Default values\n+        assertMapConvertsTo(\n+                SCALAR_PROPS,\n+                new DefaultScalarValues(UTF_8, Duration.ofHours(8), \"Hello child!\", StandardLevel.WARN, Level.INFO));\n+    }\n+\n+    @Log4jProperty\n+    record ArrayValues(char @Nullable [] password) {}\n+\n+    private static final Map<String, String> ARRAY_PROPS = Map.of(\"ArrayValues.password\", \"changeit\");\n \n     @Test\n-    void get_property_should_support_records() {\n+    void should_support_arrays_of_scalars() {\n         final TestListLogger logger = new TestListLogger(BasicPropertyEnvironmentTest.class.getName());\n-        final PropertyEnvironment env = new TestPropertyEnvironment(TEST_PROPS, logger);\n-        final ComponentProperties expected = new ComponentProperties(\n-                true,\n-                123,\n-                123456L,\n-                StandardCharsets.UTF_8,\n-                Duration.ofHours(8),\n-                \"Hello child!\",\n-                PropertyEnvironment.class,\n-                StandardLevel.INFO,\n-                new SubComponentProperties(\"Hello parent!\"));\n-        assertThat(env.getProperty(ComponentProperties.class)).isEqualTo(expected);\n+        // Missing properties\n+        PropertyEnvironment env = new TestPropertyEnvironment(Map.of(), logger);\n+        ArrayValues actual = env.getProperty(ArrayValues.class);\n+        assertThat(actual.password()).isNull();\n+        // With properties\n+        env = new TestPropertyEnvironment(ARRAY_PROPS, logger);\n+        actual = env.getProperty(ArrayValues.class);\n+        assertThat(actual.password()).containsExactly(\"changeit\".toCharArray());\n+        // Check for warnings\n         assertThat(logger.getMessages()).isEmpty();\n     }\n \n-    static Stream<Arguments> get_property_should_check_bounds() {\n+    @Log4jProperty\n+    record Component(@Nullable String type, SubComponent subComponent) {}\n+\n+    // Subcomponents shouldn't be annotated.\n+    record SubComponent(@Nullable String type) {}\n+\n+    private static final Map<String, String> COMPONENT_PROPS =\n+            Map.of(\"Component.type\", \"COMPONENT\", \"Component.subComponent.type\", \"SUBCOMPONENT\");\n+\n+    @Test\n+    void should_support_nested_records() {\n+        assertMapConvertsTo(Map.of(), new Component(null, new SubComponent(null)));\n+        assertMapConvertsTo(COMPONENT_PROPS, new Component(\"COMPONENT\", new SubComponent(\"SUBCOMPONENT\")));\n+    }\n+\n+    @Log4jProperty\n+    record BoundedClass(Class<? extends Number> className) {}\n+\n+    @Log4jProperty\n+    record BoundedClassParam<T extends Number>(Class<T> className) {}\n+\n+    static Stream<Arguments> should_support_classes_with_bounds() {\n         return Stream.of(\n                 Arguments.of(\n                         \"BoundedClass.className\",\n                         \"java.lang.String\",\n                         BoundedClass.class,\n                         new BoundedClass(null),\n-                        List.of(\"Unable to get Class 'java.lang.String' for property 'BoundedClass.className': \"\n-                                + \"class does not extend java.lang.Number.\")),\n+                        List.of(\"Invalid Class value 'java.lang.String': class does not extend java.lang.Number.\")),\n                 Arguments.of(\n                         \"BoundedClassParam.className\",\n                         \"java.lang.String\",\n                         BoundedClassParam.class,\n                         new BoundedClassParam(null),\n-                        List.of(\"Unable to get Class 'java.lang.String' for property 'BoundedClassParam.className': \"\n-                                + \"class does not extend java.lang.Number.\")),\n+                        List.of(\"Invalid Class value 'java.lang.String': class does not extend java.lang.Number.\")),\n                 Arguments.of(\n                         \"BoundedClass.className\",\n                         \"java.lang.Integer\",\n@@ -108,7 +185,7 @@ static Stream<Arguments> get_property_should_check_bounds() {\n \n     @ParameterizedTest\n     @MethodSource\n-    void get_property_should_check_bounds(\n+    void should_support_classes_with_bounds(\n             final String key,\n             final String value,\n             final Class<?> clazz,\n@@ -120,30 +197,22 @@ void get_property_should_check_bounds(\n         Assertions.<String>assertThat(logger.getMessages()).containsExactlyElementsOf(expectedMessages);\n     }\n \n-    @Log4jProperty\n-    record ComponentProperties(\n-            boolean boolAttr,\n-            int intAttr,\n-            long longAttr,\n-            Charset charsetAttr,\n-            Duration durationAttr,\n-            String stringAttr,\n-            Class<?> classAttr,\n-            StandardLevel level,\n-            SubComponentProperties subComponent) {}\n-\n-    record SubComponentProperties(String subProperty) {}\n-\n-    @Log4jProperty\n-    record BoundedClass(Class<? extends Number> className) {}\n-\n-    @Log4jProperty\n-    record BoundedClassParam<T extends Number>(Class<T> className) {}\n+    private void assertMapConvertsTo(final Map<String, String> map, final Object expected) {\n+        final TestListLogger logger = new TestListLogger(BasicPropertyEnvironmentTest.class.getName());\n+        final PropertyEnvironment env = new TestPropertyEnvironment(map, logger);\n+        final Object actual = env.getProperty(expected.getClass());\n+        assertThat(actual).isEqualTo(expected);\n+        assertThat(logger.getMessages()).isEmpty();\n+    }\n \n     private static class TestPropertyEnvironment extends BasicPropertyEnvironment {\n \n         private final Map<String, String> props;\n \n+        public TestPropertyEnvironment(final Map<String, String> props) {\n+            this(props, StatusLogger.getLogger());\n+        }\n+\n         public TestPropertyEnvironment(final Map<String, String> props, final Logger logger) {\n             super(logger);\n             this.props = props;"
        }
    ]
}

Similarly, the endpoint https://api.github.com/repos/apache/logging-log4j2/commits?sha=c624e01cc4 gives me a good response

tsantalis commented 1 week ago

@victorgveloso It is a Github API issue from the kohsuke library. It is not related to the fix discussed in this issue.

pouryafard75 commented 1 week ago

Its strange because it worked properly two days ago when I took the screenshot!